我可以使用默认值在Ruby中创建数组吗?

Perl对默认值非常好:

: jmglov@laurana; perl -e '@foo; printf "%d\n", $foo[123]' 0 : jmglov@laurana; perl -e '%foo; printf "%d\n", $foo{bar}' 0 

Ruby也可以这样做,至少对于哈希来说是这样的:

 >> foo = Hash.new(0) => {} >> foo[:bar] => 0 

但同样看起来对数组不起作用:

 >> foo = Array.new(0) => [] >> foo[123] => nil >> foo[124] = 0 => 0 >> foo[456] = 0 => 0 >> foo[455,456] => [nil, 0] 

是否可以为数组提供默认值,因此当它们自动扩展时,它们会被0填充而不是nil?

当然,我可以解决这个问题,但要付出代价:

 >> foo[457,458] = 890, 321 => [890, 321] >> foo[456] += 789 NoMethodError: You have a nil object when you didn't expect it! You might have expected an instance of Array. The error occurred while evaluating nil.+ >> foo.inject(0) {|sum, i| sum += (i || 0) } => 1211 >> foo.inject(:+) NoMethodError: You have a nil object when you didn't expect it! You might have expected an instance of Array. The error occurred while evaluating nil.+ 

更新1:我的一位同事指出我可以使用#compact来解决#inject问题,并使用#to_i解决标准的索引元素问题:

 >> foo.include? nil => true >> foo.compact.inject(:+) => 1211 >> foo[456,457] => [0, 890, 321] >> foo[455..457] => [nil, 0, 890] >> foo[455..457].map(&:to_i) => [0, 0, 890] 

更新2:感谢Andrew Grimm解决+=问题:

 >> foo = [] => [] >> def foo.[](i) >> fetch(i) {0} >> end => nil >> foo[4] => 0 >> foo => [] >> foo[4] += 123 => 123 >> foo => [nil, nil, nil, nil, 123] 

更新3:这开始看起来像打鼹鼠!

 >> foo => [nil, nil, nil, nil, 123] >> foo[-2..-1] TypeError: can't convert Range into Integer 

但我们可以解决这个问题:

 >> def foo.[](index) >> if index.is_a? Range >> index.map {|i| self[i] } >> else ?> fetch(index) { 0 } # default to 0 if no element at index; will not cause auto-extension of array >> end >> end => nil >> foo => [nil, nil, nil, nil, 123] >> foo[-2..-1] => [nil, 123] 

我现在必须(羞怯地)承认我将子类化Array以避免混乱我的代码:

 class MyClass class ArrayWithDefault < Array def [](index) if index.is_a? Range index.map {|i| self[i] } else fetch(index) { 0 } # default to 0 if no element at index; will not cause auto-extension of array end end end end 

感谢所有创意解决方案。 TIMTOWTDI确实!

鉴于Ruby为不存在的元素返回nil (而不是索引越界类型错误),您可以使用“或”:

 a = [1,2,3] puts a[5] # => nil puts a[5] || "a default" # => a default 

您可以采用猴子补丁方法,但您可能不希望在大于1个文件的脚本中执行此操作:

 a = [1,2,3] def a.[](index) self.at(index) || "a default" end puts a[5] # => "a default" 

不是自动扩展,而是使用默认值初始化为指定长度:

 >> Array.new(123, 0) => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 

我会把Johans优雅的解决方案放在那里: foo.compact.inject(:+)

如果你正在处理整数,你可以调用to_i

 foo = [] foo[100] #=> nil foo[100].to_i #=> 0 foo[100] = 3 foo[100] #=> 3 

UPD

哦,我没有读完所有主题:)

所以你可以用这个:

 foo.inject{|a,b| a.to_i + b.to_i } 

实际上,这不是最聪明的

另一种方法是覆盖Array#[]方法,如果没有项目则返回默认值

 class Array def [](index) self.at(index) ? self.at(index) : 0 end end 

 arr = [1,2,3] puts arr[0] # print 1 puts arr[5] # print 0 

我认为如果你想自动扩展数组,数组是错误的抽象。 添加另一个抽象级别。

编辑(从我们的讨论中):重要的是,实现目标的代码位于正确的位置(单一责任原则),并且该位置不是您的“客户端代码”,因此需要新的类。 扩展现有的Array类(通过inheritance/ mixin)可能比将所需行为封装在一个更新的类中更好。

最简单的方法是:

 new_array = Array.new(size, default_value) 

例如:

 new_array = Array.new(5,"foo")