ruby use array tvalues to index nested hash of hash [duplicate] - ruby

This question already has answers here:
What is the most ruby-ish way of accessing nested hash values at arbitrary depths? [duplicate]
(4 answers)
How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
(16 answers)
Closed 7 years ago.
In Ruby, I want to do something like this,
I have a hash of hash built like this.
h = {1 => {2 => {3 => "three"}},'a' => { 'b' => { 'c' => "basd"}}}
=> {"a"=>{"b"=>{"c"=>"basd"}}, 1=>{2=>{3=>"three"}}}
If I have an array with values like this.
a = [1, 2, 3]
I want to have a method which will use the array values to index nested keys in my hash and return the value pointed by last key (as guided by previous array/keys)
for eg.
getHashValue([1,2,3]) should return "three" => h[1][2][3]
if a = ['a','b', 'c'] then the return value should be basd.
How to get this done?

And then there's:
keys.inject(hash, :fetch)
or for earlier Ruby versions:
keys.inject(hash) {|h, k| h[k]}
If you did want to use recursion, a more Rubyesque approach would be:
def get_value(obj, keys)
keys.empty? ? obj : get_value(obj[keys[0]], keys[1..-1])
end

Simple recursion
def getValue(hash, keys, i = 0)
if i + 1 < keys.length
getValue(hash[keys[i]], keys, i + 1)
else
hash[keys[i]]
end
end
getValue(h, [1,2,3]) => "three"
getValue(h, ['a','b','c']) => "basd"

Ruby 2.3.0 introduced a new method called dig on both Hash and Array that solves this problem entirely.
It returns nil if an element is missing at any level of nesting.
h1 = {}
h2 = { a: {} }
h3 = { a: { b: {} } }
h4 = { a: { b: { c: 100 } } }
h1.dig(:a, :b, :c) # => nil
h2.dig(:a, :b, :c) # => nil
h3.dig(:a, :b, :c) # => nil
h4.dig(:a, :b, :c) # => 100

h = {1 => {2 => {3 => "three"}},'a' => { 'b' => { 'c' => "basd"}}}
a = ['a','b', 'c']
a.inject(h, :[]) # => "basd"
h = {1 => {2 => {3 => "three"}},'a' => { 'b' => { 'c' => "basd"}}}
a = [1, 2, 3]
a.inject(h, :[]) # => "three"

Related

How do I keep count of all the elements inside hashes which are inside an array?

