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

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) }

Related

Create a new hash from an existing array of hashes

I am newbie to ruby . I have an array of hashes input_array
[{
"name"=>"test1",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>1000,
"name"=>"test2",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>5000,
"name"=>"test3",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>3000,
"name"=>"test4",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>2000}]
and an array
existing_names_array = ["test1","test2"]
The below line gets me all the names into input_names
input_array.each_with_index {|val, index| input_names << input_array[index]['name'] }
But how can I get the input_names to be a hash with name as key and its respective users as value?
Because my final goal is to check the names which are in input_names, but not in existing_names_array and get a count of those names and users
As the names tes1,test2 exists in existing_names_array, I need the count of rest of names and count of their respective users in input_array
Expected output:
output_names = test3, test 4
total_output_names = 2
output_users = 3000,2000
total_output_users = 5000
If you use ActiveSupport's Enumerable#index_by with Ruby core's Hash#transform_values it's pretty easy, if I'm understanding your question correctly:
# add gem 'activesupport' to Gemfile, or use Rails, then ..
require 'active_support/all'
users_and_counts = input_array.
index_by { |hsh| hsh["name"] }.
transform_values { |hsh| hsh["users"] }
# => { "test1" => 1000, "test2" => 5000, ... }
You can do this with Enumerable#reduce (or Enumerable#each_with_object) as well:
users_and_counts = input_array.reduce({}) do |memo, hsh|
memo[hsh["name"]] = hsh["users"]
memo
end
Or, the simplest way, with good old each:
users_and_counts = {}
input_array.each do |hsh|
users_and_counts[hsh["name"]] = hsh["users"]
end
in response to comment
In general, there are a few ways to check whether an element is found in an array:
array = ["foo", "bar"]
# 1. The simple standard way
array.include?("foo") # => true
# 2. More efficient way
require 'set'
set = Set.new(array)
set.member?("foo") # => true
So with this knowledge we can break up our task into a few steps:
Make a new hash which is a copy of the one we built above, but without the key-vals corresponding to users in the existing_names_array (see Hash#select and Hash#reject):
require 'set'
existing_names_set = Set.new(existing_names_array)
new_users_and_counts = users_and_counts.reject do |name, count|
existing_names_set.member?(name)
end
# => { "test3" => 3000, "test4" => 2000 }
Use Hash#keys to get the list of user names:
new_user_names = new_users_and_counts.keys
# => ["test3", "test4"]
Use Hash#values to get the list of counts:
new_user_counts = new_users_and_counts.values
# => [3000, 2000]
I assume input_array is to be as follows.
input_array = [
{ "name"=>"test1", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>1000 },
{ "name"=>"test2", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>5000 },
{ "name"=>"test3", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>3000 },
{ "name"=>"test4", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>2000}
]
We are also given:
names = ["test1", "test2"]
The information of interest can be represented nicely by the following hash (which I will construct):
summary = { "test1"=>1000, "test2"=>5000 }
We might use that to compute the following.
names = summary.keys
#=> ["test1", "test2"]
users = summary.values
#=> [1000, 5000]
total_users = users.sum
#=> 6000
There are many ways to construct the hash summary. I prefer the following.
summary = input_array.each_with_object({}) do |g,h|
key = g["name"]
h[key] = g["users"] if names.include?(key)
end
#=> {"test1"=>1000, "test2"=>5000}
Another way is as follows.
summary = input_array.map { |g| g.values_at("name", "users") }.
.to_h
.slice(*names)
See [Hash#values_at(https://ruby-doc.org/core-2.7.0/Hash.html#method-i-values_at), Array#to_h and Hash#slice. The steps are as follows.
arr = input_array.map { |g| g.values_at("name", "users") }
#=> [["test1", 1000], ["test2", 5000], ["test3", 3000], ["test4", 2000]]
h = arr.to_h
#=> {"test1"=>1000, "test2"=>5000, "test3"=>3000, "test4"=>2000}
summary = h.slice(*names)
#=> {"test1"=>1000, "test2"=>5000}
The splat operator converts h.slice(*names), which is h.slice(*["test1", "test2"]), to h.slice("test1", "test2"). That's because Hash#slice was defined to accept a variable number of keys as arguments, rather than an array of keys as the argument.

Ruby change the order of hash keys

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 }

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.

ruby how to make a hash with new keys, and values from an array

I have an array of arrays like this:
arr = [["food", "eggs"],["beverage", "milk"],["desert", "cake"]]
And I need to turn it into an array of hashes where the keys are custom and new, and the values of the keys are the values in the array, like this:
hash = [{"category": "food", "item":"eggs"},
{"category": "beverage", "item":"milk"}
{"category": "desert", "item":"cake"}]
how would I do this?
thank you
Use Array#map:
arr = [["food", "eggs"], ["beverage", "milk"], ["desert", "cake"]]
arr.map { |category, item| { category: category, item: item } }
# => [
# {:category=>"food", :item=>"eggs"},
# {:category=>"beverage", :item=>"milk"},
# {:category=>"desert", :item=>"cake"}
# ]
arr = [["food", "eggs"],["beverage", "milk"],["desert", "cake"]]
arr.inject([]) do |hash, (v1, v2)|
hash << { category: v1, item: v2 }
end
I used inject to keep the code concise.
Next time you may want to show what you have tried in the question, just to demonstrate that you actually tried to do something before asking for code.
hash = array.map {|ary| Hash[[:category, :item].zip ary ]}
hash = arr.each_with_object({}){|elem, hsh|hsh[elem[0]] = elem[1]}

Ruby: What is the easiest method to update Hash values?

Say:
h = { 1 => 10, 2 => 20, 5 => 70, 8 => 90, 4 => 34 }
I would like to change each value v to foo(v), such that h will be:
h = { 1 => foo(10), 2 => foo(20), 5 => foo(70), 8 => foo(90), 4 => foo(34) }
What is the most elegant way to achieve this ?
You can use update (alias of merge!) to update each value using a block:
hash.update(hash) { |key, value| value * 2 }
Note that we're effectively merging hash with itself. This is needed because Ruby will call the block to resolve the merge for any keys that collide, setting the value with the return value of the block.
Rails (and Ruby 2.4+ natively) have Hash#transform_values, so you can now do {a:1, b:2, c:3}.transform_values{|v| foo(v)}
https://ruby-doc.org/core-2.4.0/Hash.html#method-i-transform_values
If you need it to work in nested hashes as well, Rails now has deep_transform_values(source):
hash = { person: { name: 'Rob', age: '28' } }
hash.deep_transform_values{ |value| value.to_s.upcase }
# => {person: {name: "ROB", age: "28"}}
This will do:
h.each {|k, v| h[k] = foo(v)}
The following is slightly faster than #Dan Cheail's for large hashes, and is slightly more functional-programming style:
new_hash = Hash[old_hash.map {|key, value| key, foo(value)}]
Hash#map creates an array of key value pairs, and Hash.[] converts the array of pairs into a hash.
There's a couple of ways to do it; the most straight-forward way would be to use Hash#each to update a new hash:
new_hash = {}
old_hash.each do |key, value|
new_hash[key] = foo(value)
end

Resources