19

在 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与三元运算符一起使用,但你们中的一些人发布了非常有趣的答案!

4

9 回答 9

25

因为数组是指针,所以这也有效:

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 的例子很难确定一个明确的答案。

于 2009-03-09T10:35:15.537 回答
9
old_a.map! { |a| a == "b" ? a + "!" : a }

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

map!就地修改接收器,old_a现在返回的数组也是如此。

于 2009-03-05T21:54:12.707 回答
6

我同意地图声明是好的。它清晰简单,任何人都可以轻松维护。

如果你想要更复杂的东西,这个怎么样?

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"
于 2009-03-05T14:59:43.547 回答
4

你的map解决方案是最好的。我不确定您为什么认为 map_modifying_only_elements_where 更好。使用map更干净,更简洁,并且不需要多个块。

于 2009-03-05T13:50:27.877 回答
3

一个班轮:

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

在上面的代码中,您从 [] “累积”开始。当您通过枚举器(在我们的例子中是数组,[“a”,“b”,“c”])进行枚举时,累积以及“当前”项被传递到我们的块(|累积,i|)和我们的块执行的结果被分配给累积。我上面所做的是在项目不是“b”并附加“b!”时保持累积不变。到累积数组并在它是 b 时返回它。

上面有一个使用 的答案select,这是最简单的方法(并记住)。

您可以结合select使用map以实现您正在寻找的内容:

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

select块内,您指定要“选择”的元素的条件。这将返回一个数组。您可以在结果数组上调用“map”以将感叹号附加到它。

于 2013-03-04T10:18:15.263 回答
2

红宝石 2.7+

从 2.7 开始,有一个明确的答案。

Ruby 2.7 正是filter_map为此目的而引入的。它是惯用的和高性能的,我希望它很快就会成为常态。

例如:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]

这是一个很好的阅读主题

希望这对某人有用!

于 2019-08-02T09:38:33.480 回答
1

如果您不需要旧数组,我更喜欢地图!在这种情况下,因为您可以使用 ! 表示您正在更改数组的方法。

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

我更喜欢这个:

new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }
于 2009-03-05T21:10:58.117 回答
1

它有几行长,但这里有一个替代方案:

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

在我看来,三元的收集似乎是最好的。

于 2009-03-05T22:20:11.430 回答
1

我发现实现这一目标的最佳方法是使用tap

arr = [1,2,3,4,5,6]
[].tap do |a|
  arr.each { |x| a << x if x%2==0 }
end
于 2011-12-07T22:05:54.810 回答