Basically, I have an array which contains 3 hashes. I want to count and return each key and value inside the hashes, which include any duplicates. The code is below, I have done the first draft of the code as you can see for yourself below.
my_array = [{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
#Count the number of times each element appears inside the hash
#so the output should have the number of times the :names, "blake" and "ashley" element appears
#EXPECTED OUTPUT: :name = 3, "blake" = 2, "ashley" = 1
def getOccurances(array)
array.group_by{|v| v[:name]}.map{|k,v| {name: k, count: v.length}}
end
getOccurances(my_array)
#ACTUAL OUTPUT: {:name => "blake", :count => 2}, {:name => "ashley", :count => 1}
You can map each Hash to an Array of [key, val] pairs, then flatten and each occurrence:
[{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}].
map(&:to_a).flatten.
reduce(Hash.new { 0 }) {|o, v| o[v] += 1; o }
The argument to reduce is a Hash initialized with a block, so the default value of uninitialized keys is 0; we simply iterate through the flattened entries and accumulate a count of values.
my_array.each_with_object(Hash.new(0)) { |g,h| h[g[:name]] += 1 }.
map { |k,v| { name: k, count: v } }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
Note:
my_array.each_with_object(Hash.new(0)) { |g,h| h[g[:name]] += 1 }
#=> {"blake"=>2, "ashley"=>1}

Combine hash keys and values if keys are identical except for case

Let's say I have a Ruby Hash where at least two keys are identical, except for their case, for instance:
{ 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
Is there a way I can combine like keys (except for their case) such that the resulting Hash might look like this?
{ 'foo' => 3, 'bar' => 3 }
Thank you!
You can build a new hash:
new_hash = Hash.new(0)
old_hash.each_pair { |k, v| new_hash[k.downcase] += v }
You can use inject to loop all the hash items and build a new hash.
hash = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
hash.inject({}) do |result, (key, value)|
key = key.downcase
result[key] = result[key] ? result[key] + value : value
result
end
Here is one more way of doing this
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
p h.collect{|k, v| {k.downcase => v}}.reduce { |a, v| a.update(v) {|k, o, n| o + n } }
#=> {"foo"=>3, "bar"=>3}
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
h.each_with_object({}) { |(k,v),g| g[k.downcase] = g[k.downcase].to_i + v }
#=> {"foo"=>3, "bar"=>3}
This makes use of the fact that if g does not have a key e, g[e] on the right side will equal nil and nil.to_i #=> 0. On the other hand, if g has a key e, h[e].to_i will equal h[e].
Another way:
h.each_with_object({}) { |(k,v),g| g.update(k.downcase=>v) { |_,o,v| o+v } }
#=> {"foo"=>3, "bar"=>3}

Do average on hashes

lets say i got 3 hashes:
hash1={
a => 1,
b => 2,
c => 4
}
hash2={
b => 1,
c => 3
}
hash3={
a => 2,
b => 1,
c => 3,
d => 4
}
I want to average stuff to a new hash according to keys, so new hash will be
result={a=>1.5,b=>4/3,c=>10/3,d=>4}
meaning if key not exists in one hash we don't count it as 0.
is there an elegant way?
result = {}
[hash1, hash2, hash3].each{|h| h.each{|k, v| (result[k] ||= []).push(v)}}
result.each{|k, v| result[k] = v.inject(:+).to_f/v.length}
result # =>
# {
# a => 1.5,
# b => 1.3333333333333333,
# c => 3.3333333333333335,
# d => 4.0
# }

Search an Array of Hashes with a partial Hash in Ruby

Given a hash
z = [{'a' => 1, 'b' => 2}, {'a' => 3, 'b' => 4}, {'a' => 1, 'b' => 4}]
How do I search if the search parameter itself is a hash e.g.
{'a' => 3}
so that I can do something like z.find_by_hash({'a' => 3}) for it to return
{'a' => 3, 'b' => 4}
and also to get a collection of arrays like z.find_by_hash({'a' => 1}) for it to return
[{'a' => 1, 'b' => 2}, {'a' => 1, 'b => 4}]
Thanks
You can do this:
class Array
def find_by_hash(hash)
self.select { |h| h.includes_hash?(hash) }
end
end
class Hash
def includes_hash?(other)
included = true
other.each do |key, value|
included &= self[key] == other[key]
end
included
end
end
This extends Hash by a method to find out if a Hash includes another (with multiple keys and values). Array is extended with the method you wanted, but it's a more generic approach since you can do this:
ary = [ {:a => 1, :b => 3, :c => 5}, {:a => 5, :b => 2, :c => 8} ]
ary.find_by_hash( { :a => 1, :c => 5 } )
Note: You should also consider using Symbols for Hash keys since it is a common practice in Ruby, but my approach does also work with your keys.
z = [{'a' => 1, 'b' => 2}, {'a' => 3, 'b' => 4}, {'a' => 1, 'b' => 4}]
class Array
def search_hash(hash)
key = hash.keys.first
value = hash.values.first
select { |h| h[key] == value }
end
end
z.search_hash({'a' => 3}) #=> [{"a"=>3, "b"=>4}]
or you can type it without curly brackets
z.search_hash('a' => 3)
Basically what you need is something like this:
class Array
def find_by_hash(h)
h.collect_concat do |key, value|
self.select{|h| h[key] == value}
end
end
end
I didn't find an approach in API, so I think we have to implement it of our own.
(by the way, I think #megas' approach is better and more readable)
Code by TDD:
class SearchHashTest < Test::Unit::TestCase
def setup
#array_with_hash_elements = ArrayWithHashElements.new [{'a' => 1, 'b' => 2}, {'a' => 3, 'b' => 4}, {'a' => 1, 'b' => 4}]
end
def test_search_an_array_by_hash_parameter_and_return_single_hash
assert_equal( {'a' => 3, 'b' => 4}, #array_with_hash_elements.search({'a'=>3}) )
end
def test_search_an_array_by_hash_parameter_and_return_an_array
assert_equal( [{'a' => 1, 'b' => 2}, {'a'=> 1, 'b' => 4}], #array_with_hash_elements.search({'a'=>1}))
end
end
implemented code ( just for demo, not production)
class ArrayWithHashElements
def initialize some_array
#elements = some_array
end
def search( query_hash)
puts "search: #{query_hash.inspect}"
result = []
#elements.each do | array_element_in_hash_form|
query_hash.each_pair do | key, value |
if array_element_in_hash_form.has_key?(key) && array_element_in_hash_form[key] == value
puts "adding : #{array_element_in_hash_form.inspect} to result"
result << array_element_in_hash_form
end
end
result
end
return result.size != 1 ? result : result[0]
end
end
result:
sg552#siwei-moto:~/workspace/test$ ruby search_hash_test.rb
Loaded suite search_hash_test
Started
search: {"a"=>1}
adding : {"b"=>2, "a"=>1} to result
adding : {"b"=>4, "a"=>1} to result
.search: {"a"=>3}
adding : {"b"=>4, "a"=>3} to result
.
Finished in 0.000513 seconds.
2 tests, 2 assertions, 0 failures, 0 errors

Ruby merge hashes putting keys into csv string

Is there a clever way to achieve the following in Ruby?
hash1 = { "a" => 1, "b" => 2, "d" => 3}
hash2 = { "a" => 4, "b" => 5, "c" => 7}
hash3 = { "a" => 4, "d" => 7, "e" => 9}
puts hash1.csvMerge(hash2).csvMerge(hash3)
with the output being:
{ "a" => "1,4,4",
"b" => "2,5,0",
"c" => "0,7,0",
"d" => "3,0,7",
"e" => "0,0,9" }
What I'm trying to do is merge a bunch of Ruby hashes, in practice I have over a dozen, into a single hash where the values are combined into a comma separated string.
hash1.merge(hash2){|key, oldval, newval| [oldval,newval].join(",")}
=> {"a"=>"1,4", "b"=>"2,5", "d"=>3, "c"=>7}
hashes = [hash1, hash2, hash3]
keys = hashes.inject({}){|hh, h| hh = hh.merge(h); hh}.keys # the set of all keys #
default_hash = keys.inject({}){|d, k| d[k] = 0; d} # hash with value 0 for all keys #
complemented_hashes = hashes.map{|h| default_hash.merge(h)} # missing values filled in #
p Hash[complemented_hashes.map{|h| h.to_a}.flatten(1).group_by{|k, v| k}.
map{|k, v| [k, v.map{|k, v| v}.join(",")]}]
I dont know if this has performance issues
hashes = [hash1, hash2, hash3]
hash_with_all_keys = {}
hashes.each{|hash| hash_with_all_keys.merge!(hash)}
keys = hash_with_all_keys.keys
result_hash_mapping = keys.map do |key|
value = hashes.map{|hash| hash[key].to_i}.join(",")
[key, value]
end
result_hash = Hash[result_hash_mapping]
I could have replaced lines 2 to 4 with keys = hashes.inject({}){|merge_hash, hash| merge_hash.merge(hash)}.keys but I find it hard to read.
hash1 = { "a" => 1, "b" => 2, "d" => 3}
hash2 = { "a" => 4, "b" => 5, "c" => 7}
hash3 = { "a" => 4, "d" => 7, "e" => 9}
hashes = [hash1, hash2, hash3]
hashes.each{|h| h.each{|k,v| h[k] = v.to_s }}
res = hashes.inject{|m,h| m.merge(h){|k,old,new| "#{old},#{new}" }} #merge and glue
p res
#=> {"a"=>"1,4,4", "b"=>"2,5", "d"=>"3,7", "c"=>"7", "e"=>"9"}
Functional approach:
hashes = [hash1, hash2, hash3]
result = Hash[hashes.flat_map(&:keys).uniq.map do |key|
[key, hashes.map { |h| h[key] || 0 }.join(",")]
end]
#=> {"a"=>"1,4,4", "b"=>"2,5,0", "c"=>"0,7,0", "d"=>"3,0,7", "e"=>"0,0,9"}

Resources