create - ruby select
En Ruby, ¿hay un método Array que combine ''seleccionar'' y ''mapa''? (13)
Aquí hay un ejemplo. No es lo mismo que su problema, pero puede ser lo que quiera o puede dar una pista sobre su solución:
def example
lines.each do |x|
new_value = do_transform(x)
if new_value == some_thing
return new_value # here jump out example method directly.
else
next # continue next iterate.
end
end
end
Tengo una matriz de Ruby que contiene algunos valores de cadena. Necesito:
- Encuentre todos los elementos que coincidan con algún predicado
- Ejecutar 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?
Creo que de esta manera es más legible, porque divide las condiciones del filtro y el valor mapeado, mientras que queda claro que las acciones están conectadas:
results = @lines.select { |line|
line.should_include?
}.map do |line|
line.value_to_map
end
Y, en su caso específico, elimine la variable de result
todos juntos:
def example
@lines.select { |line|
line.should_include?
}.map { |line|
line.value_to_map
}.uniq.sort
end
Debería intentar usar mi biblioteca Rearmed Ruby en la que he agregado el método Enumerable#select_map
. Aquí hay un ejemplo:
items = [{version: "1.1"}, {version: nil}, {version: false}]
items.select_map{|x| x[:version]} #=> [{version: "1.1"}]
# or without enumerable monkey patch
Rearmed.select_map(items){|x| x[:version]}
No estoy seguro de que haya uno. El módulo Enumerable , que agrega select
y map
, no muestra uno.
select_and_transform
pasar en dos bloques el método select_and_transform
, que sería un poco intuitivo en mi humilde opinión.
Obviamente, podrías encadenarlos, lo cual es más legible:
transformed_list = lines.select{|line| ...}.map{|line| ... }
No, pero puedes hacerlo así:
lines.map { |line| do_some_action if check_some_property }.reject(&:nil?)
O mejor:
lines.inject([]) { |all, line| all << line if check_some_property; all }
Otra forma diferente de abordar esto es usar lo nuevo (en relación con esta pregunta) Enumerator::Lazy
:
def example
@lines.lazy
.select { |line| line.property == requirement }
.map { |line| transforming_method(line) }
.uniq
.sort
end
El método .lazy
devuelve un enumerador perezoso. Llamar .select
o .map
en un enumerador vago devuelve otro enumerador perezoso. Solo cuando llama a .uniq
hace forzar al enumerador y devolver una matriz. Entonces, ¿qué ocurre de manera efectiva? .select
llamadas .select
y .map
se combinan en una: solo itera sobre @lines
una vez para hacer ambas .select
y .map
.
Mi instinto es que el método de reduce
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 posterior. En una situación @lines.select.map
normal, select
devuelve una matriz que luego se modifica mediante un map
, devolviendo de nuevo una matriz. En comparación, la evaluación perezosa solo crea una matriz una vez. Esto es útil cuando su objeto de colección inicial es grande. También te permite trabajar con enumeradores infinitos, por ejemplo random_number_generator.lazy.select(&:odd?).take(10)
.
Puede usar reduce
para esto, que requiere solo una pasada:
[1,1,1,2,3,4].reduce([]) { |a, n| a.push(n*3) if n==1; a }
=> [3, 3, 3]
En otras palabras, inicialice el estado para que sea lo que desea (en nuestro caso, una lista vacía para completar: []
), luego siempre asegúrese 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 hace un bucle sobre la lista con una pasada ( map
+ select
o compact
requiere dos pases).
En tu caso:
def example
results = @lines.reduce([]) do |lines, line|
lines.push( ...(line) ) if ...
lines
end
return results.uniq.sort
end
Respuesta simple:
Si tiene n registros, y desea select
y map
según la condición, entonces
records.map { |record| record.attribute if condition }.compact
Aquí, atributo es lo que quieras del registro y la condición en que puedes poner cualquier control.
compacto es eliminar el nil innecesario que salió de esa condición si
Si no desea crear dos matrices diferentes, ¡puede usar compact!
pero ten cuidado al respecto.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}
new_array.compact!
¡Curiosamente, compact!
hace una eliminación en lugar de nil. El valor de retorno de compact!
es la misma matriz si hubo cambios pero nulos si no hubo nulos.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}.tap { |array| array.compact! }
Sería un trazador de líneas.
Si tiene una select
que puede usar el operador de case
( ===
), grep
es una buena alternativa:
p [1,2,''not_a_number'',3].grep(Integer){|x| -x } #=> [-1, -2, -3]
p [''1'',''2'',''not_a_number'',''3''].grep(//D/, &:upcase) #=> ["NOT_A_NUMBER"]
Si necesitamos una lógica más compleja, podemos crear lambdas:
my_favourite_numbers = [1,4,6]
is_a_favourite_number = -> x { my_favourite_numbers.include? x }
make_awesome = -> x { "***#{x}***" }
my_data = [1,2,3,4]
p my_data.grep(is_a_favourite_number, &make_awesome) #=> ["***1***", "***4***"]
Tu versión:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Mi version:
def example
results = {}
@lines.each{ |line| results[line] = true if ... }
return results.keys.sort
end
Esto hará 1 iteración (excepto el tipo), y tiene la ventaja añadida de mantener la singularidad (si no te importa uniq, simplemente haz resultados como una matriz y results.push(line) if ...
Usualmente uso map
y compact
juntos junto con mis criterios de selección como postfix if
. compact
se deshace de los nils.
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}
=> [3, 3, 3, nil, nil, nil]
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact
=> [3, 3, 3]
def example
@lines.select {|line| ... }.map {|line| ... }.uniq.sort
end
En Ruby 1.9 y 1.8.7, también puede encadenar y ajustar iteradores simplemente sin pasarles un bloque:
enum.select.map {|bla| ... }
Pero no es posible en este caso, ya que los tipos de valores de retorno de bloque de select
y map
no coinciden. Tiene más sentido para algo como esto:
enum.inject.with_index {|(acc, el), idx| ... }
AFAICS, lo mejor que puedes hacer es el primer ejemplo.
Aquí hay un pequeño ejemplo:
%w[a b 1 2 c d].map.select {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["a", "b", "c", "d"]
%w[a b 1 2 c d].select.map {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["A", "B", false, false, "C", "D"]
Pero lo que realmente quieres es ["A", "B", "C", "D"]
.