Ruby change the order of hash keys - ruby

I have a hash and I would like the change the key order from.
{"result"=>{"data"=>[{"Quantity"=>13, "Rate"=>17.1},
{"Quantity"=>29,"Rate"=>3.2},
{"Quantity"=>7, "Rate"=>3.4}]}}
To:
{"result"=>{"data"=>[{"Rate"=>17.1, "Quantity"=>13},
{"Rate"=>3.2, "Quantity"=>29},
{"Rate"=>3.4, "Quantity"=>7}]}}
that can be accessed by hash["result"]["data"]. I tried;
hash["result"]["data"][0].each_value{|v| v.replace({"Rate" => v.delete("Rate")}.merge(v))}
But it gives error:
NoMethodError (undefined method `delete' for
17.1:Float):

Try this,
hash["result"]["data"].each{|v| v.replace({"Rate" => v.delete("Rate")}.merge(v))}

I think their is no need to do this much of operations. I suppose data contains your whole hash then just one map and reverse of hash will resolve your problem.
data['result']['data'] = data['result']['data'].map{|v| Hash[v.to_a.reverse]}

Four more ways...
Reverse the order of those hash items:
hash['result']['data'].map! { |h| h.to_a.reverse.to_h }
Move "Quantity" to the end:
hash['result']['data'].each { |h| h["Quantity"] = h.delete("Quantity") }
Move the first item to the end:
hash['result']['data'].map! { |h| h.merge([h.shift].to_h) }
Force a certain given order:
keys = ["Rate", "Quantity"]
hash['result']['data'].map! { |h| keys.zip(h.values_at(*keys)).to_h }

hash = {a: 1, b: 2, c: 3}
hash.slice!(:b, :a)
puts hash # { :b => 2, :a => 1 }

Related

why am I not able to merge hashes within an each loop

Iam trying to loop through an array and merge one key/value pair to my hashes within this array, however it is not working. When I do it manually, it is working. What am I doing wrong?
:001 > array = [{foo: 5}, {bar: 3}]
=> [{:foo=>5}, {:bar=>3}]
:002 > array.each{|hash| hash.merge(match: true)}
=> [{:foo=>5}, {:bar=>3}]
:003 > array[0].merge(match: true)
=> {:foo=>5, :match=>true}
Use merge! instead of merge. merge method returns a new hash, merge! adds the key value pairs to the hash.
array = [{ foo: 5 }, { bar: 3 }]
array.each { |hash| hash.merge!(match: true) }
#demir's answer is right.
If you don't want to change the original array. You can use map instead of each and assign the output to new variable.
array = [{ foo: 5}, { bar: 3 }]
new_array = array.map { |hsh| hsh.merge(match: true) }

what is difference between store vs merge in ruby hashes?

i create a hash:
a = {}
=> {}
then:
a.store(:b, {})
=> {}
and:
a.merge!(c: {})
=> {:b=>{}, :c=>{}}
what are differences actually?
store is an assignment method.
a = {}
# => {}
a.store(:b, {})
a
# => {:b=>{}}
# Here you are assigning a key :b with empty hash {}
Another example to make it clearer:
a = {}
# => {}
a.store("key", "value")
a
# => {"key"=>"value"}
merge on the other hand manipulates your existing hash by merging with a different hash.
Example:
a = {}
# => {}
a.merge({"key" => "value"})
# => {"key"=>"value"}
a
# => {} # original value still unchanged
a.merge!({"key" => "value"})
# => {"key"=>"value"}
a
# => {"key"=>"value"} # original value updated
However unless you use merge! a's value will not get changed i.e. merge will occur only for return.
what are differences actually?
I think the main difference is merge! will let you decide which value to keep when duplicate key is provided, since it expects a block as well.
On the other hand, when you use store, the previous value will be replaced by the latest value when duplicate key is provided.
store
h1 = { "a" => 100, "b" => 200 }
h1.store("b", 254)
#=> {"a"=>100, "b"=>254}
merge!
h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }
h1.merge!(h2) { |key, v1, v2| v1 }
#=> {"a"=>100, "b"=>200, "c"=>300}
store takes just one key/value tuple as input and returns the stored value.
h1 = { foo: 'bar' }
h1.store(:baz, 1) #=> 1
h1 #=> { foo: 'bar', baz: 1 }
Whereas merge! accepts a hash as input and returns the updated hash:
h2 = { foo: 'bar' }
h2.merge!(baz: 1, buz: 2) #=> { foo: 'bar', baz: 1, buz: 2 }
h2 #=> { foo: 'bar', baz: 1, buz: 2 }
merge! takes one argument, which is hash to merge into original. store takes two arguments, which is key and value to store. Therefore, with merge!, you can add multiple keys to original hash, like this:
a = {}
a.merge!(a: 'a', b: 'b')
a
# => {:a => "a", :b => "b"}
For a hash h, Hash#store has the same effect as Hash#[]=: they both either add one key-value pair k=>v to h (if h does not have a key k) or modify the value of key k (if the hash already contains the key). Also, they both return v.
Hash#merge! (aka update) has two forms. The first does the same thing as store, except it does it for each key-value pair in another hash. The second form uses a block to determine the values of keys that are present in both hashes being merged. Please refer to the docs for details on that form of the method. Both forms of merge! return the "merged" hash.
Hash#merge is not a relevant comparison as it does not mutate the hash.

Shorthand way to take an array of Ruby models and turn it into a hash with the id as the key?

I have an array of models that I would like to turn into a hash so I can reference them by id. I know I can iterate over the items and put them in a hash, but I know there must be a quick, shorthand way of doing this same thing:
my_models_hash = {}
#my_models.each do |model|
my_models_hash[model.id] = model
end
How can I do this same thing in one, short line?
You're after each_with_object.
my_models_hash = #my_models.each_with_object({}) { |m,h| h[m.id] = m }
One way:
#my_models.map { |m| [m.id, m] }.to_h
Prior to v2.0, this would have to be written:
Hash[#my_models.map { |m| [m.id, m] }]
If you're using Rails (or more specifically ActiveSupport), there's Enumerable#index_by:
#my_models.index_by(&:id)
#=> { 1 => #<Model id: 1, ...>, 2 => #<Model id: 2, ...>, ...}

How to merge multiple hashes in Ruby?

h = { a: 1 }
h2 = { b: 2 }
h3 = { c: 3 }
Hash#merge works for 2 hashes: h.merge(h2)
How to merge 3 hashes?
h.merge(h2).merge(h3) works but is there a better way?
You could do it like this:
h, h2, h3 = { a: 1 }, { b: 2 }, { c: 3 }
a = [h, h2, h3]
p Hash[*a.map(&:to_a).flatten] #= > {:a=>1, :b=>2, :c=>3}
Edit: This is probably the correct way to do it if you have many hashes:
a.inject{|tot, new| tot.merge(new)}
# or just
a.inject(&:merge)
Since Ruby 2.0 on that can be accomplished more graciously:
h.merge **h1, **h2
And in case of overlapping keys - the latter ones, of course, take precedence:
h = {}
h1 = { a: 1, b: 2 }
h2 = { a: 0, c: 3 }
h.merge **h1, **h2
# => {:a=>0, :b=>2, :c=>3}
h.merge **h2, **h1
# => {:a=>1, :c=>3, :b=>2}
You can just do
[*h,*h2,*h3].to_h
# => {:a=>1, :b=>2, :c=>3}
This works whether or not the keys are Symbols.
Ruby 2.6 allows merge to take multiple arguments:
h = { a: 1 }
h2 = { b: 2 }
h3 = { c: 3 }
h4 = { 'c' => 4 }
h5 = {}
h.merge(h2, h3, h4, h5) # => {:a=>1, :b=>2, :c=>3, "c"=>4}
This works with Hash.merge! and Hash.update too. Docs for this here.
Also takes empty hashes and keys as symbols or strings.
Much simpler :)
Answer using reduce (same as inject)
hash_arr = [{foo: "bar"}, {foo2: "bar2"}, {foo2: "bar2b", foo3: "bar3"}]
hash_arr.reduce { |acc, h| (acc || {}).merge h }
# => {:foo2=>"bar2", :foo3=>"bar3", :foo=>"bar"}
Explanation
For those beginning with Ruby or functional programming, I hope this brief explanation might help understand what's happening here.
The reduce method when called on an Array object (hash_arr) will iterate through each element of the array with the returned value of the block being stored in an accumulator (acc). Effectively, the h parameter of my block will take on the value of each hash in the array, and the acc parameter will take on the value that is returned by the block through each iteration.
We use (acc || {}) to handle the initial condition where acc is nil. Note that the merge method gives priority to keys/values in the original hash. This is why the value of "bar2b" doesn't appear in my final hash.
Hope that helps!
To build upon #Oleg Afanasyev's answer, you can also do this neat trick:
h = { a: 1 }
h2 = { b: 2 }
h3 = { c: 3 }
z = { **h, **h2, **h3 } # => {:a=>1, :b=>2, :c=>3}
Cheers!
class Hash
def multi_merge(*args)
args.unshift(self)
args.inject { |accum, ele| accum.merge(ele) }
end
end
That should do it. You could easily monkeypatch that into Hash as I have shown.
newHash = [h, h2, h3].each_with_object({}) { |oh, nh| nh.merge!(oh)}
# => {:a=>1, :b=>2, :c=>3}
Here are the 2 monkeypatched ::Hash instance methods we use in our app. Backed by Minitest specs. They use merge! instead of merge internally, for performance reasons.
class ::Hash
# Merges multiple Hashes together. Similar to JS Object.assign.
# Returns merged hash without modifying the receiver.
#
# #param *other_hashes [Hash]
#
# #return [Hash]
def merge_multiple(*other_hashes)
other_hashes.each_with_object(self.dup) do |other_hash, new_hash|
new_hash.merge!(other_hash)
end
end
# Merges multiple Hashes together. Similar to JS Object.assign.
# Modifies the receiving hash.
# Returns self.
#
# #param *other_hashes [Hash]
#
# #return [Hash]
def merge_multiple!(*other_hashes)
other_hashes.each(&method(:merge!))
self
end
end
Tests:
describe "#merge_multiple and #merge_multiple!" do
let(:hash1) {{
:a => "a",
:b => "b"
}}
let(:hash2) {{
:b => "y",
:c => "c"
}}
let(:hash3) {{
:d => "d"
}}
let(:merged) {{
:a => "a",
:b => "y",
:c => "c",
:d => "d"
}}
describe "#merge_multiple" do
subject { hash1.merge_multiple(hash2, hash3) }
it "should merge three hashes properly" do
assert_equal(merged, subject)
end
it "shouldn't modify the receiver" do
refute_changes(->{ hash1 }) do
subject
end
end
end
describe "#merge_multiple!" do
subject { hash1.merge_multiple!(hash2, hash3) }
it "should merge three hashes properly" do
assert_equal(merged, subject)
end
it "shouldn't modify the receiver" do
assert_changes(->{ hash1 }, :to => merged) do
subject
end
end
end
end
Just for fun, you can do it also this way:
a = { a: 1 }, { b: 2 }, { c: 3 }
{}.tap { |h| a.each &h.method( :update ) }
#=> {:a=>1, :b=>2, :c=>3}
With modern Ruby, you wont even have to use merge unless you need to change the variable in place using the ! variant, you can just double splat (**) your way through.
h = { a: 1 }
h2 = { b: 2 }
h3 = { c: 3 }
merged_hash = { **h, **h2, **h3 }
=> { a: 1, b: 2, c:3 }

Convert cartesian product to nested hash in ruby

I have a structure with a cartesian product that looks like this (and could go out to arbitrary depth)...
variables = ["var1","var2",...]
myhash = {
{"var1"=>"a", "var2"=>"a", ...}=>1,
{"var1"=>"a", "var2"=>"b", ...}=>2,
{"var1"=>"b", "var2"=>"a", ...}=>3,
{"var1"=>"b", "var2"=>"b", ...}=>4,
}
... it has a fixed structure but I'd like simple indexing so I'm trying to write a method to convert it to this :
nested = {
"a"=> {
"a"=> 1,
"b"=> 2
},
"b"=> {
"a"=> 3,
"b"=> 4
}
}
Any clever ideas (that allow for arbitrary depth)?
Maybe like this (not the cleanest way):
def cartesian_to_map(myhash)
{}.tap do |hash|
myhash.each do |h|
(hash[h[0]["var1"]] ||= {}).merge!({h[0]["var2"] => h[1]})
end
end
end
Result:
puts cartesian_to_map(myhash).inspect
{"a"=>{"a"=>1, "b"=>2}, "b"=>{"a"=>3, "b"=>4}}
Here is my example.
It uses a method index(hash, fields) that takes the hash, and the fields you want to index by.
It's dirty, and uses a local variable to pass up the current level in the index.
I bet you can make it much nicer.
def index(hash, fields)
# store the last index of the fields
last_field = fields.length - 1
# our indexed version
indexed = {}
hash.each do |key, value|
# our current point in the indexed hash
point = indexed
fields.each_with_index do |field, i|
key_field = key[field]
if i == last_field
point[key_field] = value
else
# ensure the next point is a hash
point[key_field] ||= {}
# move our point up
point = point[key_field]
end
end
end
# return our indexed hash
indexed
end
You can then just call
index(myhash, ["var1", "var2"])
And it should look like what you want
index({
{"var1"=>"a", "var2"=>"a"} => 1,
{"var1"=>"a", "var2"=>"b"} => 2,
{"var1"=>"b", "var2"=>"a"} => 3,
{"var1"=>"b", "var2"=>"b"} => 4,
}, ["var1", "var2"])
==
{
"a"=> {
"a"=> 1,
"b"=> 2
},
"b"=> {
"a"=> 3,
"b"=> 4
}
}
It seems to work.
(see it as a gist
https://gist.github.com/1126580)
Here's an ugly-but-effective solution:
nested = Hash[ myhash.group_by{ |h,n| h["var1"] } ].tap{ |nested|
nested.each do |v1,a|
nested[v1] = a.group_by{ |h,n| h["var2"] }
nested[v1].each{ |v2,a| nested[v1][v2] = a.flatten.last }
end
}
p nested
#=> {"a"=>{"a"=>1, "b"=>2}, "b"=>{"a"=>3, "b"=>4}}
You might consider an alternative representation that is easier to map to and (IMO) just as easy to index:
paired = Hash[ myhash.map{ |h,n| [ [h["var1"],h["var2"]], n ] } ]
p paired
#=> {["a", "a"]=>1, ["a", "b"]=>2, ["b", "a"]=>3, ["b", "b"]=>4}
p paired[["a","b"]]
#=> 2

Resources