Alexander Gorg
Alexander Gorg

Reputation: 1078

Array is being modified during enumeration

My situation is quite sophisticated, that's why I replaced original data with simple numbers. So, please, don't pay attention to very simple data and 'idiotic' if conditions. It's just an example. Also, please, ignore typos if there's any - original code has no typos.

I have array of hashes with elements like my_hsh = {"numbers" => [1, 2, 3, 4], "n_count" => 4}

What I need to do:

  1. Loop through the parent array and select appropriate hashes,
  2. add each such hash to the array my_arr_nochange,
  3. loop through "numbers" in each such hash and add'em to the_hash["numbers"],
  4. add hashes without these numbers to my_arr_updt.

So the code:

the_hash = {"numbers" => []}
my_arr_updt = []
my_arr_nochange = []
array_of_hashes.each do |my_hsh|
  if my_hsh["n_count"] == 4
    my_arr_nochange << my_hsh
    updated_hsh = my_hsh
    my_hsh["numbers"].each do |num|
      if num == 2
        the_hash["numbers"] += [ num ]
        updated_hsh["numbers"] -= [ num ]
      end
    end
    my_arr_updt << updated_hsh
  end
end

return the_hash, my_arr_updt, my_arr_nochange

The problem is my_arr_nochange is being modified, so instead of getting the old state of my_hsh inside I get the new one. Like:

my_arr_updt
=> [{"numbers" => [1, 3, 4], "n_count" => 4}]
my_arr_nochange 
=> [{"numbers" => [1, 3, 4], "n_count" => 4}]

Tried splitting in different methods and using sub-variables. No result.

P.S.: If you could help with more appropriate title, I would appreciate that too.

Upvotes: 1

Views: 69

Answers (1)

max pleaner
max pleaner

Reputation: 26778

I believe your issue is updated_hsh = my_hsh.

This does not duplicate the hash. Any changes to updated_hsh will change my_hsh and vice versa.

Using Object#clone or Object#dup is a step in the right direction, but won't actually duplicate inner objects (the "numbers" arrays):

h1 = [{numbers: [1,2,3], n_count: 3}]
h2 = h1.map(&:clone)
h2[0][:numbers] << 4
h2[0][:n_count] += 1

h1
# => [{numbers: [1,2,3,4], n_count: 3}]

You can see that n_count was not altered in the original, but numbers was.

To get around this, you can use Hash#deep_dup. This method isn't available in Ruby core. It is part of Active Support which is required by Rails, and can easily be loaded to a plain Ruby program as well, if you require the gem.

require 'active_support/all'

h1 = [{numbers: [1,2,3], n_count: 3}]
h2 = h1.map(&:deep_dup)
h2[0][:numbers] << 4
h2[0][:n_count] += 1

h1
# => [{numbers: [1,2,3], n_count: 3}]

You can also implement deep_dup yourself, if you don't want to use active support. See How to create a deep copy of an object in Ruby?

Another alternative is to manually build the inner hashes, like so:

h1 = [{numbers: [1,2,3], n_count: 3}]
h2 = h1.map do |hsh|
  {
    numbers: hsh[:numbers].clone,
    n_count: hsh[:n_count]
  }
end

Although deep_dup is more concise

Upvotes: 6

Related Questions