Pregunta

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

Esta es una forma 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 " dejar solo " caso, 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 buena forma de hacer esto en Ruby (o quizás Rails tenga algún tipo de método de conveniencia que aún no haya encontrado)?


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

¿Fue útil?

Solución

Estoy de acuerdo en que la declaración del mapa es buena tal 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"

Otros consejos

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úrate de usar un método que modifique el objeto en lugar de crear un nuevo objeto. P.ej. upcase! comparado con upcase .

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

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

da

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

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

La solución

Su mapa 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 varios 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 " ;. A medida que enumera a través de un enumerador (en nuestro caso, la matriz, [" a " ;, " b " ;, " c "]), tanto acumulativa como actual " el elemento 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 agregue "b!" a una matriz acumulativa y devolverla cuando es una b.

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

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

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

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

Si no necesita la matriz anterior, ¡prefiero el mapa! 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) }

Tiene algunas líneas de largo, pero aquí hay una alternativa:

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

He descubierto que la mejor manera de lograr esto es usando tap

arr = [1,2,3,4,5,6]
[].tap do |a|
  arr.each { |x| a << x if x%2==0 }
end
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top