Отобразить массив, модифицирующий только элементы, соответствующие определенному условию - PullRequest
18 голосов
/ 05 марта 2009

В 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 с троичным оператором, некоторые из вас отправили очень интересные ответы!

Ответы [ 8 ]

25 голосов
/ 09 марта 2009

Поскольку массивы являются указателями, это также работает:

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.

9 голосов
/ 06 марта 2009
old_a.map! { |a| a == "b" ? a + "!" : a }

дает

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

map! изменяет получатель на месте, поэтому old_a теперь возвращает массив

6 голосов
/ 05 марта 2009

Я согласен, что утверждение о карте хорошее. Это ясно и просто, и будет легко для тех, кто поддерживает.

Если вы хотите что-то более сложное, как насчет этого?

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"
3 голосов
/ 05 марта 2009

Ваше решение map является лучшим. Я не уверен, почему вы считаете, что map_modifying_only_elements_where лучше. Использование map чище, более кратко и не требует нескольких блоков.

2 голосов
/ 04 марта 2013

Один лайнер:

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

В приведенном выше коде вы начинаете с [] "кумулятивный". При перечислении через Enumerator (в нашем случае массив, ["a", "b", "c"]), накопительный, а также "текущий" элемент передаются в наш блок (| cumulative, i |) и результат выполнения нашего блока присваивается кумулятивному. Что я делаю выше, так это оставляю кумулятивным без изменений, когда элемент не "b" и добавляю "b!" в накопительный массив и вернуть его, когда это b.

Существует ответ выше, который использует select, что является самым простым способом (и помнить) его.

Вы можете комбинировать select с map для достижения того, что вы ищете:

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

Внутри блока select вы указываете условия для элемента, который будет "выбран". Это вернет массив. Вы можете вызвать «map» в результирующем массиве, чтобы добавить к нему восклицательный знак.

1 голос
/ 08 декабря 2011

Я обнаружил, что лучший способ сделать это - использовать tap

arr = [1,2,3,4,5,6]
[].tap do |a|
  arr.each { |x| a << x if x%2==0 }
end
1 голос
/ 06 марта 2009

Это несколько строк, но вот альтернатива этому черту:

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"]

На мой взгляд, сбор с троичным кажется лучшим.

1 голос
/ 06 марта 2009

Если вам не нужен старый массив, я предпочитаю map! в этом случае, потому что вы можете использовать! метод для представления вы меняете массив на месте.

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

Я предпочитаю это:

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