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#grep
mé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_map
con este propósito exacto. Más info aquí .Respuestas:
Normalmente utilizo
map
ycompact
junto con mis criterios de selección como sufijoif
.compact
se deshace de los nulos.fuente
map
+compact
realmente funcionaría mejor queinject
y publiqué mis resultados de referencia en un hilo relacionado: stackoverflow.com/questions/310426/list-comprehension-in-ruby/…map
yselect
, es solo quecompact
es un caso especialreject
que funciona con cero y funciona algo mejor debido a que se implementó directamente en C.Puede usar
reduce
para 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
+select
ocompact
requiere dos pases).En tu caso:
fuente
each_with_object
tiene 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 }
.reduce
primero 😊Ruby 2.7+
¡Hay ahora!
Ruby 2.7 se presenta
filter_map
con 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 ,select
yfind_all
son sinónimos, talmap
y comocollect
son, puede resultar difícil recordar el nombre de este método. Es quefilter_map
,select_collect
,find_all_map
ofilter_collect
?Otra forma diferente de abordar esto es usando el nuevo (en relación con esta pregunta)
Enumerator::Lazy
:El
.lazy
método devuelve un enumerador perezoso. Llamar.select
o.map
en 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.select
y.map
se combinan en una: solo se repite una@lines
vez para hacer ambas.select
y.map
.Mi instinto es que el
reduce
mé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.map
situación normal ,select
devuelve 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
reduce
como una transformación de "hacer todo" siempre me parece bastante complicado.@lines
vez para hacer ambas.select
y.map
". El uso.lazy
no 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 unaputs
declaración al principio de los bloquesselect
ymap
en el primer ejemplo. Verá que imprimen el mismo número de líneas).lazy
se imprime la misma cantidad de veces. Ese es mi punto: sumap
bloqueo y suselect
bloqueo se ejecutan el mismo número de veces en las versiones perezosa y ansiosa. La versión perezosa no "combina sus llamadas.select
y.map
"lazy
combina porque un elemento que falla laselect
condición no se pasa almap
. En otras palabras: anteponerlazy
es más o menos equivalente a reemplazarselect
ymap
con un bloque únicoreduce([])
, y "inteligentemente" haciendo queselect
el bloque sea una condición previa para la inclusión enreduce
el resultado de.Si tiene un
select
que puede usar elcase
operador (===
),grep
es 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
select
ymap
, no muestra uno.Se le pedirá que pase en dos cuadras hasta el
select_and_transform
mé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
select
y enmap
funció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
result
variable 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
select
ymap
no 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
select
devuelve un valor de tipo booleano que decide si conservar el elemento o no,map
devuelve 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_map
en 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