Ruby Method Array#≪≪ Not Updating the Array in Hash

Ruby method Array# not updating the array in hash

If you create a Hash using the block form of Hash.new, the block gets executed every time you try to access an element which doesn't actually exist. So, let's just look at what happens:

h = Hash.new { [] }
h[0] << 'a'

The first thing that gets evaluated here, is the expression

h[0]

What happens when it gets evaluated? Well, the block gets run:

[]

That's not very exciting: the block simply creates an empty array and returns it. It doesn't do anything else. In particular, it doesn't change h in any way: h is still empty.

Next, the message << with one argument 'a' gets sent to the result of h[0] which is the result of the block, which is simply an empty array:

[] << 'a'

What does this do? It adds the element 'a' to an empty array, but since the array doesn't actually get assigned to any variable, it is immediately garbage collected and goes away.

Now, if you evaluate h[0] again:

h[0] # => []

h is still empty, since nothing ever got assigned to it, therefore the key 0 is still non-existent, which means the block gets run again, which means it again returns an empty array (but note that it is a completely new, different empty array now).

h[0] += ['a']

What happens here? First, the operator assign gets desugared to

h[0] = h[0] + ['a']

Now, the h[0] on the right side gets evaluated. And what does it return? We already went over this: h[0] doesn't exist, therefore the block gets run, the block returns an empty array. Again, this is a completely new, third empty array now. This empty array gets sent the message + with the argument ['a'], which causes it to return yet another new array which is the array ['a']. This array then gets assigned to h[0].

Lastly, at this point:

h[0] # => ['a']

Now you have finally actually put something into h[0] so, obviously, you get out what you put in.

So, to answer the question you probably had, why don't you get out what you put in? You didn't put anything in in the first place!

If you actually want to assign to the hash inside the block, you have to, well assign to the hash inside the block:

h = Hash.new {|this_hash, nonexistent_key| this_hash[nonexistent_key] = [] }
h[0] << 'a'
h[0] # => ['a']

It's actually fairly easy to see what is going on in your code example, if you look at the identities of the objects involved. Then you can see that everytime you call h[0], you get a different array.

Efficient way to update values to array of hashes in ruby?

You could just use the same object:

item_1 = {'id' => 1, 'cost' => '2.00'}
item_2 = {'id' => 2, 'cost' => '6.00'}

items = [item_1, item_2, item_1, item_1, item_1]
#=> [{"id"=>1, "cost"=>"2.00"}, {"id"=>2, "cost"=>"6.00"},
# {"id"=>1, "cost"=>"2.00"}, {"id"=>1, "cost"=>"2.00"},
# {"id"=>1, "cost"=>"2.00"}]

This makes updates trivial:

item_1['cost'] = '8.00'

items
#=> [{"id"=>1, "cost"=>"8.00"}, {"id"=>2, "cost"=>"6.00"},
# {"id"=>1, "cost"=>"8.00"}, {"id"=>1, "cost"=>"8.00"},
# {"id"=>1, "cost"=>"8.00"}]

Pushing Hash onto Array: last Hash overwriting previous array elements

You should try something like

arr = ['bob', 'jack', 'smith']
array_of_hashes = Array.new

arr.each { |item|
hash = Hash.new
hash[:name] = item
array_of_hashes << hash
}

puts array_of_hashes

with a new instance being pushed into the array for every new item.

Updating Ruby Hash Values with Array Values

I would just do:

h.keys.zip(@data).to_h

If the only purpose of h is as an interim step getting to the result, you can dispense with it and do:

columns.zip(@data).to_h

Array to Hash Ruby

a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

That's it. The * is called the splat operator.

One caveat per @Mike Lewis (in the comments): "Be very careful with this. Ruby expands splats on the stack. If you do this with a large dataset, expect to blow out your stack."

So, for most general use cases this method is great, but use a different method if you want to do the conversion on lots of data. For example, @Łukasz Niemier (also in the comments) offers this method for large data sets:

h = Hash[a.each_slice(2).to_a]

Pushing to an array not working as expected

It seems there are some strange behavior in row objects wich seems to be some kind of singleton, and that's why dup method wont solve it.

Jumping into the source code it seems that the to_a method will duplicate the inner row elements and that's why it works so the answer is to use to_a on the row object or if you want you can also transform it into a Hash to preserve meta.

while row=sth.fetch do
tasks.push(row.to_a)
end

But I recommend the more ruby way

sth.fetch do |row|
tasks << row.to_a
end

pushing elements onto an array in a ruby Hash

Sometimes the hash is initially filled with data and later on it is only used to retrieve data. In those cases I prefer the first possibility, because the default proc can be "emptied" (in Ruby 1.9).

ht = Hash.new {|h,k| h[k]=[]}
ht["cats"] << "Jellicle"
ht["cats"] << "Mr. Mistoffelees"
ht["dogs"]
p ht
#=> {"cats"=>["Jellicle", "Mr. Mistoffelees"], "dogs"=>[]}

ht.default_proc = proc{}
ht["parrots"] #nil
p ht
#=> {"cats"=>["Jellicle", "Mr. Mistoffelees"], "dogs"=>[]} No parrots!

Add element to an array if it's not there already

You can use Set instead of Array.

Pushing hash into array

The reason this is happening, at least according to your code is because you are using the same output hash for each row. If you ran

puts entry_array.collect(&:object_id)

at the end of your CSV file, you would see they are all the same object. So, even though you put it into the array at the end of each row, you are still modifying the same object, which the array is now pointing to. Essentially what you're doing is

a = { hello: 'world' } # => {:hello=>"world"}
b = a # => {:hello=>"world"}
b[:hello] = 'there'
a # => {:hello=>"there"}
b # => {:hello=>"there"}

# storing it in an array does the same thing
output = { hello: 'world' } # => {:hello=>"world"}
array = [output] # => [{:hello=>"world"}]
output[:hello] = 'there'
output # => {:hello=>"there"}
array # => [{:hello=>"there"}]

What you need to do to fix this is instantiate a new hash for each row:

attributes = [:user_id, :project_id, :task_id, :comment]
entry_array = []

CSV.foreach(csv_file, headers: true, converters: :date).with_index do |row, line_no|
output = { } # Instantiate here, inside the loop, so each row gets its own hash
entry_hash = row.to_hash

# if you need the key for something, use this
# entry_hash.each.with_index do |(key, value), index|
# otherwise, just iterate over each value
entry_hash.each_value.with_index do |value, index|
output[attributes[index]] = value.is_a?(Array) ? value.first.to_i : value
end

entry_array << output
end

I've changed your class check to an is_a? and also removed the i counter in favor of using with_index over the iteration, you weren't using the key in the sample shown, so I just used each_value but have left a comment showing how to use each_index with each on a hash, if you were using the key just not shown.



Related Topics



Leave a reply



Submit