Tengo uno mapque 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_mappara 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
mapbloque, 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,rejectlas cosas que no quieres oselectlo que quieres:Considero usar
compactpara 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
nilentradas, no cadenas vacías. Por cierto,nilno es lo mismo que una cadena vacía.reduceoinject?compactes más rápido, pero en realidad escribir el código correctamente al principio elimina la necesidad de tratar con nils por completoIntenta usar
reduceoinject.Estoy de acuerdo con la respuesta aceptada que no debemos
mapycompact, pero no por las mismas razones.Siento en el fondo que
mapesocompactes equivalente aselectentoncesmap. Considere:mapes 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 hacerloselectde antemano, entonces probablemente no quiera unmapen el set. Si tiene que hacerloselectdespués (ocompact), entonces probablemente no quiera unmapen el set. En cualquier caso, está iterando dos veces en todo el conjunto, cuandoreducesolo 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
compactes el mejor enfoque para resolver esta tarea. Sin embargo, podemos lograr el mismo resultado solo con una simple resta:fuente
each_with_objectes probablemente la forma más limpia de ir aquí:En mi opinión,
each_with_objectes mejor queinject/reduceen 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_objectpara recopilar valores, y utilizamosObject#tappara deshacernos de la variable temporal que de otro modo sería necesaria paranilverificar el resultado delprocess_xmé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 entradaxen ese método. Si supongo que va a procesar el valor dexpasándolo un pocourly determinar cuál de losxs realmente se procesa en resultados válidos no nulos, entonces, puede serEnumerabble.group_byuna mejor opción queEnumerable#map.fuente