Add element in the array of hash object - ruby

To a hash of arrays of hashes like
{'1' => [{'a' => 'ss', 'b' => 'tt'},
{'a' => 'sdd', 'b' => 'tdd'}],
'2' => [{'a' => 'ffff', 'b' => 'sds'}],
'3' => [{'a' => 'ddd', 'b' => 'ssss'},
{'a' => 'sss', 'b' => 'sssss'}]}
I want to add 'c' => 'xyz' in every hash in the arrays to get
{'1' => [{'a' => 'ss', 'b' => 'tt', 'c' => 'xyz'},
{'a' => 'sdd', 'b' => 'tdd', 'c' => 'xyz'}],
'2' => [{'a' => 'ffff', 'b' => 'sds', 'c' => 'xyz'}],
'3' => [{'a' => 'ddd', 'b' => 'ssss', 'c' => 'xyz'},
{'a' => 'sss', 'b' => 'sssss', 'c' => 'xyz'}]}
How I can I implement this?

my_arrays = {'1' => [{'a' => 'ss', 'b' => 'tt'},
{'a' => 'sdd', 'b' => 'tdd'}],
'2' => [{'a' => 'ffff', 'b' => 'sds'}],
'3' => [{'a' => 'ddd', 'b' => 'ssss'},
{'a' => 'sss', 'b' => 'sssss'}]}
my_arrays.each do |key, value|
value.each do |val|
val.store('c', 'xyz')
end
end

h = {'1' => [{'a' => 'ss', 'b' => 'tt'},
{'a' => 'sdd', 'b' => 'tdd'}],
'2' => [{'a' => 'ffff', 'b' => 'sds'}],
'3' => [{'a' => 'ddd', 'b' => 'ssss'},
{'a' => 'sss', 'b' => 'sssss'}]}
h.values.each do |ary|
ary.each do |inner_hash|
inner_hash['c'] = 'xyz'
end
end
Above solution is very similar to marmeladze's answer, so let me just explain the differences:
We don't need to do anything with the keys of the outer hash. So we can directly use the outer hash's values for iteration and not care about the keys at all.
inner_hash['c'] = 'xyz' uses element assigment syntax instead of calling #store. They do exactly the same.
In a comment to marmeladze's answer, you asked:
can we achieve this by iterating once instead of 2 each loops ?
h.values gives us an array of arrays. (Because it gives the array of values of h and the values of h are arrays.) We can collapse nested arrays with flatten and thus can achieve the same result with only one explicit iteration:
h.values.flatten.each do |inner_hash|
inner_hash['c'] = 'xyz'
end
or, (equivalently) if you prefer it all on one line
h.values.flatten.each { |ih| ih['c'] = 'xyz' }

Related

Getting data from an array of hashes in ruby

I get an array of hashes from a google sheet, it looks like this
itemList = [ {:id => '1', :type => 'A', :category => 'Cat1' },
{:id => '2', :type => 'A', :category => 'Cat1' },
{:id => '3', :type => 'B', :category => 'Cat1' },
{:id => '4', :type => 'B', :category => 'Cat1' },
{:id => '1', :type => 'A', :category => 'Cat2' },
{:id => '2', :type => 'A', :category => 'Cat2' },
{:id => '3', :type => 'C', :category => 'Cat2' } ]
I would like to be able to print this on the terminal
Cat1
A
1, 2
B
3, 4
Cat2
A
1, 2
C
3
Is there an easy way to do it?
Thank you
Following will provide you required format,
items = itemList.group_by {|x| x[:category] }
val = items.inject({}) do |m,(k,v)|
tmp = v.group_by { |x| x[:type] }
m[k] = tmp.update(tmp) { |i,j| j.map { |x| x[:id] } }
m
end
# => {"Cat1"=>{"A"=>["1", "2"], "B"=>["3", "4"]}, "Cat2"=>{"A"=>["1", "2"], "C"=>["3"]}}
Display it like below,
val.each { |k,v| puts k; v.each { |i,j| puts i; puts j.join(', ') }; puts }
Cat1
A
1, 2
B
3, 4
Cat2
A
1, 2
C
3

Ruby - Show Deltas Between 2 array of hashes based on subset of hash keys

