ruby map iteration collect

ruby - Mapear una matriz modificando solo los elementos que coinciden con cierta condición



iteration collect (8)

En Ruby, ¿cuál es la forma más expresiva de asignar una matriz de tal manera que ciertos elementos se modifiquen y los otros queden intactos ?

Esta es una manera directa de 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 "sin permiso", 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 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 forma agradable de hacerlo en Ruby (o quizás Rails tenga algún método de conveniencia que no haya encontrado todavía)?

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


Como 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 modifique el objeto en lugar de crear un nuevo objeto. Por ejemplo, upcase! en comparación con la upcase .

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


Descubrí que la mejor manera de lograr esto es usando el tap

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


Es un par de líneas largas, pero aquí hay una alternativa para el infierno:

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 colección con ternario parece mejor en mi opinión.


Estoy de acuerdo en que la declaración del mapa es buena como es. Es claro y simple, y sería fácil de mantener para cualquier persona.

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"


Si no necesitas la matriz antigua, prefiero mapear! en este caso porque puedes usar el! Método para representar que está cambiando la matriz en su lugar.

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

Prefiero esto sobre

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


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


Un trazador de líneas:

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

En el código anterior, comienzas con [] "acumulativo". Al enumerar a través de un enumerador (en nuestro caso, la matriz, ["a", "b", "c"]), el elemento acumulativo y "actual" se pasa 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 sin cambios acumulativo cuando el elemento no es "b" y añadir "b". a la matriz acumulativa y lo devuelve cuando es un b.

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

Puede combinar select con map para lograr lo que está buscando:

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

Dentro del bloque de select , especifique las condiciones para que un elemento sea "seleccionado". Esto devolverá una matriz. Puede llamar "mapa" en la matriz resultante para agregarle el signo de exclamación.


old_a.map! { |a| a == "b" ? a + "!" : a }

da

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

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