2011-03-01 27 views
51
(1..4).collect do |x| 
    next if x == 3 
    x + 1 
end # => [2, 3, nil, 5] 
    # desired => [2, 3, 5] 

Si se cumple la condición para next, collect pone nil en la matriz, mientras que lo que estoy tratando de hacer es poner ningún elemento en la matriz devuelta si la condición es reunió. ¿Es esto posible sin llamar al delete_if { |x| x == nil } en la matriz devuelta?Saltar sobre iteración en enumerables # recoger

(Uso de Ruby 1.8.7; mi código extracto es fuertemente abstraído)

Respuesta

74

hay un método Enumerable#reject que sirve sólo la finalidad:

(1..4).reject{|x| x == 3}.collect{|x| x + 1} 

La práctica de utilizar directamente una salida de un método como una entrada de otro se llama método de encadenamiento y es muy común en Ruby.

BTW, map (o collect) se utiliza para el mapeo directo de la entrada enumerable a la de salida. Si necesita generar una cantidad diferente de elementos, es probable que necesite otro método de Enumerable.

Editar: Si te molesta por el hecho de que algunos de los elementos se itera dos veces, usted puede utilizar la solución menos elegante basado en inject (o su método similar llamado each_with_object):

(1..4).each_with_object([]){|x,a| a << x + 1 unless x == 3} 
+3

Hermoso código. Esta es una solución mucho mejor que la mía. – Benson

+3

Esta es la respuesta más lenta (aunque no por mucho), pero sin duda es la más limpia. Solo desearía que hubiera una manera de hacer esto con 'collect', ya que hubiera esperado llamar a' next' para que me devolviera _absolutamente nada_, en lugar de "nothing" (aka 'nil'). –

+1

Si es más lento que cualquier otro enfoque, el optimizador de Ruby podría usar algo de trabajo ¿no crees? – Shannon

44

Me gustaría simplemente llamar .compact en la matriz resultante, que elimina cualquier instancia de nil en una matriz. Si desea que modificar la matriz existente (no hay razón para no), utilice .compact!:

(1..4).collect do |x| 
    next if x == 3 
    x 
end.compact! 
+13

hice una referencia rápida, de interés, de las cuatro soluciones propuestas hasta ahora: recopilar + compacta, recopilar + !, compactos rechazan + recopilar y construyendo una matriz de resultados como y ou go. En MRI 1.9.1, al menos, collect + compact! es el ganador de velocidad por un margen estrecho, con collect + compact y reject + collect muy cerca detrás. Construir el conjunto de resultados es aproximadamente el doble de lento que esos. –

+0

@glenn: ¿Le importaría incluir lo siguiente en su punto de referencia: '(1..4) .inject ([]) {| a, x | x == 3? a: a.push (x + 1)} '? Gracias. –

+0

Claro. Esa es la más lenta hasta ahora, aunque solo un poco más lenta que la creación de la matriz de resultados. También agregué una versión ajustada de esta manera: 'a.inject ([]) {| aa, x | aa << x + 1 a menos que x == 3; aa} ', que fue más rápido que la construcción de la matriz, pero aún considerablemente más lento que las tres 3 formas rápidas. –

0

yo sugeriría a utilizar:

(1..4).to_a.delete_if {|x| x == 3} 

lugar de la siguiente instrucción a cobro revertido +.

+0

El código _real_ es más complicado que el código resumido en mi pregunta, por lo que no conseguiría lo que estoy buscando, desafortunadamente. (El recopilador realmente manipula el valor, en lugar de simplemente devolverlo) –

+0

bien, ¡así que el compacto sugerido! la solución es la indicada para ti. – ALoR

4

sólo una sugerencia, ¿por qué no lo haces de esta manera:

result = [] 
(1..4).each do |x| 
    next if x == 3 
    result << x 
end 
result # => [1, 2, 4] 

de esa manera guardó otra iteración para eliminar nula elementos de la matriz. espero que ayude =)

+0

Esto es como una versión mucho más detallada del ejemplo 'reject' de Mladen. – Benson

+0

en realidad, no, "rechazar" itera a través de la matriz una vez, y "recolectar" itera a través de la matriz una vez más, por lo que hay dos iteraciones. en el caso de "cada" solo lo hace una vez =) – Staelen

+4

Esta es una idea razonable, pero en Ruby a menudo es muy interesante hacer la prueba de velocidad y ver qué sucede en realidad. En mi punto de referencia rápido (que podría o no coincidir con el caso real de Andrew, por supuesto), construir el conjunto de esta manera es en realidad el doble de lento que cualquiera de las otras formas. Sospecho que el problema es que iterar a través de la matriz en C es en realidad mucho más rápido que agregar elementos en el nivel Ruby <<. –

0

Se podría tirar la toma de decisiones en un método de ayuda, y utilizarlo a través de Enumerable#reduce:

def potentially_keep(list, i) 
    if i === 3 
    list 
    else 
    list.push i 
    end 
end 
# => :potentially_keep 

(1..4).reduce([]) { |memo, i| potentially_keep(memo, i) } 
# => [1, 2, 4]