Сопоставьте массив, изменяющий только элементы, соответствующие определенному условию
В Ruby наиболее выразительный способ сопоставить массив таким образом, чтобы некоторые элементы были изменены, а остальные остались нетронутыми?
Это прямой способ сделать это:
old_a = ["a", "b", "c"] # ["a", "b", "c"]
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) } # ["a", "b!", "c"]
Опуская "автономный" случай, конечно, если этого недостаточно:
new_a = old_a.map { |x| x+"!" if x=="b" } # [nil, "b!", nil]
Что бы я хотел, это примерно так:
new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"})
do |y|
y + "!"
end
# ["a", "b!", "c"]
Есть ли какой-нибудь хороший способ сделать это в Ruby (или, может быть, у Rails есть какой-то метод удобства, который я еще не нашел)?
Спасибо всем за ответ. В то время как вы коллективно убедили меня, что лучше всего использовать map
с тройным оператором, некоторые из вас отправили очень интересные ответы!
Ответы
Ответ 1
Я согласен, что оператор карты хорош, как есть. Это ясно и просто, и было бы легко
для тех, кто поддерживает.
Если вам нужно что-то более сложное, как насчет этого?
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"
Ответ 2
Поскольку массивы являются указателями, это также работает:
a = ["hello", "to", "you", "dude"]
a.select {|i| i.length <= 3 }.each {|i| i << "!" }
puts a.inspect
# => ["hello", "to!", "you!", "dude"]
В цикле убедитесь, что вы используете метод, который изменяет объект, а не создает новый объект. Например. upcase!
по сравнению с upcase
.
Точная процедура зависит от того, чего именно вы пытаетесь достичь. Трудно сформулировать определенный ответ с примерами foo-bar.
Ответ 3
old_a.map! { |a| a == "b" ? a + "!" : a }
дает
=> ["a", "b!", "c"]
map!
изменяет приемник на месте, поэтому old_a
теперь является возвращенным массивом.
Ответ 4
Ваше решение map
является лучшим. Я не уверен, почему вы думаете, что map_modification_only_elements_where как-то лучше. Использование map
более чистое, более сжатое и не требует нескольких блоков.
Ответ 5
Один вкладыш:
["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative }
В приведенном выше коде вы начинаете с [] "кумулятивный". Когда вы перечисляете через Enumerator (в нашем случае массив, [ "a", "b", "c" ]), кумулятивный, а также "текущий" элемент передаются нашему блоку (| кумулятивный, я |) и результат выполнения нашего блока присваивается кумулятивному. То, что я делаю выше, остается кумулятивным без изменений, когда элемент не является "b" и добавляет "b!". к кумулятивному массиву и вернуть его, когда он равен b.
Есть ответ выше, который использует select
, что является самым простым способом сделать (и запомнить) его.
Вы можете комбинировать select
с map
, чтобы добиться того, что вы ищете:
arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" }
=> ["b!"]
Внутри блока select
указываются условия для выбора элемента. Это вернет массив. Вы можете вызвать "map" в результирующем массиве, чтобы добавить к нему восклицательный знак.
Ответ 6
Если вам не нужен старый массив, я предпочитаю карту! в этом случае, потому что вы можете использовать! метод для представления вы меняете массив на месте.
self.answers.map!{ |x| (x=="b" ? x+"!" : x) }
Я предпочитаю это:
new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }
Ответ 7
Это несколько строк, но здесь альтернатива для этого:
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"]
Сбор с тройной кажется лучше всего на мой взгляд.
Ответ 8
Я нашел, что лучший способ добиться этого - использовать tap
arr = [1,2,3,4,5,6]
[].tap do |a|
arr.each { |x| a << x if x%2==0 }
end