Ruby Hash Key Reference

2019-09-16 08:31发布

问题:

Hello I have a custom Hash that needs to be returned in an API. But currently I'm struggling to find a good way to do so. The following example will describe the problem.

Lets say we have the following code:

data = {name: "Jon", value: "13"}
results = []

[1, 2, 3, 4, 5].each do |i|
  data[:id] = i
  results << data
end

# output 
# results = [{name: "Jon", value: "13", id: 5}, {name: "Jon", value: "13", id: 5}, {name: "Jon", value: "13", id: 5}, {name: "Jon", value: "13", id: 5}, {name: "Jon", value: "13", id: 5}]

I expected something like this:

# results = [{name: "Jon", value: "13", id: 1}, {name: "Jon", value: "13", id: 2}, {name: "Jon", value: "13", id: 3}, {name: "Jon", value: "13", id: 4}, {name: "Jon", value: "13", id: 5}]

How can I achieve this format efficiently (memory usage)? The following code fixes the reference issue (because it creates a new hash) but is inefficient, because my initial hash 'data' is really big.

# inefficient but working
[1, 2, 3, 4, 5].each do |i|
  data[:id] = i
  results << data.dup
end

# output
# results = [{name: "Jon", value: "13", id: 1}, {name: "Jon", value: "13", id: 2}, {name: "Jon", value: "13", id: 3}, {name: "Jon", value: "13", id: 4}, {name: "Jon", value: "13", id: 5}]

Thank you!

回答1:

You can't.

References in Ruby (and in almost every other programming language) point to a single object in memory.

irb(main):004:0> h = { foo: :bar }
=> {:foo=>:bar}
irb(main):005:0> h.object_id
=> 70125571572420

As long as I mutate h I'm still working with same reference:

irb(main):006:0> h[:bar] = 'baz'
=> "baz"
irb(main):007:0> h.object_id
=> 70125571572420

If I want a slightly different version of h without changing h it will be stored as a separate object of course:

h.merge(x: 2).object_id
=> 70125567041320

.merge in this case duplicates the hash and returns the result of merging it with args.

There is no way around this. Hashes in Ruby cannot "inherit" from a reference.