I'm attempting to compare two arrays of hashes with very similar hash structure (identical and always-present keys) and return the deltas between the two--specifically, I'd like to capture the folllowing:
Hashes part of array1 that do not exist in array2
Hashes part of array2 that do not exist in array1
Hashes which appear in both data sets
This typically can be achieved by simply doing the following:
deltas_old_new = (array1-array2)
deltas_new_old = (array2-array1)
The problem for me (which has turned into a 2-3 hour struggle!) is that I need to identify the deltas based on the values of 3 keys within the hash ('id', 'ref', 'name')--the values of these 3 keys are effectively what makes up a unique entry in my data -- but I must retain the other key/value pairs of the hash (e.g. 'extra' and numerous other key/value pairs not shown for brevity.
Example Data:
array1 = [{'id' => '1', 'ref' => '1001', 'name' => 'CA', 'extra' => 'Not Sorted On 5'},
{'id' => '2', 'ref' => '1002', 'name' => 'NY', 'extra' => 'Not Sorted On 7'},
{'id' => '3', 'ref' => '1003', 'name' => 'WA', 'extra' => 'Not Sorted On 9'},
{'id' => '7', 'ref' => '1007', 'name' => 'OR', 'extra' => 'Not Sorted On 11'}]
array2 = [{'id' => '1', 'ref' => '1001', 'name' => 'CA', 'extra' => 'Not Sorted On 5'},
{'id' => '3', 'ref' => '1003', 'name' => 'WA', 'extra' => 'Not Sorted On 9'},
{'id' => '8', 'ref' => '1002', 'name' => 'NY', 'extra' => 'Not Sorted On 7'},
{'id' => '5', 'ref' => '1005', 'name' => 'MT', 'extra' => 'Not Sorted On 10'},
{'id' => '12', 'ref' => '1012', 'name' => 'TX', 'extra' => 'Not Sorted On 85'}]
Expected Outcome (3 separate array of hashes):
Object containing data in array1 but not in array2 --
[{'id' => '2', 'ref' => '1002', 'name' => 'NY', 'extra' => 'Not Sorted On 7'},
{'id' => '7', 'ref' => '1007', 'name' => 'OR', 'extra' => 'Not Sorted On 11'}]
Object containing data in array2 but not in array1 --
[{'id' => '8', 'ref' => '1002', 'name' => 'NY', 'extra' => 'Not Sorted On 7'},
{'id' => '5', 'ref' => '1005', 'name' => 'MT', 'extra' => 'Not Sorted On 10'},
{'id' => '12', 'ref' => '1012', 'name' => 'TX', 'extra' => 'Not Sorted On 85'}]
Object containing data in BOTH array1 and array2 --
[{'id' => '1', 'ref' => '1001', 'name' => 'CA', 'extra' => 'Not Sorted On 5'},
{'id' => '3', 'ref' => '1003', 'name' => 'WA', 'extra' => 'Not Sorted On 9'}]
I've tried numerous attempts at comparing iterating over the arrays and using Hash#keep_if based on the 3 keys as well as merging both data sets into a single array and then attempting to de-dup based on array1 but I keep coming up empty handed. Thank you in advance for your time and assistance!
For this type of problem it's generally easiest to work with indices.
Code
def keepers(array1, array2, keys)
a1 = make_hash(array1, keys)
a2 = make_hash(array2, keys)
common_keys_of_a1_and_a2 = a1.keys & a2.keys
[keeper_idx(array1, a1, common_keys_of_a1_and_a2),
keeper_idx(array2, a2, common_keys_of_a1_and_a2)]
end
def make_hash(arr, keys)
arr.each_with_index.with_object({}) do |(g,i),h|
(h[g.values_at(*keys)] ||= []) << i
end
end
def keeper_idx(arr, a, common_keys_of_a1_and_a2)
arr.size.times.to_a - a.values_at(*common_keys_of_a1_and_a2).flatten
end
Example
array1 =
[{'id' => '1', 'ref' => '1001', 'name' => 'CA', 'extra' => 'Not Sorted On 5'},
{'id' => '2', 'ref' => '1002', 'name' => 'NY', 'extra' => 'Not Sorted On 7'},
{'id' => '3', 'ref' => '1003', 'name' => 'WA', 'extra' => 'Not Sorted On 9'},
{'id' => '3', 'ref' => '1003', 'name' => 'WA', 'extra' => 'Not Sorted On 8'},
{'id' => '7', 'ref' => '1007', 'name' => 'OR', 'extra' => 'Not Sorted On 11'}]
array2 =
[{'id' => '1', 'ref' => '1001', 'name' => 'CA', 'extra' => 'Not Sorted On 5'},
{'id' => '3', 'ref' => '1003', 'name' => 'WA', 'extra' => 'Not Sorted On 9'},
{'id' => '8', 'ref' => '1002', 'name' => 'NY', 'extra' => 'Not Sorted On 7'},
{'id' => '5', 'ref' => '1005', 'name' => 'MT', 'extra' => 'Not Sorted On 10'},
{'id' => '5', 'ref' => '1005', 'name' => 'MT', 'extra' => 'Not Sorted On 12'},
{'id' => '12', 'ref' => '1012', 'name' => 'TX', 'extra' => 'Not Sorted On 85'}]
Notice that the two arrays are slightly different than those given in the question. The question did not specify whether each array could contain two hashes the have the same values for the specified keys. I therefore added a hash to each array to show has that case is dealt with.
keys = ['id', 'ref', 'name']
idx1, idx2 = keepers(array1, array2, keys)
#=> [[1, 4], [2, 3, 4, 5]]
idx1 (idx2) are the indices of the elements of array1 (array2) that remain after matches are removed. (array1 and array2 are not modified, however.)
It follows that the two arrays map to
array1.values_at(*idx1)
#=> [{"id"=> "2", "ref"=>"1002", "name"=>"NY", "extra"=>"Not Sorted On 7"},
# {"id"=> "7", "ref"=>"1007", "name"=>"OR", "extra"=>"Not Sorted On 11"}]
and
array2.values_at(*idx2)
#=> [{"id"=> "8", "ref"=>"1002", "name"=>"NY", "extra"=>"Not Sorted On 7"},
# {"id"=> "5", "ref"=>"1005", "name"=>"MT", "extra"=>"Not Sorted On 10"},
# {"id"=> "5", "ref"=>"1005", "name"=>"MT", "extra"=>"Not Sorted On 12"},
# {"id"=>"12", "ref"=>"1012", "name"=>"TX", "extra"=>"Not Sorted On 85"}]
The indices of the hashes that are removed are given as follows.
array1.size.times.to_a - idx1
#=> [0, 2, 3]
array2.size.times.to_a - idx2
#[0, 1]
Explanation
The steps are as follows.
a1 = make_hash(array1, keys)
#=> {["1", "1001", "CA"]=>[0], ["2", "1002", "NY"]=>[1],
# ["3", "1003", "WA"]=>[2, 3], ["7", "1007", "OR"]=>[4]}
a2 = make_hash(array2, keys)
#=> {["1", "1001", "CA"]=>[0], ["3", "1003", "WA"]=>[1],
# ["8", "1002", "NY"]=>[2], ["5", "1005", "MT"]=>[3, 4],
# ["12", "1012", "TX"]=>[5]}
common_keys_of_a1_and_a2 = a1.keys & a2.keys
#=> [["1", "1001", "CA"], ["3", "1003", "WA"]]
keeper_idx(array1, a1, common_keys_of_a1_and_a2)
#=> [1, 4] (for array1)
keeper_idx(array2, a2, common_keys_of_a1_and_a2)
#=> [2, 3, 4, 5]ยท (for array2)
This isn't very pretty, but it works. It creates a third array containing all unique values in both array1 and array2 and iterates through that.
Then, since include? doesn't allow a custom matcher, we can fake it by using detect and looking for an item in the array which has the custom sub-hash matching. We'll wrap that in a custom method so we can just call it passing in array1 or array2 instead of writing it twice.
Finally, we loop through our array3 and determine whether the item came from array1, array2, or both of them and add to the corresponding output array.
array1 = [{'id' => '1', 'ref' => '1001', 'name' => 'CA', 'extra' => 'Not Sorted On 5'},
{'id' => '2', 'ref' => '1002', 'name' => 'NY', 'extra' => 'Not Sorted On 7'},
{'id' => '3', 'ref' => '1003', 'name' => 'WA', 'extra' => 'Not Sorted On 9'},
{'id' => '7', 'ref' => '1007', 'name' => 'OR', 'extra' => 'Not Sorted On 11'}]
array2 = [{'id' => '1', 'ref' => '1001', 'name' => 'CA', 'extra' => 'Not Sorted On 5'},
{'id' => '3', 'ref' => '1003', 'name' => 'WA', 'extra' => 'Not Sorted On 9'},
{'id' => '8', 'ref' => '1002', 'name' => 'NY', 'extra' => 'Not Sorted On 7'},
{'id' => '5', 'ref' => '1005', 'name' => 'MT', 'extra' => 'Not Sorted On 10'},
{'id' => '12', 'ref' => '1012', 'name' => 'TX', 'extra' => 'Not Sorted On 85'}]
# combine the arrays into 1 array that contains items in both array1 and array2 to loop through
array3 = (array1 + array2).uniq { |item| { 'id' => item['id'], 'ref' => item['ref'], 'name' => item['name'] } }
# Array#include? doesn't allow a custom matcher, so we can fake it by using Array#detect
def is_included_in(array, object)
object_identifier = { 'id' => object['id'], 'ref' => object['ref'], 'name' => object['name'] }
array.detect do |item|
{ 'id' => item['id'], 'ref' => item['ref'], 'name' => item['name'] } == object_identifier
end
end
# output array initializing
array1_only = []
array2_only = []
array1_and_array2 = []
# loop through all items in both array1 and array2 and check if it was in array1 or array2
# if it was in both, add to array1_and_array2, otherwise, add it to the output array that
# corresponds to the input array
array3.each do |item|
in_array1 = is_included_in(array1, item)
in_array2 = is_included_in(array2, item)
if in_array1 && in_array2
array1_and_array2.push item
elsif in_array1
array1_only.push item
else
array2_only.push item
end
end
puts array1_only.inspect # => [{"id"=>"2", "ref"=>"1002", "name"=>"NY", "extra"=>"Not Sorted On 7"}, {"id"=>"7", "ref"=>"1007", "name"=>"OR", "extra"=>"Not Sorted On 11"}]
puts array2_only.inspect # => [{"id"=>"8", "ref"=>"1002", "name"=>"NY", "extra"=>"Not Sorted On 7"}, {"id"=>"5", "ref"=>"1005", "name"=>"MT", "extra"=>"Not Sorted On 10"}, {"id"=>"12", "ref"=>"1012", "name"=>"TX", "extra"=>"Not Sorted On 85"}]
puts array1_and_array2.inspect # => [{"id"=>"1", "ref"=>"1001", "name"=>"CA", "extra"=>"Not Sorted On 5"}, {"id"=>"3", "ref"=>"1003", "name"=>"WA", "extra"=>"Not Sorted On 9"}]
See Array#- and Array#&
array1 - array2 #data in array1 but not in array2
array2 - array1 #data in array2 but not in array1
array1 & array2 #data in both array1 and array2
Since you've tagged this question set you can use sets similarly:
require 'set'
set1 = array1.to_set
set2 = array2.to_set
set1 - set2
set2 - set1
set1 & set2

array x array matrix in ruby

I'd like to convert from
{'key1' => (1..10) ,
'key2' => (11..20) ,
'key3' => (21..30)}
to
[{'key1' => 1, 'key2' => 11, 'key3' => 21},
{'key1' => 1, 'key2' => 11, 'key3' => 22},...
.
.
{'key1' => 10, 'key2' => 20, 'key3' => 30}]
How to solve it?
Here it is :
hsh = {'key1' => (1..10) ,'key2' => (11..20) , 'key3' => (21..30)}
keys = hsh.keys
hsh['key1'].to_a.product(hsh['key2'].to_a,hsh['key3'].to_a).map{|a|Hash[keys.zip(a)]}
# => [{'key1' => 1, 'key2' => 11, 'key3' => 21},
# {'key1' => 1, 'key2' => 11, 'key3' => 22},...
# .
# .
# {'key1' => 10, 'key2' => 20, 'key3' => 30}]
You could also write the above as below,when you have more number of keys:
hsh = {'key1' => (1..10) ,'key2' => (11..20) , 'key3' => (21..30)}
keys = hsh.keys
array = hsh.values_at(*keys[1..-1]).map(&:to_a)
hsh['key1'].to_a.product(*array).map{|a|Hash[keys.zip(a)]}
So many ways... A kiss answer (edited to extend to any number of keys):
s = {'key1' => (1..10), 'key2' => (11..20), 'key3' => (21..30)}
r = []
s.each {|k,v| a = []; (v.to_a).each {|i| a << {k=>i}}; r << a}
result = r.shift
r.each {|e| result = result.product(e).map(&:flatten)}
result
h = {
'key1' => (1..10),
'key2' => (11..20),
'key3' => (21..30)
}
h.map { |k,v| [k].product(v.to_a) }.transpose.map { |e| Hash[e] }
#=> [{"key1"=>1, "key2"=>11, "key3"=>21},
# {"key1"=>2, "key2"=>12, "key3"=>22},
# {"key1"=>3, "key2"=>13, "key3"=>23},
# {"key1"=>4, "key2"=>14, "key3"=>24},
# {"key1"=>5, "key2"=>15, "key3"=>25},
# {"key1"=>6, "key2"=>16, "key3"=>26},
# {"key1"=>7, "key2"=>17, "key3"=>27},
# {"key1"=>8, "key2"=>18, "key3"=>28},
# {"key1"=>9, "key2"=>19, "key3"=>29},
# {"key1"=>10, "key2"=>20, "key3"=>30}]
h = {'key1' => (1..10) ,
'key2' => (11..20) ,
'key3' => (21..30)}
arrays = h.values.map(&:to_a).transpose
p arrays.map{|ar| Hash[h.keys.zip(ar)] }
#=> [{"key1"=>1, "key2"=>11, "key3"=>21}, {"key1"=>2, "key2"=>12, "key3"=>22},...
h = {'key1' => (1..10), 'key2' => (11..20), 'key3' => (21..30)}
Edit 1: made some changes, principally the use of inject({}):
f,*r = h.map {|k,v| [k].product(v.to_a)}
f.zip(*r).map {|e| e.inject({}) {|h,a| h[a.first] = a.last; h}}
Edit 2: After seeing the use of Hash[] in #Phrogz's answer to another question:
f,*r = h.map {|k,v| [k].product(v.to_a)}
f.zip(*r).map {|e| Hash[*e.flatten]}
Lazier way of doing the same:
h = {
'key1' => (1..10),
'key2' => (11..20),
'key3' => (21..30)
}
result = ( 0...h.values.map( &:to_a ).map( &:size ).max ).map do |i|
Hash.new { |hsh, k| hsh[k] = h[k].to_a[i] }
end
result[1]['key3'] #=> 22

Ruby loops. Hash into string

I am working with Ruby. I need to grab each key/value and put it into a string.
So far I have:
values = ['first' => '1', 'second' => '2']
#thelink = values.collect do | key, value |
"#{key}=#{value}&"
end
When I print #thelink I see:
first1second2=&
But Really what I want is
first=1&second=2
Could anybody help/explain please?
There is something subtle you are missing here {} vs [].
See the below taken from IRB tests:
irb(main):002:0> {'first' => 1, 'second' => 2}
=> {"second"=>2, "first"=>1}
irb(main):003:0> ['first' => 1, 'second' => 2]
=> [{"second"=>2, "first"=>1}]
irb(main):004:0> {'first' => 1, 'second' => 2}.class
=> Hash
irb(main):005:0> ['first' => 1, 'second' => 2].class
=> Array
Similar to this:
irb(main):006:0> {'first' => 1, 'second' => 2}.collect { |key,value| puts "#{key}:#{value}" }
second:2
first:1
=> [nil, nil]
irb(main):007:0> ['first' => 1, 'second' => 2].collect { |key,value| puts "#{key}:#{value}" }
second2first1:
=> [nil]
The array has a single element (a hash) that, as a string, is everything concatenated. This is the important thing to note here.
On the other hand, the hash iterates by handing you the key/value pairs that you are expecting.
Hope that helps.
I think your code has a typo (a hash is delimited by {} not by []). Try this
values = {'first' => '1', 'second' => '2'}
r = values.map{|k,v| "#{k}=#{v}"}.join('&')
puts r
#shows: first=1&second=2

hash containing an array to array of hashes in ruby

I've read through quite a few of posts, but none seem to do just this, which is a bit tricky.
Say I have a hash that contains an array as one of its values.
hash = {
:a => 'one',
:arr => [
{:id => 'ten', :amount => 10, :b => 'two'},
{:id => 'twenty', :amount => 20, :b => 'two'},
{:id => 'apple', :amount => 7, :b => 'applesauce'}
],
:c => 3
}
I want to convert this to an array of hashes (which would be of the size of the contained array), as follows:
# => [
{:a => 'one', :id => 'ten', :amount => 10, :b => 'two', :c => 3},
{:a => 'one', :id => 'twenty', :amount => 20, :b => 'two', :c => 3},
{:a => 'one', :id => 'apple', :amount => 7, :b => 'applesauce', :c => 3}
]
The conversion should maintain whatever key/value pairs are inside and outside the array, and ideally I could pass in the key of the array to ask it perform the action:
flatten_hash_array(hash, :arr)
I realize that the Ruby flatten in the Array class is not what we need. Grasping for a verb! Any help would be appreciated.
This should do the job, barring validity checks.
def flatten_hash_array(hash, key)
hash[key].map {|entry| entry.merge(hash.reject {|k| k == key})}
end

Resources