1

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?

4
  • possible duplicate of Can I create an array in Ruby with default values? Commented Mar 11, 2014 at 18:34
  • look new(size) {|index| block } Commented Mar 11, 2014 at 18:46
  • @Max, it's not the same problem, although it have a lot in common, the answers there do not provide a solution for my problem. Commented Mar 11, 2014 at 20:03
  • @ArupRakshit This is not what I'm looking for, was new(size) {|index|block} will create an Array of size size, it will not call the block again when I try to access a previously not existent key. Commented Mar 11, 2014 at 20:05

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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

2 Comments

I suggest that the method be named fetch. This would make it consistent with the Hash API. Also, I don't think a reader method should implicitly modify the array in place.
@MatheusMoreira fetch already exists for Array, it's similar to [] but throws an exception when the index is out of bounds. I did not want to change semantics of an existing method. I was just showing you could add an alternative method.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.