假设我在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”方法是什么?
答案 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