2009-03-05 16 views
17

En Ruby, ¿cuál es la forma más expresiva de mapear una matriz de tal manera que se modifiquen ciertos elementos y los demás no se hayan modificado?Asignar una matriz que modifique solo los elementos que coincidan con una determinada condición

Esta es una forma recta hacia adelante para hacerlo:

old_a = ["a", "b", "c"]       # ["a", "b", "c"] 
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) } # ["a", "b!", "c"] 

Omitiendo el caso de "dejar solos" por supuesto, si no es suficiente:

new_a = old_a.map { |x| x+"!" if x=="b" }  # [nil, "b!", nil] 

Lo que me gustaría es algo así como esto:

new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"}) 
     do |y| 
      y + "!" 
     end 
# ["a", "b!", "c"] 

¿hay alguna buena manera de hacer esto en Rubí (o tal vez Carriles tiene algún tipo de método de conveniencia que no he encontrado todavía)?


Gracias a todos por responder. Mientras que colectivamente me convencieron de que es mejor usar map con el operador ternario, ¡algunos de ustedes publicaron respuestas muy interesantes!

+0

Creo que el elemento #map está bien tal como está. ;-) –

+0

Sí, estoy de acuerdo. ¡Puedes eliminar los parens si eso te gusta más! –

+0

Cerrado? FTL. Mira mi publicación = P –

Respuesta

6

Acepto que la declaración del mapa es buena como es. Es claro y simple, y sería fácil para que cualquiera pueda mantener.

Si quieres algo más complejo, ¿qué tal esto?

module Enumerable 
    def enum_filter(&filter) 
    FilteredEnumerator.new(self, &filter) 
    end 
    alias :on :enum_filter 
    class FilteredEnumerator 
    include Enumerable 
    def initialize(enum, &filter) 
     @enum, @filter = enum, filter 
     if enum.respond_to?(:map!) 
     def self.map! 
      @enum.map! { |elt| @filter[elt] ? yield(elt) : elt } 
     end 
     end 
    end 
    def each 
     @enum.each { |elt| yield(elt) if @filter[elt] } 
    end 
    def each_with_index 
     @enum.each_with_index { |elt,index| yield(elt, index) if @filter[elt] } 
    end 
    def map 
     @enum.map { |elt| @filter[elt] ? yield(elt) : elt } 
    end 
    alias :and :enum_filter 
    def or 
     FilteredEnumerator.new(@enum) { |elt| @filter[elt] || yield(elt) } 
    end 
    end 
end 

%w{ a b c }.on { |x| x == 'b' }.map { |x| x + "!" } #=> [ 'a', 'b!', 'c' ] 

require 'set' 
Set.new(%w{ He likes dogs}).on { |x| x.length % 2 == 0 }.map! { |x| x.reverse } #=> #<Set: {"likes", "eH", "sgod"}> 

('a'..'z').on { |x| x[0] % 6 == 0 }.or { |x| 'aeiouy'[x] }.to_a.join #=> "aefiloruxy" 
+16

Usted He convertido una línea en treinta y algo. Me gusta tu estilo. – Pesto

+0

I * did * dijimos que pegar el condicional w/en el mapa era mejor :) – rampion

3

Su solución map es la mejor. No estoy seguro de por qué crees que map_modifying_only_elements_where es de alguna manera mejor. El uso de map es más limpio, más conciso y no requiere múltiples bloques.

1

¡Si no necesita la matriz anterior, prefiero el mapa! en este caso porque puedes usar el! método para representarlo está cambiando la matriz en su lugar.

self.answers.map!{ |x| (x=="b" ? x+"!" : x) } 

prefiero este sobre: ​​

new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) } 
7
old_a.map! { |a| a == "b" ? a + "!" : a } 

da

=> ["a", "b!", "c"] 

map! modifica el receptor en su lugar, por lo que es ahora que old_a matriz devuelta.

1

Está a unas pocas líneas de largo, pero aquí es una alternativa para el placer de hacerlo:

oa = %w| a b c | 
na = oa.partition { |a| a == 'b' } 
na.first.collect! { |a| a+'!' } 
na.flatten! #Add .sort! here if you wish 
p na 
# >> ["b!", "a", "c"] 

la colecta con ternaria parece mejor en mi opinión.

23

Debido a que las matrices son punteros, esto también funciona:

a = ["hello", "to", "you", "dude"] 
a.select {|i| i.length <= 3 }.each {|i| i << "!" } 

puts a.inspect 
# => ["hello", "to!", "you!", "dude"] 

En el bucle, asegúrese de usar un método que altera el objeto en lugar de crear un nuevo objeto. P.ej. upcase! en comparación con upcase.

El procedimiento exacto depende de qué es exactamente lo que está tratando de lograr. Es difícil encontrar una respuesta definitiva con ejemplos de foo-bar.

1

que he encontrado que la mejor manera de lograr esto es mediante el uso de tap

arr = [1,2,3,4,5,6] 
[].tap do |a| 
    arr.each { |x| a << x if x%2==0 } 
end 
2

Un forro:

["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative } 

En el código anterior, se empieza con [] "acumulativo". Al enumerar a través de un Enumerador (en nuestro caso, la matriz, ["a", "b", "c"]), el elemento acumulativo y el elemento "actual" pasan a nuestro bloque (| acumulativo, i |) y el resultado de la ejecución de nuestro bloque se asigna a acumulativo. Lo que hago arriba es mantener el cumulativo sin cambios cuando el ítem no es "b" y anexar "b!" al conjunto acumulativo y devolverlo cuando es un b.

Hay una respuesta más arriba que usa select, que es la forma más fácil de hacerlo (y recordar).

Se pueden combinar con selectmap con el fin de lograr lo que está buscando:

arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" } 
=> ["b!"] 

Dentro del bloque select, se especifican las condiciones para un elemento a ser "seleccionados". Esto devolverá una matriz. Puede llamar "mapa" en la matriz resultante para agregarle el signo de admiración.

Cuestiones relacionadas