Accessing values in nested hash - ruby

I'm trying to filter nested hashes and pull various keys and values. Here is the hash I'm looking at:
exp = {
fam: {cty: "bk", ins: 3},
spec: {cty: "man", ins: 2},
br: {cty: "qns", ins: 1},
aha: {cty: "man", ins: 0}
}
I'm trying to find all the hash keys where cty is "man". I'd like to run something where the result is the following hash:
e = {
spec: {cty: "man", ins: 2},
aha: {cty: "man", ins: 0}
}
I tried this and it seems like it almost works:
exp.each do |e, c, value|
c = :cty.to_s
value = "man"
if e[c] == value
puts e
end
end
But the result I get is:
=> true
Instead of what I'm looking for:
e = {
spec: {cty: "man", ins: 2},
aha: {cty: "man", ins: 0}
}

To start, you need to understand what iterating over a hash will give you.
Consider this:
exp = {
fam: {cty: "bk", ins: 3},
spec: {cty: "man", ins: 2},
br: {cty: "qns", ins: 1},
aha: {cty: "man", ins: 0}
}
exp.map { |e, c, value| [e, c, value] }
# => [[:fam, {:cty=>"bk", :ins=>3}, nil], [:spec, {:cty=>"man", :ins=>2}, nil], [:br, {:cty=>"qns", :ins=>1}, nil], [:aha, {:cty=>"man", :ins=>0}, nil]]
This is basically what you're doing as you loop and Ruby passes the block the key/value pairs. You're telling Ruby to give you the current hash key in e, the current hash value in c and, since there's nothing else being passed in, the value parameter becomes nil.
Instead, you need a block variable for the key, one for the value:
exp.map { |k, v| [k, v] }
# => [[:fam, {:cty=>"bk", :ins=>3}], [:spec, {:cty=>"man", :ins=>2}], [:br, {:cty=>"qns", :ins=>1}], [:aha, {:cty=>"man", :ins=>0}]]
Notice that the nil values are gone.
Rewriting your code taking that into account, plus refactoring it for simplicity:
exp = {
fam: {cty: 'bk', ins: 3},
spec: {cty: 'man', ins: 2},
br: {cty: 'qns', ins: 1},
aha: {cty: 'man', ins: 0}
}
exp.each do |k, v|
if v[:cty] == 'man'
puts k
end
end
# >> spec
# >> aha
Now it's returning the keys you want, so it becomes easy to grab the entire hashes. select is the appropriate method to use when you're trying to locate specific things:
exp = {
fam: {cty: 'bk', ins: 3},
spec: {cty: 'man', ins: 2},
br: {cty: 'qns', ins: 1},
aha: {cty: 'man', ins: 0}
}
e = exp.select { |k, v| v[:cty] == 'man' }
# => {:spec=>{:cty=>"man", :ins=>2}, :aha=>{:cty=>"man", :ins=>0}}
Older versions of Ruby didn't maintain hash output from the hash iterators so we'd have to coerce back to a hash:
e = exp.select { |k, v| v[:cty] == 'man' }.to_h
# => {:spec=>{:cty=>"man", :ins=>2}, :aha=>{:cty=>"man", :ins=>0}}

e = {}
exp.each do |k,v|
if v[:cty] == "man"
e[k] = v
end
end
p e
or even
e = exp.select do |k,v|
v[:cty] == "man"
end

