Tengo una matriz de Ruby que contiene algunos valores de cadena. Necesito:
- Encuentra todos los elementos que coinciden con algún predicado
- Ejecute los elementos coincidentes a través de una transformación
- Devuelve los resultados como una matriz
En este momento, mi solución se ve así:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
¿Existe un método Array o Enumerable que combine select y map en una sola declaración lógica?

Enumerable#grepmétodo hace exactamente lo que se le pidió y ha estado en Ruby durante más de diez años. Requiere un argumento predicado y un bloque de transformación. @hirolau da la única respuesta correcta a esta pregunta.filter_mapcon este propósito exacto. Más info aquí .Respuestas:
Normalmente utilizo
mapycompactjunto con mis criterios de selección como sufijoif.compactse deshace de los nulos.fuente
map+compactrealmente funcionaría mejor queinjecty publiqué mis resultados de referencia en un hilo relacionado: stackoverflow.com/questions/310426/list-comprehension-in-ruby/…mapyselect, es solo quecompactes un caso especialrejectque funciona con cero y funciona algo mejor debido a que se implementó directamente en C.Puede usar
reducepara esto, que requiere solo un pase:En otras palabras, inicialice el estado para que sea el que desee (en nuestro caso, una lista vacía para llenar :)
[], luego asegúrese siempre de devolver este valor con modificaciones para cada elemento en la lista original (en nuestro caso, el elemento modificado empujado a la lista).Este es el más eficiente ya que solo recorre la lista con un pase (
map+selectocompactrequiere dos pases).En tu caso:
fuente
each_with_objecttiene más sentido? No es necesario que devuelva la matriz al final de cada iteración del bloque. Simplemente puede hacerlomy_array.each_with_object([]) { |i, a| a << i if i.condition }.reduceprimero 😊Ruby 2.7+
¡Hay ahora!
Ruby 2.7 se presenta
filter_mapcon este propósito exacto. Es idiomático y performativo, y espero que se convierta en la norma muy pronto.Por ejemplo:
He aquí una buena lectura sobre el tema .
¡Espero que sea útil para alguien!
fuente
filter, dado que ,selectyfind_allson sinónimos, talmapy comocollectson, puede resultar difícil recordar el nombre de este método. Es quefilter_map,select_collect,find_all_mapofilter_collect?Otra forma diferente de abordar esto es usando el nuevo (en relación con esta pregunta)
Enumerator::Lazy:El
.lazymétodo devuelve un enumerador perezoso. Llamar.selecto.mapen un enumerador perezoso devuelve otro enumerador perezoso. Solo una vez que llama.uniq, realmente fuerza al enumerador y devuelve una matriz. Entonces, lo que sucede efectivamente es que sus llamadas.selecty.mapse combinan en una: solo se repite una@linesvez para hacer ambas.selecty.map.Mi instinto es que el
reducemétodo de Adam será un poco más rápido, pero creo que es mucho más legible.La consecuencia principal de esto es que no se crean objetos de matriz intermedios para cada llamada de método subsiguiente. En una
@lines.select.mapsituación normal ,selectdevuelve una matriz que luego es modificada pormap, devolviendo nuevamente una matriz. En comparación, la evaluación diferida solo crea una matriz una vez. Esto es útil cuando su objeto de colección inicial es grande. También le permite trabajar con enumeradores infinitos, por ejemplorandom_number_generator.lazy.select(&:odd?).take(10).fuente
reducecomo una transformación de "hacer todo" siempre me parece bastante complicado.@linesvez para hacer ambas.selecty.map". El uso.lazyno significa que las operaciones encadenadas en un enumerador perezoso se "colapsen" en una sola iteración. Este es un malentendido común de la evaluación perezosa con operaciones de encadenamiento sobre una colección. (Puede probar esto agregando unaputsdeclaración al principio de los bloquesselectymapen el primer ejemplo. Verá que imprimen el mismo número de líneas).lazyse imprime la misma cantidad de veces. Ese es mi punto: sumapbloqueo y suselectbloqueo se ejecutan el mismo número de veces en las versiones perezosa y ansiosa. La versión perezosa no "combina sus llamadas.selecty.map"lazycombina porque un elemento que falla laselectcondición no se pasa almap. En otras palabras: anteponerlazyes más o menos equivalente a reemplazarselectymapcon un bloque únicoreduce([]), y "inteligentemente" haciendo queselectel bloque sea una condición previa para la inclusión enreduceel resultado de.Si tiene un
selectque puede usar elcaseoperador (===),grepes una buena alternativa:Si necesitamos una lógica más compleja podemos crear lambdas:
fuente
No estoy seguro de que haya uno. El módulo Enumerable , que agrega
selectymap, no muestra uno.Se le pedirá que pase en dos cuadras hasta el
select_and_transformmétodo, lo que sería un poco intuitivo en mi humilde opinión.Obviamente, podría simplemente encadenarlos, lo que es más legible:
fuente
Respuesta simple:
Si tiene n registros y lo desea
selecty enmapfunción de la condición,Aquí, el atributo es lo que desee del registro y la condición que puede marcar.
compacto es eliminar los cero innecesarios que surgieron de esa condición si
fuente
No, pero puedes hacerlo así:
O mejor:
fuente
reject(&:nil?)es básicamente lo mismo quecompact.Creo que de esta manera es más legible, porque divide las condiciones del filtro y el valor mapeado mientras queda claro que las acciones están conectadas:
Y, en su caso específico, elimine la
resultvariable por completo:fuente
En Ruby 1.9 y 1.8.7, también puede encadenar y envolver iteradores simplemente sin pasarles un bloque:
Pero no es realmente posible en este caso, ya que los tipos de bloque devuelven valores de
selectymapno coinciden. Tiene más sentido para algo como esto:AFAICS, lo mejor que puede hacer es el primer ejemplo.
He aquí un pequeño ejemplo:
Pero lo que realmente quieres es
["A", "B", "C", "D"].fuente
selectdevuelve un valor de tipo booleano que decide si conservar el elemento o no,mapdevuelve el valor transformado. El valor transformado en sí probablemente será verdadero, por lo que se seleccionan todos los elementos.Debería intentar usar mi biblioteca Rearmed Ruby en la que agregué el método
Enumerable#select_map. He aquí un ejemplo:fuente
select_mapen esta biblioteca simplemente implementa la mismaselect { |i| ... }.map { |i| ... }estrategia de muchas respuestas anteriores.Si no desea crear dos matrices diferentes, puede usarlo,
compact!pero tenga cuidado.Curiosamente,
compact!hace una eliminación in situ de nil. El valor de retorno decompact!es la misma matriz si hubo cambios, pero nulo si no hubo nulos.Sería un trazador de líneas.
fuente
Tu versión:
Mi version:
Esto hará 1 iteración (excepto la ordenación) y tiene la ventaja adicional de mantener la singularidad (si no le importa uniq, simplemente haga que los resultados sean una matriz y
results.push(line) if ...fuente
He aquí un ejemplo. No es lo mismo que su problema, pero puede ser lo que desee o puede dar una pista de su solución:
fuente