The Array.new method can receive a block. It passes the index of the element and the result of the block is stored in the array.
Array.new(3) { |index| index ** 2 }
# => [0, 1, 4]
However, all the elements will be created the moment you call the method. They will also be stored in the block and there is no way to prevent that.
We can subclass Array and implement the desired Hash-like behavior.
class CustomArray < Array
def [](index)
if super.nil? then @default_proc.call self, index end
super
end
end
class << CustomArray
def new(*arguments, **keyword_arguments, &block)
if arguments.empty? and block
super().tap do |array|
array.instance_variable_set :@default_proc, block
end
else super end
end
end
This way, the usual Array API is preserved. If a size parameter isn't passed with the block, it will be used as the default Proc.
array = CustomArray.new { |array, index| array[index] = index + 2 }
p array[10]
# => 12
p array
# => [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 12]
See it run here.
Note that this implementation is problematic due to the meaning of nil. In this case, it is defined to be the empty value due to how arrays work, which is reasonable in the context of contiguous memory. If you store an element at an index that is bigger than the size of the array, it will fill in the blanks with nil in order to indicate empty space between the last element and the element you just inserted.
If nil is a meaningful value, consider using a Hash with integer keys instead of an Array.
new(size) {|index|block}will create an Array of sizesize, it will not call the block again when I try to access a previously not existent key.