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.