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.
精彩评论