开发者

Can I create an array in Ruby with default values?

Perl is pretty nice about default values:

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

Ruby can do the same, at least for hashes:

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

But the same seemingly does not work for arrays:

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

Is it possible to supply a default value for arrays, so when they are auto-extended, they're filled with 0 instead of nil?

Of course I can work around this, but at a cost to expressiveness:

>> 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.+

Update 1: One of my colleagues pointed out that I can开发者_C百科 use #compact to solve the #inject issue, and #to_i to solve the standard element-at-index issue:

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

Update 2: Thanks to Andrew Grimm for a solution to the += issue:

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

Update 3: this is starting to look like whack-a-mole!

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

But we can deal with that:

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

I now have to admit (sheepishly) that I'll subclass Array to avoid cluttering my code:

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

Thanks for all the creative solutions. TIMTOWTDI indeed!


Not auto extended, but initialized to the specified length with a default value:

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


Given that Ruby returns nil for a non-existing element (as opposed to index-out-of-bounds type error), you could just use an "or":

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

You could take the monkey patch approach, but you probably would not want to do this in anything larger than a 1-file script:

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


The easiest way would be:

new_array = Array.new(size, default_value)

For example:

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


Another approach would be overriding the Array#[] method and return the default value if there is no item

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

and

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


I'll put Johans elegant solution out there: foo.compact.inject(:+)


If you're dealing with integers you can call to_i:

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

UPD

Oh, I didn't read all topic :)

so you can use this:

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

which, actually, not the smartest one


I think an array is the wrong abstraction if you want to auto extend the array. Add another level of abstraction.

Edit (from our discussion): The important thing is that the code to achieve your goal is located in the right place (single responsibility principle), and that place is not your "client code", hence the need for a new class. Extending the existing Array class (through inheritance/mixin) is probably better than encapsulating the wanted behaviour in an entierly new class.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