tutorial - ruby wikipedia
Mapa y eliminar valores nulos en Ruby (7)
@El hombre de hojalata, bonito, no conozco este método. Bueno, definitivamente compacta es la mejor manera, pero también se puede hacer con substracción simple:
[1, nil, 3, nil, nil] - [nil]
=> [1, 3]
Tengo un mapa que cambia un valor o lo pone a cero. Entonces quiero eliminar las entradas nulas de la lista. La lista no necesita ser guardada.
Esto es lo que tengo actualmente:
items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]
items.select! { |x| !x.nil? } # [1, nil, 3, nil, nil] => [1, 3]
Soy consciente de que podría hacer un bucle y recolectar condicionalmente en otra matriz como esta:
new_items = []
items.each do |x|
x = process_x x
new_items.append(x) unless x.nil?
end
items = new_items
Pero no parece ese rubí-esque. ¿Existe una buena forma de ejecutar una función sobre una lista eliminando / excluyendo los nils a medida que avanza?
En tu ejemplo:
items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]
no parece que los valores hayan cambiado más que reemplazados por nil
. Si ese es el caso, entonces:
items.select{|x| process_x url}
Será suficiente.
Intenta usar #reduce
o #inject
!
[1, 2, 3].reduce([]) { |memo, i|
if i % 2 == 0
memo << i
end
memo
}
Estoy de acuerdo con la respuesta aceptada de que no debemos mapear y compactar, ¡pero no por las mismas razones!
Siento que dentro de ese mapa, entonces compacto es equivalente a seleccionar-luego-mapa. Considere: un mapa es una función de uno a uno. Si está mapeando desde algún conjunto de valores y mapea, entonces quiere un valor en el conjunto de salida para cada valor en el conjunto de entrada. Si tiene que seleccionar de antemano, es probable que no desee un mapa en el conjunto. Si tiene que seleccionar más tarde (o compacta), es probable que no desee un mapa en el conjunto. En cualquier caso, está iterando dos veces en todo el conjunto, cuando una reducción solo tiene que ejecutarse una vez.
Además, en inglés, está intentando "reducir un conjunto de enteros a un conjunto de enteros pares".
Podrías usar compact
:
[1, nil, 3, nil, nil].compact
=> [1, 3]
Me gustaría recordarle a la gente que si obtiene una matriz que contiene nils como resultado de un bloque de map
, y ese bloque intenta devolver valores de manera condicional, entonces tiene un olor de código y necesita repensar su lógica.
Por ejemplo, si estás haciendo algo que hace esto:
[1,2,3].map{ |i|
if i % 2 == 0
i
end
}
# => [nil, 2, nil]
Entonces no lo hagas En su lugar, antes del map
, reject
las cosas que no desea o select
lo que desea:
[1,2,3].select{ |i| i % 2 == 0 }.map{ |i|
i
}
# => [2]
Considero usar el compact
para limpiar un desastre como un esfuerzo de última hora 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 están distribuyendo en nuestro programa; Los datos inesperados / desconocidos son malos. Cada vez que veo nils en una matriz en la que estoy trabajando, entiendo 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 tamizar a través de la matriz para eliminar ellos despues
''Just my $%0.2f.'' % [2.to_f/100]
Si quisiera un criterio más flexible para el rechazo, por ejemplo, para rechazar cadenas vacías y nulas, podría usar:
[1, nil, 3, 0, ''''].reject(&:blank?)
=> [1, 3, 0]
Si desea ir más lejos y rechazar valores cero (o aplicar una lógica más compleja al proceso), puede pasar un bloque para rechazar:
[1, nil, 3, 0, ''''].reject do |value| value.blank? || value==0 end
=> [1, 3]
[1, nil, 3, 0, '''', 1000].reject do |value| value.blank? || value==0 || value>10 end
=> [1, 3]
Una forma más de lograrlo será como se muestra a continuación. Aquí, utilizamos Enumerable#each_with_object
para recopilar valores, y hacemos uso de Object#tap
para deshacerse de la variable temporal que, de lo contrario, se necesita para verificar el resultado del método process_x
.
items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}
Ejemplo completo para ilustración:
items = [1,2,3,4,5]
def process x
rand(10) > 5 ? nil : x
end
items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}
Enfoque alternativo:
Al observar el método al que llama process_x url
, no está claro cuál es el propósito de la entrada x
en ese método. Si asumo que va a procesar el valor de x
pasándole un poco de url
y determinar cuál de las x
s realmente se procesa en resultados válidos no nulos, entonces, puede ser Enumerabble.group_by
es una opción mejor que Enumerable#map
.
h = items.group_by {|x| (process x).nil? ? "Bad" : "Good"}
#=> {"Bad"=>[1, 2], "Good"=>[3, 4, 5]}
h["Good"]
#=> [3,4,5]
each_with_object
es probablemente la forma más limpia de ir aquí:
new_items = items.each_with_object([]) do |x, memo|
ret = process_x(x)
memo << ret unless ret.nil?
end
En mi opinión, each_with_object
es mejor que inject
/ reduce
en casos condicionales porque no tiene que preocuparse por el valor de retorno del bloque.