Ruby: This "Double dimensional hash" requires processing - ruby

I have this:
h = { 1 => { 1 => {:a => "x", :b => "y", :c => "z"},
2 => {:a => "xx", :b => "yy", :c => "zz"}
},
2 => { 1 => {:a => "p", :b => "q", :c => "r"},
2 => {:a => "pp", :b => "qq", :c => "rr"}
}
}
I want to get this:
result = { 1 => { 1 => {:a => "x"},
2 => {:a => "xx"}
},
2 => { 1 => {:a => "p"},
2 => {:a => "pp"}
}
}
What would be a nice way of doing this ?

A single example can't really define your structure. For example, are hashes always 3 levels deep with hashes to be pruned at the level 3?
You can start with:
h.each{|k1,v1| v1.each{|k2, v2| v2.delete_if{|k3,v3| k3 != :a}}}

(Should really be a comment, but the code's hard to read that way)
If you're removing from the innermost hash all keys except :a, why not assign the value part of that hash directly to the hash that contains it?
result = {
1 => { 1 => "x", 2 => "xx"},
2 => { 1 => "p", 2 => "pp"}
}

Related

How to merge hash of arrays?

I have an array:
[
{a => 1, b => { c => 1, d => 1}},
{a => 1, b => { c => 1, d => 2}},
{a => 1, b => { c => 2, d => 2}},
{a => 2, b => { c => 1, d => 1}},
]
I want to change it to this:
[
{a => 1, b => [{ c => 1, d => [1, 2]}, { c => 2, d => [2]}]},
{a => 2, b=> [ { c=> 1, d => [1] } ]}
]
Rules/Requirements:
Hashes of same value of a go to one hash
b should be an array of {c => , d =>}
d should be an array
d with same value of c go to same array
Here is a solution. It is very explicit, so it would not generalize to other hash structures.
hashes = [
{:a => 1, :b => { :c => 1, :d => 1}},
{:a => 1, :b => { :c => 1, :d => 2}},
{:a => 1, :b => { :c => 2, :d => 2}},
{:a => 2, :b => { :c => 1, :d => 1}},
]
a_values = {}
hashes.each do |hash|
a_value = hash[:a]
a_values[a_value] ||= {}
c_value = hash[:b][:c]
a_values[a_value][c_value] ||= { :c => c_value, :d => [] }
d_value = hash[:b][:d]
a_values[a_value][c_value][:d].push(d_value)
end
# Now aggregate the results
results = a_values.map do |a_value, c_hashes|
b_arr = c_hashes.map { |c_value, c_hash| c_hash }
{ :a => a_value, :b => b_arr }
end
And here is the output:
[
{:a=>1, :b=>[{:c=>1, :d=>[1, 2]}, {:c=>2, :d=>[2]}]},
{:a=>2, :b=>[{:c=>1, :d=>[1]}]}
]

Add element to a nested hash in ruby

I have a hash:
a = { 21 => { 3 => {:x => 5, :y => 6}}}
I want to add another value to the key '21' so that the hash looks like this:
a = { 21 => { 3 => {:x => 5, :y => 6}, 4 => {:x => 8, :y => 7}}}
How can I do that?
You want to add an key-value pair to a hash (a[21]). a[21] will give you the inner hash object.
a = { 21 => { 3 => {:x => 5, :y => 6}}}
a[21]
# => {3=>{:x=>5, :y=>6}}
Associating key, values to the inner hash will solve your problem.
a[21][4] = {:x => 8, :y => 7}
a
# => {21=>{3=>{:x=>5, :y=>6}, 4=>{:x=>8, :y=>7}}}
Another way is:
a[21].update({ 4=>{:x => 8, :y => 7} })
a #=> {21=>{3=>{:x=>5, :y=>6}, 4=>{:x=>8, :y=>7}}}

How to get the position of a Key/value pair inside a hash?

Can the below code can be shortened ?
h = { 1 => "a", 2 => "b", 3 => "c" }
h.to_a.index([2,'b']) # => 1
I didn't find anything in the Hash API.
Use Enumerable#find_index as below :
h = { 1 => "a", 2 => "b", 3 => "c" }
h.find_index([2,'b']) # => 1

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

Resources