Asigne una matriz modificando solo elementos que cumplan una determinada condición
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!
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.
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