As the Tin Man pointed out, there are two parameters you can pass in to a block (the code between do and end in this case) when iterating through a hash --- one for its key and the other for its value.
To iterate though a hash (and print out its values)
h = { a: "hello", b: "bonjour", c: "hola" }
using .each method, you can do:
h.each do |key, value|
puts value
end
The result will be:
hello
bonjour
hola
=> {:a=>"hello", :b=>"bonjour", :c=>"hola"}
Please note that the value "returned" is the hash we iterated through, which evaluates to true in ruby. (Anything other than nil or false will evaluate to true in Ruby. See What evaluates to false in Ruby?)
This is important because the reason you got true in your code (which in fact should be {:fam=>{:cty=>"bk", :ins=>3}, :spec=>{:cty=>"man", :ins=>2}, :br=>{:cty=>"qns", :ins=>1}, :aha=>{:cty=>"man", :ins=>0}}), rather than the parsed hash you wanted is that the value returned by .each method for a hash is the hash itself (which evaluates to true).
Which is why osman created an empty hash e = {} so that, during each iteration of the hash, we can populate the newly created hash e with the key and value we want.
This explains why he can do:
e = exp.select do |k,v|
v[:cty] == "man"
end
Here the code depends upon select method being able to return a new hash with the key and value we want (rather than the original hash as is the case for .each method).
But if you do
e = exp.each do |k,v|
v[:cty] == "man"
end
The variable e will be assigned the original hash exp itself, which is not what we want. Therefore it's very important to understand what the returned value is when applying a method.
For more information about return values (and Ruby in general), I highly recommend the free e-book from LaunchSchool "Introduction to Programming with Ruby" (https://launchschool.com/books/ruby). This not only helped me recognise the importance of return values, but also gave me a solid foundation on Ruby prigramming in general, which is really useful if you are planning to learn Ruby on Rails (which I'm doing now :)).

Simplest way to digging into Nested hash is:-
class Hash
def deep_find(key, object=self, found=nil)
if object.respond_to?(:key?) && object.key?(key)
return object[key]
elsif object.is_a? Enumerable
object.find { |*a| found = deep_find(key, a.last) }
return found
end
end
end
Hash.deep_find(key)

Related

Ruby select latest duplicated values from an array of hash

Let say I have this kind of array
a = [
{key: "cat", value: 1},
{key: "dog", value: 2},
{key: "mouse", value: 5},
{key: "rat", value: 3},
{key: "cat", value: 5},
{key: "rat", value: 2},
{key: "cat", value: 1},
{key: "cat", value: 1}
]
Let say I have this array, and want to get only the latest value found for "cat".
I know how to select all of them
like
a.select do |e|
e[:key] == "cat"
end
But I'm looking for a way to just get a selection of the last 3
desired result would be
[
{key: "cat", value: 5},
{key: "cat", value: 1},
{key: "cat", value: 1}
]
thanks!
In a comment on the question #Stefan suggested:
a.select { |e| e[:key] == "cat" }.last(3)
Provided a is not too large that is likely what you should use. However, if a is large, and especially if it contains many elements (hashes) h for which h[:key] #=> "cat", it likely would be more efficient to iterate backwards from the end of the array and terminate ("short-circuit") as soon as three elements h have been found for which h[:key] #=> "cat". This also avoids the construction of a potentially-large temporary array (a.select { |e| e[:key] == "cat" }).
One way to do that is as follows.
a.reverse_each.with_object([]) do |h,arr|
arr.insert(0,h) if h[:key] == "cat"
break arr if arr.size == 3
end
#=> [{:key=>"cat", :value=>5},
# {:key=>"cat", :value=>1},
# {:key=>"cat", :value=>1}]
See Array#reverse_each, Enumerator#with_object and Array#insert. Note that because reverse_each and with_object both return enumerators, chaining them produces an enumerator as well:
a.reverse_each.with_object([])
#=> #<Enumerator: #<Enumerator: [{:key=>"cat", :value=>1},
# ...
# {:key=>"cat", :value=>1}]:reverse_each>:with_object([])>
It might be ever-so-slightly faster to replace the block calculation with
arr << h if h[:key] == "cat"
break arr.reverse if arr.size == 3
If a contains fewer elements h for which h[:key] #=> "cat" an array arr will be returned for which arr.size < 3. It therefore is necessary to confirm that the array returned contains three elements.
This check must also be performed when #Stefan's suggested code is used, as (for example)
a.select { |e| e[:key] == "cat" }.last(99)
#=> [{:key=>"cat", :value=>1},
# {:key=>"cat", :value=>5},
# {:key=>"cat", :value=>1},
# {:key=>"cat", :value=>1}]

I need to replace the hash key inside the array with values from a h`ash

I have an array like this
arr = [["ContactCreate", "Test1"], [nil, nil], ["ROW1", "one"],
["ROW2", "four"], ["ROW3", "seven"], ["ROW4", "Ten"],
["ROW5", "thirteen"]]
and a hash like this
h = {"ROW1"=>["id=xxx", "TypeAndWait"], "ROW2"=>["id=xxx", "TypeAndWait"],
"ROW3"=>["id=yyy", "Select"], "ROW4"=>["id=zzz", "SelectAndWait"],
"ROW5"=>["id=aaa", "Check"]}
I want to replace first element of the each subarray with its hash value, provided it is a hash key. The resultant array should be as follows.
[["ContactCreate", "Test1"], [nil, nil], [["id=xxx", "TypeAndWait"], "one"],
[["id=xxx", "TypeAndWait"], "four"], [["id=yyy", "Select"], "seven"],
[["id=zzz", "SelectAndWait"], "Ten"], [["id=aaa", "Check"], "thirteen"]]
For example, "ROW1" in the third subarray should be replaced by h["ROW1"].
Is there any easy way to achieve this?
This should work (arr is your array, h is your hash)
arr.map do |x, y|
[h[x] || x, y]
end
h.default_proc = Proc.new { |h,k| k }
arr.map { |e1,e2| [h[e1], e2] }
This approach could also be used as follows.
arr = [["ContactCreate", "Test1", "Test2"], ["ROW1", nil], [1, "ROW2", "four"]]
arr.map { |a| a.map { |e| h[e] } }
#=> [["ContactCreate", "Test1", "Test2"], [["id=xxx", "TypeAndWait"], nil],
# [1, ["id=xxx", "TypeAndWait"], "four"]]
The default proc merely causes h[k] to return k if h does not have a key k. See Hash#default_proc=.
If one does not wish to modify h by attaching a default proc, one could attach the default proc to h.dup and use the dup in place of h.

Merging/adding hashes in array with same key

I have an array of hashes:
array = [
{"points": 0, "block": 3},
{"points": 25, "block": 8},
{"points": 65, "block": 4}
]
I need to merge the hashes. I need the output to be:
{"points": 90, "block": 15}
You could merge the hashes together, adding the values that are in both hashes:
result = array.reduce do |memo, next_hash|
memo.merge(next_hash) do |key, memo_value, next_hash_value|
memo_value + next_hash_value
end
end
result # => {:points=>90, :block=>15}
and if your real hash has keys that don't respond well to +, you have access to the key, you could set up a case statement, to handle the keys differently, if needed.
If you have the array as you mentioned in this structure:
array = [
{"points": 0, "block": 3},
{"points": 25, "block": 8},
{"points": 65, "block": 4}
]
You can use the following code to achieve your goal:
result = {
points: array.map{ |item| item[:points] }.inject(:+),
block: array.map{ |item| item[:block] }.inject(:+)
}
You will get this result:
{:points=>90, :block=>15}
Note: This will iterate twice over the array. I'm trying to figure out a better way to iterate once and still have the same elegant/easy to ready code.
If you want to do it more generically (more keys than :points and :block), then you can use this code:
array = [
{"points": 0, "block": 3},
{"points": 25, "block": 8},
{"points": 65, "block": 4}
]
keys = [:points, :block] # or you can make it generic with array.first.keys
result = keys.map do |key|
[key, array.map{ |item| item.fetch(key, 0) }.inject(:+)]
end.to_h
You can create method as below to get the result
def process_array(array)
points = array.map{|h| h[:points]}
block = array.map{|h| h[:block]}
result = {}
result['points'] = points.inject{ |sum, x| sum + x }
result['block'] = block.inject{ |sum, x| sum + x }
result
end
and calling the method with array input will give you expected result.
[54] pry(main)> process_array(array)
=> {"points"=>90, "block"=>15}
You can also use the Enumerator each_with_object, using a hash as object.
result = array.each_with_object(Hash.new(0)) {|e, h| h[:points] += e[:points]; h[:block] += e[:block] }
# => {:points=>90, :block=>15}
Hash.new(0) means initialise the hash to default value 0 for any keys, for example:
h = Hash.new(0)
h[:whathever_key] # => 0
I was interested in how the reduce method introduced by "Simple Lime" worked and also how it would benchmark against simple iteration over the array and over the keys of each hash.
Here is the code of the "iteration" approach:
Hash.new(0).tap do |result|
array.each do |hash|
hash.each do |key, val|
result[key] = result[key] + val
end
end
end
I was surprised, that the "iteration" code performed 3 times better than the reduce approach.
Here is the benchmark code https://gist.github.com/landovsky/6a1b29cbf13d0cf81bad12b6ba472416

Cleanest way to count multiple values in an array of hashes?

I have an array of hashes like this:
data = [
{group: "A", result: 1},
{group: "B", result: 1},
{group: "A", result: 0},
{group: "A", result: 1},
{group: "B", result: 1},
{group: "B", result: 1},
{group: "B", result: 0},
{group: "B", result: 0}
]
The group will only be either A or B, and the result will only be 1 or 0. I want to count how many times the result is 0 or 1 for each group, i.e., to get a tally like so:
A: result is "1" 2 times
result is "0" 1 time
B: result is "1" 3 times
result is "0" 2 times
I am thinking of storing the actual results in a nested hash, like:
{ a: { pass: 2, fail: 1 }, b: { pass: 3, fail: 2 } }
but this might not be the best way, so I'm open to other ideas here.
What would be the cleanest way to do this in Ruby while iterating over the data only once? Using data.inject or data.count somehow?
stats = Hash[data.group_by{|h| [h[:group], h[:result]] }.map{|k,v| [k, v.count] }]
#=> {["A", 1]=>2, ["B", 1]=>3, ["A", 0]=>1, ["B", 0]=>2}
I'll leave the transformation to the desired format up to you ;-)
This way would go over the hash only one time:
result = Hash.new { |h, k| h[k] = { pass: 0, fail: 0 }}
data.each do |item|
result[item[:group]][item[:result] == 0 ? :fail : :pass] += 1
end
result
# => {"A"=>{:pass=>2, :fail=>1}, "B"=>{:pass=>3, :fail=>2}}
You could use the form of Hash#update (same as Hash#merge!) that takes a block to determine the values of keys that are contained in both hashes being merged:
data.map(&:values).each_with_object({}) { |(g,r),h|
h.update({g.to_sym=>{pass: r, fail: 1-r } }) { |_,oh,nh|
{ pass: oh[:pass]+nh[:pass], fail: oh[:fail]+nh[:fail] } } }
#=> {:A=>{:pass=>2, :fail=>1}, :B=>{:pass=>3, :fail=>2}}
If that is truely your desired output then something like this would work:
def pass_fail_hash(a=[],statuses=[:pass,:fail])
a.map(&:dup).group_by{|h| h.shift.pop.downcase.to_sym}.each_with_object({}) do |(k,v),obj|
obj[k] = Hash[statuses.zip(v.group_by{|v| v[:result]}.map{|k,v| v.count})]
statuses.each {|status| obj[k][status] ||= 0 }
end
end
Then
pass_fail_hash data
#=> {:a=>{:pass=>2, :fail=>1}, :b=>{:pass=>3, :fail=>2}}
Thank you to #CarySwoveland for pointing out my original method did not take into account cases where there were no passing or failing values. This has now been resolved so that a hash array like [{ group: "A", result: 1 }] will now show {a:{:pass => 1, :fail => 0}} where it would have previously been {a:{:pass => 1, :fail => nil}}.

How to merge two hashes with no new keys

How could I merge two hashes that results in no new keys, meaning the merge would merge keys that exist in both hashes?
For example, I want the following:
h = {:foo => "bar"}
j = {:foo => "baz", :extra => "value"}
puts h.merge(j) # {:foo => "baz"}
I'm looking for a really clean way of doing this as my current implementation is pretty messy.
You could remove keys that weren't in the first hash from the second hash, then merge:
h.merge j.select { |k| h.keys.include? k }
Unlike my edited-out alternative, this is safe if you decide to change it to a merge! or update.
If you're using activesupport (part of rails), you can take advantage of 2 extra methods on Hash:
Hash#slice takes the desired keys as separate arguments (not an array of keys) and returns a new hash with just the keys you asked for.
Hash#except takes the same arguments as slice, but returns a new hash with keys that were not in the arguments.
First load activesupport:
require 'active_support/core_ext'
Merge only entries from j whose keys are already in h (i.e. modify, but don't add any or remove entries in h):
h.merge(j.slice(*h.keys))
Example:
ignore_new = ->(h, j) { h.merge(j.slice(* h.keys)) }
ignore_new.({a: 1, b: 2, c: 3}, {b: 10, c: 11, d: 12})
# => {:a=>1, :b=>10, :c=>11}
Get the leftovers from j that weren't in h:
j.except(*h.keys)
Bonus:
If you want true intersection, meaning you want a result that only has keys that are in common between the 2 hashes, do this:
h.merge(j).slice(* ( h.keys & j.keys ) )
Example:
intersect = ->(h, j) { h.merge(j).slice(* (h.keys & j.keys) ) }
intersect.({a: 1, b: 2, c: 3}, {b: 10, c: 11, d: 12})
# => {:b=>10, :c=>11}
and leftovers from h that weren't in j:
h.except(*j.keys)
You may also want to use activesupport's HashWithIndifferentAccess if you want string & symbol key-access to be considered equivalent.
Note that none of the above examples change the original hashes; new hashes are returned instead.
Yjerem's answer works in Ruby 1.9, but not in 1.8.x. In 1.8.x the Hash#select method returns an array. Hash#reject returns a hash.
h.reject { |k,v| !j.keys.include? k }
If you want to keep only key-value pairs that have identical values, you can do this:
h.reject { |k,v| j[k] != h[k] }
The edge case there is nils. If you are storing nils in your Hash then you have to do this:
h.reject { |k,v| !j.has_key? k or j[k] != h[k] }
[h].inject({}) { |m,e| e.merge(j) { |k,o,n| m[k] = n }; m}
or
[{}].inject(h) { |m,e| m.merge(j) { |k,o,n| e[k] = n }; e}
or (probably the best, but not technically a single expression) ...
t = {}; h.merge(j) { |k,o,n| t[k] = n }; t
The more customized way of doing this is:
h = {"foo"=> "bar"}
j = {"foo" => "baz", "extra" => "value"}
k = h.merge(j)
result: {"foo"=>"baz", "extra"=>"value"}
Here the key "foo" in the second hash is overriding the "foo" in first hash.But if you want to keep the old value i.e bar or if you want keep the new value i.e "baz"? You can do something like this:
k = h.merge(j){|key, old, new| old}
result: {"foo"=>"bar", "extra"=>"value"}
k = h.merge(j){|key, old, new| new}
result: {"foo"=>"baz", "extra"=>"value"}

Resources