Tengo uno map
que cambia un valor o lo establece en cero. Luego quiero eliminar las entradas nulas de la lista. La lista no necesita ser mantenida.
Esto es lo que tengo actualmente:
# A simple example function, which returns a value or nil
def transform(n)
rand > 0.5 ? n * 10 : nil }
end
items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]
Soy consciente de que podría hacer un bucle y recopilar condicionalmente en otra matriz como esta:
new_items = []
items.each do |x|
x = transform(x)
new_items.append(x) unless x.nil?
end
items = new_items
Pero no parece tan idiomático. ¿Hay una buena manera de mapear una función sobre una lista, eliminando / excluyendo los nulos a medida que avanza?
filter_map
, que parece ser perfecto para esto. Ahorra la necesidad de volver a procesar la matriz, en lugar de obtenerla como desee la primera vez. Más información aquí.Respuestas:
Ruby 2.7+
Hay ahora!
Ruby 2.7 se presenta
filter_map
para este propósito exacto. Es idiomático y eficaz, y espero que se convierta en la norma muy pronto.Por ejemplo:
En su caso, a medida que el bloque se evalúa como falsey, simplemente:
" Ruby 2.7 agrega Enumerable # filter_map " es una buena lectura sobre el tema, con algunos puntos de referencia de rendimiento contra algunos de los enfoques anteriores de este problema:
fuente
Podrías usar
compact
:Me gustaría recordarle a la gente que si obtiene una matriz que contiene nils como la salida de un
map
bloque, y ese bloque intenta devolver valores condicionalmente, entonces tiene olor a código y necesita repensar su lógica.Por ejemplo, si estás haciendo algo que hace esto:
Entonces no lo hagas. En cambio, antes del
map
,reject
las cosas que no quieres oselect
lo que quieres:Considero usar
compact
para limpiar un desastre como un último esfuerzo para deshacernos de las cosas que no manejamos correctamente, generalmente porque no sabíamos lo que nos esperaba. Siempre debemos saber qué tipo de datos se arrojan en nuestro programa; Los datos inesperados / desconocidos son malos. Cada vez que veo nils en una matriz en la que estoy trabajando, profundizo en por qué existen y veo si puedo mejorar el código que genera la matriz, en lugar de permitir que Ruby pierda tiempo y memoria generando nils y luego revisando la matriz para eliminar ellos luego.fuente
nil
entradas, no cadenas vacías. Por cierto,nil
no es lo mismo que una cadena vacía.reduce
oinject
?compact
es más rápido, pero en realidad escribir el código correctamente al principio elimina la necesidad de tratar con nils por completoIntenta usar
reduce
oinject
.Estoy de acuerdo con la respuesta aceptada que no debemos
map
ycompact
, pero no por las mismas razones.Siento en el fondo que
map
esocompact
es equivalente aselect
entoncesmap
. Considere:map
es una función uno a uno. Si está mapeando desde un conjunto de valores, y ustedmap
, entonces desea un valor en el conjunto de salida para cada valor en el conjunto de entrada. Si tiene que hacerloselect
de antemano, entonces probablemente no quiera unmap
en el set. Si tiene que hacerloselect
después (ocompact
), entonces probablemente no quiera unmap
en el set. En cualquier caso, está iterando dos veces en todo el conjunto, cuandoreduce
solo necesita ir una vez.Además, en inglés, está tratando de "reducir un conjunto de enteros en un conjunto de enteros pares".
fuente
En tu ejemplo:
no parece que los valores hayan cambiado más que ser reemplazados por
nil
. Si ese es el caso, entonces:Será suficiente.
fuente
Si desea un criterio más flexible para el rechazo, por ejemplo, para rechazar cadenas vacías y nulas, puede usar:
Si desea ir más allá y rechazar valores cero (o aplicar una lógica más compleja al proceso), puede pasar un bloque para rechazar:
fuente
blank?
que solo está disponible en rieles, podríamos usar elitems.reject!(&:nil?) # [1, nil, 3, nil, nil] => [1, 3]
que no está acoplado a los rieles. (sin embargo, no excluiría cadenas vacías o ceros)Definitivamente
compact
es el mejor enfoque para resolver esta tarea. Sin embargo, podemos lograr el mismo resultado solo con una simple resta:fuente
each_with_object
es probablemente la forma más limpia de ir aquí:En mi opinión,
each_with_object
es mejor queinject
/reduce
en casos condicionales porque no tiene que preocuparse por el valor de retorno del bloque.fuente
Una forma más de lograrlo será como se muestra a continuación. Aquí, utilizamos
Enumerable#each_with_object
para recopilar valores, y utilizamosObject#tap
para deshacernos de la variable temporal que de otro modo sería necesaria paranil
verificar el resultado delprocess_x
método.Ejemplo completo para ilustración:
Enfoque alternativo:
Al observar el método que está llamando
process_x url
, no está claro cuál es el propósito de la entradax
en ese método. Si supongo que va a procesar el valor dex
pasándolo un pocourl
y determinar cuál de losx
s realmente se procesa en resultados válidos no nulos, entonces, puede serEnumerabble.group_by
una mejor opción queEnumerable#map
.fuente