Reputation: 2155
In Ruby with Hashes I can do this:
h = Hash.new { |h,k| h[k] = "created #{k}" }
so that every time I try to access an item with a key that does not exists in the Hash it will call the block and create this new item and store with the key.
Is there a similar way to do this with Arrays?
Upvotes: 1
Views: 138
Reputation: 17020
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
.
Upvotes: 2
Reputation: 3055
By default, no I do not think that is possible. If you are willing to do a little monkey-patching, you can add a similar method yourself. For example, by extending Array
with a get
method which accepts a Block, you can simulate what you want.
The get
method will act like the regular []
when no Block is given. When you pass a Block and the value is nil
, it will store whatever results from the Block at index i
.
class Array
def get(i, &block)
return self[i] if !block || !self[i].nil?
self[i] = block.call(i)
end
end
array = [1, 2]
array.get(0) # => 1
array.get(5) # => nil
array.get(5) { |i| "Created index #{i}" } # => "Created index 5"
p array # => [1, 2, nil, nil, nil, "Created index 5"]
Upvotes: 1