使用默认对象填充ruby数组

时间:2016-08-02 15:34:29

标签: ruby

假设我在ruby中有一个类计数器,定义为

class Counter
    attr_accessor :starting_value
    def initialize(starting_value)
        @starting_value = starting_value
    end

    def tick
        @starting_value = @starting_value + 1
    end
end

我想使用默认参数用该对象填充数组,如下所示: counter_arr = Array.new(5, Counter.new(0))

这几乎是我想要的,除了我现在有一个数组包含相同的计数器实例5次,而不是一个包含5个新计数器的数组。我运行代码时的IE

counter_arr = Array.new(5, Counter.new(0)) 
counter_arr[0].tick
counter_arr.each do |c|
    puts(c.starting_value)
end

我输出

1
1
1
1
1

而不是

1
0
0
0
0

我想知道,使用多个新对象实例初始化数组的“ruby-esque”方法是什么?

3 个答案:

答案 0 :(得分:4)

人们在学习Ruby时遇到的第一个主要绊脚石之一,如果他们不熟悉一种普遍使用对象崇敬的语言,那么这些是如何工作的。

数组是对零个或多个其他对象的引用的集合。这些对象不一定是唯一的,在某些情况下它们都是相同的。你在这里创建这样一个对象:

counters = Array.new(5, Counter.new(0))

这会创建一个单数Counter对象,并用它填充数组的所有5个插槽。这是因为方法的参数在调用方法之前被评估一次。你可以测试一下:

counters.map(&:object_id)

返回数组中每个对象的唯一对象ID。它们是随机值,每个过程都不​​同,但它们是相同的。

解决此问题的方法是使用块初始值设定项:

counters = Array.new(5) do
  Counter.new(0)
end

它不会插入相同的对象,而是每次都会评估该块的结果,并且由于初始化了一个新的Counter对象,因此这些对象将是唯一的。

整理此方法的一种方法是调整Counter对象以获得合理的默认值:

class Counter
  def initialize(initial = nil)
    @value = initial.to_i
  end

  def tick
    @value += 0
  end
end

这具有接受任意值的优点,即使那些不一定是正确类型的值也是如此。现在Counter.new('2')将自动转换该值。这是Duck Typing的基本原则。如果它可以给你一个数字,它就像一个数字一样好。

答案 1 :(得分:2)

尝试

counter_arr = Array.new(5) { Counter.new(0) } 

答案 2 :(得分:1)

counter_arr = ([-> { Counter.new(0) }] * 5).map &:call