Usar select, reject, collect, inject y detect

Del blog de Mathew Carriere me tomo el atrevimiento de traducir este post que me resultó muy interesante. Si bien es básico, no por eso se desmerece. Son múltiples las razones por las que traduzco esto:

  • sirve de referencia para mis alumnos
  • es útil para ilustrar el block closures de Ruby y los iteradores
  • nunca está de más conocer otro lenguaje

Gasto mucho tiempo convenciendo a mis amigos a que se cambien a Mac. Algunos de mis amigos son desarrolladores de software, de modo que cuando consideran que el evangelismo ha terminado, los convenzo de subirse a Rails. Sin embargo, el aprender Rails usualmente significa aprender Ruby por primera vez. En este post voy a mostrar uno de los temas que veo necesarios para los nuevos en Ruby. Iteraciones.

Las iteraciones en Ruby parecen implicar un proceso de evolución para los principiantes en el lenguaje. Ellos siempre encontrarán su forma de iterar y cuando sean confrontados a hacerlo con un array, su primera elección será un for..in

  1. a = [1,2,3,4]
  2. for n in a
  3. puts n
  4. end

Esto funciona, pero no es muy... Ruby. La siguiente etapa en la evolución ocurrirá cuando se use un iterador por primera vez. Por lo tanto todo el bucle for es desechado y each usado en su lugar. El Rubista nace en este punto:

  1. a.each do |n|
  2. puts n
  3. end

Lo que vemos a continuación es un poco de la lógica condicional usada dentro de cada bloque. La lógica es generalmente introducida para realizar las siguientes operaciones.

1. Construir una lista de los elementos de un array.

2. Totalizar los elementos de un array.

3. Encontrar un item en un array.

Si asi es como piensas, detente. Ruby tiene muchos iteradores más. Dependiendo de lo que estés intentando realizar deberías ver que es lo que hay disponible. Por lo tanto revisa la lista anterior y observa si podemos encontrar una forma Ruby de hacerlo.

Construir una lista de elemtos desde un array usando select

Para esta operación deberías usar select. La forma en que select trabaja es simple. Básicamente itera a traves de todos los elementos de tu array y ejecuta tu lógica en cada uno de ellos. Si la lógica devuelve TRUE, entonces agrega el elemento a un nuevo array que es devuelto cuando la operación haya terminado. Aquí hay un ejemplo.

  1. a = [1,2,3,4] a.select {|n| n > 2}

Esto devolverá los dos últimos elementos en el array: 3 y 4. ¿Porqué? Porque 3 y 4 son ambos mayores que 2, de acuerdo a la lógica que pusimos en el bloque. Es de notar que select tiene una hermana malvada llamada reject. Esta ejecutará la operación opuesta a select. La lógica que devuelve FALSE agrega un item al array que es devuelto. Aquí hay un ejemplo como el anterior, excepto que intercambiaremos select por reject:

  1. a = [1,2,3,4] a.reject {|n| n > 2}

En este ejemplo el valor devuelto es [1,2] debido a que estos elementos devolvieron falso cuando la condición fue testeada.

Tambien tengo que mencionar otro hermano cercano de select y reject, collect, el cual devuelve un array de valores que son el RESULTADO de la lógica en el bloque. Previamente hemos devuelto los items basados en el resultado de la CONDICION en el bloque. Quizas necesitemos obtener el cuadrado de los valores de nuestro array.

  1. a = [1,2,3,4] a.collect {|n| n*n}

Esto devuelve un nuevo array con cada item nuestro array al cuadrado

Finalmente, notemos que usar select, reject y collect devuelve un array. Si quieres devolver algo diferente porque estas concatenando o totalizando valores, entonces chequea inject.

Totalizar los items en un array usando inject

Cuando piensas en acumular, concatenar, o totalizar valores en un array, entonces debes pensar en inject. La principal diferencia entre select e inject es que inject te da otra variable para usar en el bloque. Esta variable, referenciada como el acumulador, se usa para almacenar el total o concatenación de cada iteración. El valor agregado al acumulador es el resultado de la lógica que colocas en el bloque. Al final de cada iteracion, sin importar de que valor sea, puede ser agregado al acumulador. Por ejemplo, sumemos todos los numeros juntos en nuestro array.

  1. a = [1,2,3,4] a.inject {|acc,n| acc + n}

Esto devolverá 10. El valor total de todos los elementos de nuestro array. La lógica en el bloque es simple: agregar el elemento actual al acumulador. Recuerda, debes hacer algo con el acumulador en cada iteración. Si simplemente hemos agregado n en el bloque el valor final del acumulador debería haber sido 4. ¿Porqué? Es debido a que es el último valor en el array y además como no lo hemos agregado explícitamente al acumulador, este valor es reemplazado en cada iteración.

Puedes tambien usar un parámetro con la llamada inject para determinar cual es el valor por defecto para el acumulador.

  1. a = [1,2,3,4] a.inject(10) {|acc,n| acc + n}

En este ejemplo el resultado es 10 debido a que hemos asignado el acumular un valor inicial de 10.

Si necesitas devolver una cadena o un array desde inject, entonces necesitarás tratar a la variable acumuladora de ese modo. Puedes unsar el parámetro del valor por defecto de inject para hacer esto:

  1. a = [1,2,3,4] a.inject([]) {|acc,n| acc << n+n}

En este ejemplo sumé n a sí misma y entonces la agregué a la variable acumuladora. Inicialicé el acumulador como un array vacío usando el parámetro de valor por defecto.

Encontrar un intem en el array usando detect

Nuestra última operación de ejemplo fue encontrar un elemento en el array. Saquemosla de ahi y digamos que otros iteradores podrían ser usados para seleccionar el valor correcto del array, pero voy a mostrate como usar detect para redonder nuestras exploraciones de estos iteradores.

Encontremos el valor 3 en un array usando detect:

  1. a = [1,2,3,4]
  2. a.detect {|n| n == 3}

Esto devuelve 3. El valor que hemos estado buscando. Si el valor no ha sido hallado, entonces el iterador devuelve nil.

Por lo tanto si tu cabeza ha estado tratando de hilvanar como determinar que iterador usar y cuando, recuerda esto:

1. Usa select o reject si necesitas seleccionar o rechazar items basados en una condición.

2. Usa collect si necesitas construir un array de resultados a partir de la lógica en el bloque.

3. Usa inject si necesitas acumular, totalizar o concatenar valores de un array

4. Usa detect si necesitas encontrar un item en un array.

 

Al usar estos iteradores estarás un paso mas cerca a ser un maestro.... Ruby-fu.