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

时间:2011-03-16 11:34:43

标签: ruby arrays

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来解决标准元素 - at-index问题:

>> 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确实!

7 个答案:

答案 0 :(得分:126)

未自动扩展,但使用默认值初始化为指定长度:

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

答案 1 :(得分:16)

鉴于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"

答案 2 :(得分:3)

如果您正在处理整数,可以致电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 }

实际上,这不是最聪明的

答案 3 :(得分:3)

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

答案 4 :(得分:2)

另一种方法是覆盖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

答案 5 :(得分:1)

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

编辑(来自我们的讨论):重要的是,实现目标的代码位于正确的位置(单一责任原则),而且该位置您的&#34;客户代码&#34;,因此需要新的课程。扩展现有的Array类(通过继承/ mixin)可能比将所需行为封装在一个更新的类中更好。

答案 6 :(得分:0)

最简单的方法是:

new_array = Array.new(size, default_value)

例如:

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