Ruby - Compare/merge 2 array of hashes based on 1 key - ruby

Lets say I have 2 array of hashes as follows,
local_todos = [{name: "abc", title: "abcdwer", api_id: "1234567", updated_at: "2013-22-12"},
{name: "abcd", title: "abcdwe", api_id: "098098", updated_at: "2013-22-11"},
{name: "abcde", title: "abcdqw", api_id: "345345", updated_at: "2013-22-18"},
{name: "abcdef", title: "abcder", api_id: "234456", updated_at: "2013-22-15"}]
google_tasks = [{name: "abc", title: "xxxxx", id: "1234567", updated: "2013-22-19"},
{name: "abcd", title: "zzzzz", id: "098098", updated: "2013-22-15"},
{name: "abcde", title: "abcdqw", id: "345345", updated: "2013-22-18"},
{name: "abcdef", title: "abcder", id: "234456", updated: "2013-22-15"}]
Now I want to, merge/compare/filter these 2 hashes purely based on api_id(local_todos) and id(google_tasks), so that only the id/api_id(both are the same value) that has a difference in the updated_at(local_todos) and updated(google_tasks) value is printed as the output.
Desired output will be like this,
["1234567", "098098"]
Because if you check those 2 ids has a different updated/updated_at values.
Any help?

Just simple selection:
local_todos.map do | v1 |
google_tasks.any? {|v2| v1[ :api_id ] == v2[ :id ] && v1[ :updated_at ] != v2[ :updated ] } && v1[ :api_id ] || nil
end.compact
# => ["1234567", "098098"]
To compare the code you are able to use the following code:
funcs =
[ proc { local_todos.map {|v1| google_tasks.any? {|v2| v1[ :api_id ] == v2[ :id ] && v1[ :updated_at ] != v2[ :updated ] } && v1[ :api_id ] || nil }.compact },
proc {
local_todos.inject([]) do |result,l_td|
if found = google_tasks.detect {|g_td| g_td[:id] == l_td[:api_id] and g_td[:updated] != l_td[:updated_at]}
result << found[:id]
end
result
end
},
]
def ctime func
time = 0
1000.times { time += Benchmark.measure { 1000.times { func.call } }.to_a[5].to_f }
rtime = time /= 1000000
end
funcs.each {| func | p ctime( func ) }
# 3.9753190517425536e-05
# 4.056975722312927e-05
In my bench results the first code is slight faster.

local_todos.inject([]) do |result,l_td|
if found = google_tasks.detect {|g_td| g_td[:id] == l_td[:api_id] and g_td[:updated] != l_td[:updated_at]}
result << found[:id]
end
result
end

local_todos.collect do |l_todo|
google_tasks.collect do |g_task|
l_todo[:api_id] if (l_todo[:api_id] == g_task[:id] && l_todo[:updated_at] != g_task[:updated])
end.compact
end.flatten

Related

Ruby; How can I update the nested hashes in this one?

Sorry, it's a little bit too long. I need to change the status of :hero and :heroine to "dead"
def update_status
epic_tragedy = {
:montague => {
:patriarch => {name: "Lord Montague", age: "53"},
:matriarch => {name: "Lady Montague", age: "54"},
:hero => {name: "Romeo", age: "15", status: "alive"},
:hero_friends => [
{name: "Benvolio", age: "17", attitude: "worried"},
{name: "Mercutio", age: "18", attitude: "hot-headed"}
]
},
:capulet => {
:patriarch => {name: "Lord Capulet", age: "50"},
:matriarch => {name: "Lady Capulet", age: "51"},
:heroine => {name: "Juliet", age: "15", status: "alive"},
:heroine_friends => [
{name: "Steven", age: "30", attitude: "confused"},
{name: "Nurse", age: "44", attitude: "worried"}
]
}
}
I did add the code below inside the method but it shows error.
epic_tragedy[:montague][:hero][:status] = "dead"
epic_tragedy[:capulet][:hero][:status] = "dead"
Is there any additional step before I put those lines?
epic_tragedy is a local variable within the method definition. Such local variables cannot be accessed from outside the method.
However, the hash is returned when the method is executed, so try
h = update_status
#=> {:montague=>{
# :patriarch=>{:name=>"Lord Montague", :age=>"53"},
# :matriarch=>{:name=>"Lady Montague", :age=>"54"},
# :hero=>{:name=>"Romeo", :age=>"15", :status=>"alive"},
# :hero_friends=>[
# {:name=>"Benvolio", :age=>"17", :attitude=>"worried"},
# {:name=>"Mercutio", :age=>"18", :attitude=>"hot-headed"}
# ]
# },
# :capulet=>{
# :patriarch=>{:name=>"Lord Capulet", :age=>"50"},
# :matriarch=>{:name=>"Lady Capulet", :age=>"51"},
# :heroine=>{:name=>"Juliet", :age=>"15", :status=>"alive"},
# :heroine_friends=>[
# {:name=>"Steven", :age=>"30", :attitude=>"confused"},
# {:name=>"Nurse", :age=>"44", :attitude=>"worried"}
# ]
# }
# }
h[:montague][:hero][:status] = "dead"
h[:capulet][:heroine][:status] = "dead"
h #=> {:montague=>{
# :patriarch=>{:name=>"Lord Montague", :age=>"53"},
# :matriarch=>{:name=>"Lady Montague", :age=>"54"},
# :hero=>{:name=>"Romeo", :age=>"15", :status=>"dead"},
# :hero_friends=>[
# {:name=>"Benvolio", :age=>"17", :attitude=>"worried"},
# {:name=>"Mercutio", :age=>"18", :attitude=>"hot-headed"}
# ]
# },
# :capulet=>{
# :patriarch=>{:name=>"Lord Capulet", :age=>"50"},
# :matriarch=>{:name=>"Lady Capulet", :age=>"51"},
# :heroine=>{:name=>"Juliet", :age=>"15", :status=>"dead"},
# :heroine_friends=>[
# {:name=>"Steven", :age=>"30", :attitude=>"confused"},
# {:name=>"Nurse", :age=>"44", :attitude=>"worried"}
# ]
# }
# }

Transform array of nested Hashes into flat array of non-nested hashes

I want to transform the given array into result array:
given = [{
"foo_v1_4" => [{
"derivate_version" => 0,
"layers" => {
"tlayer" => {
"baz" => {
"three" => 0.65
},
"bazbar" => {
"three" => 0.65
}
}
}
}]
}]
# the value of key :one is first hash key (foo_v1_4) plus underscore (_) plus derivate_version (0)
result = [{
one: 'foo_v1_4_0',
tlayer: 'baz',
three: '0.6'
},
{
one: 'foo_v1_4_0',
tlayer: 'bazbar',
three: '0.6'
}
]
What I tried:
given.each do |el |
el.each do |derivat |
derivat.each do |d |
d.each do |layer |
layer.each do |l |
derivat = "#{d}_#{l['derivate_version']}"
puts derivat
end
end
end
end
end
I'm struggling at iterating through "layers" hash, the amount of elements in layers is equal to the amount of elements in result array.
It helps to format the objects so we can better see their structures:
given = [
{
"foo_v1_4" => [
{ "derivate_version" => 0,
"layers" => {
"tlayer" => {
"baz" => { "three" => 0.65 },
"bazbar" => { "three" => 0.65 }
}
}
}
]
}
]
result = [
{
one: 'foo_v1_4_0',
tlayer: 'baz',
three: '0.6'
},
{
one: 'foo_v1_4_0',
tlayer: 'bazbar',
three: '0.6'
}
]
We can begin by writing the structure of result:
result = [
{
one:
tlayer:
three:
},
{
one:
tlayer:
three:
}
]
We see that
given = [ { "foo_v1_4" => <array> } ]
The values of the keys :one in the hash result[0] is therefore the first key of the first element of given:
one_val = given[0].keys[0]
#=> "foo_v1_4"
result = [
{
one: one_val
tlayer:
three:
},
{
one: one_val
tlayer:
three:
}
]
All the remaining objects of interest are contained in the hash
h = given[0]["foo_v1_4"][0]["layers"]["layer"]
#=> {
# "baz"=>{ "three"=>0.65 },
# "bazbar"=>{ "three"=>0.65 }
# }
so it is convenient to define it. We see that:
h.keys[0]
#=> "baz"
h.keys[1]
#=> "bazaar"
h["bazbar"]["three"]
#=> 0.65
Note that it generally is not good practice to assume that hash keys are ordered in a particular way.
We may now complete the construction of result,
v = h["bazbar"]["three"].truncate(1)
#=> 0.6
result = [
{
one: one_val,
tlayer: h.keys[0],
three: v
},
{ one: one_val,
tlayer: h.keys[1],
three: v
}
]
#=> [
# { :one=>"foo_v1_4", :tlayer=>"baz", :three=>0.6 },
# { :one=>"foo_v1_4", :tlayer=>"bazbar", :three=>0.6 }
# ]
The creation of the temporary objects one_val, h, and v improves time- and space-efficiency, makes the calculations easier to test and improves the readability of the code.
Try the below:
result = []
given.each do |level1|
level1.each do |key, derivate_versions|
derivate_versions.each do |layers|
# iterate over the elements under tlayer
layers.dig('layers', 'tlayer').each do |tlayer_key, tlayer_value|
sub_result = {}
# key - foo_v1_4, layers['derivate_version'] - 0 => 'foo_v1_4_0'
sub_result[:one] = key + '_' + layers['derivate_version'].to_s
# talyer_key - baz, barbaz
sub_result[:tlayer] = tlayer_key
# talyer_value - { "three" => 0.65 }
sub_result[:three] = tlayer_value['three']
result << sub_result
end
end
end
end
The value of result will be:
2.6.3 :084 > p result
[{:one=>"foo_v1_4_0", :tlayer=>"baz", :three=>0.65}, {:one=>"foo_v1_4_0", :tlayer=>"bazbar", :three=>0.65}]

How to limit an array of similar hashes to those that have more than one of the same key:value pair (details inside)

I have an array like this
arr = [ { name: "Josh", grade: 90 }, {name: "Josh", grade: 70 },
{ name: "Kevin", grade: 100 }, { name: "Kevin", grade: 95 },
{ name: "Ben", grade: 90 }, { name: "Rod", grade: 90 },
{ name: "Rod", grade: 70 }, { name: "Jack", grade: 60 } ]
I would like Ben and Jack to be removed since they only have one record in this array. What would be the most elegant way to get this done? I could manually go through it and check, but is there a better way? Like the opposite of
arr.uniq! { |person| person[:name] }
arr.reject! { |x| arr.count { |y| y[:name] == x[:name] } == 1 }
An O(n) solution:
count_hash = {}
arr.each { |x| count_hash[x[:name]] ||= 0; count_hash[x[:name]] += 1 }
arr.reject! { |x| count_hash[x[:name]] == 1 }
Here are three more ways that might be of some interest, though I prefer Robert's solution.
Each of the following returns:
#=> [{:name=>"Josh" , :grade=> 90}, {:name=>"Josh" , :grade=>70},
# {:name=>"Kevin", :grade=>100}, {:name=>"Kevin", :grade=>95},
# {:name=>"Rod" , :grade=> 90}, {:name=>"Rod" , :grade=>70}]
#1
Use the well-worn but dependable Enumerable#group_by to aggregate by name, Hash#values to extract the values then reject those that appear but once:
arr.group_by { |h| h[:name] }.values.reject { |a| a.size == 1 }.flatten
#2
Use the class method Hash#new with a default of zero to identify names with multiple entries, then select for those:
multiples = arr.each_with_object(Hash.new(0)) { |h,g| g[h[:name]] += 1 }
.reject { |_,v| v == 1 } #=> {"Josh"=>2, "Kevin"=>2, "Rod"=>2}
arr.select { |h| multiples.key?(h[:name]) }
#3
Use the form of Hash#update (aka Hash#merge!) that takes a block to determine names that appear only once, then reject for those:
singles = arr.each_with_object({}) { |h,g|
g.update({ h[:name] => 1 }) { |_,o,n| o+n } }
.select { |_,v| v == 1 } #=> {"Ben"=>1, "Jack"=>1}
arr.reject { |h| singles.key?(h[:name]) }

Check if any of array hash objects has needed value

I have an array like this:
arr = [{id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
I need to check if any of arr objects have name Sam. What is the most elegant way? I can only think of cycling with each.
I need to check if any of arr objects have name Sam
Enumerable#any? is a good way to go.
arr = [ {id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
arr.any? {|h| h[:name] == "Sam"}
# => true
Now if you also want to see which Array object has the value Sam in it,you can use Enumerable#find for the same:
arr.find {|h| h[:name] == "Sam"}
# => {:id=>2, :name=>"Sam"}
You can also choose select or count methods
Enumberable#select
> arr = [{id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
> arr.select { | h | h[:name] == 'Sam' }
# => [{:id=>2, :name=>"Sam"}]
Enumberable#count
> arr.count { | h | h[:name] == 'Sam' }
# => 1
You can use Enumberable#find_all to return all object that match the constrain
arr = [{:id=>1,:first_name=>'sam'},{:id=>2,:first_name=>'sam'},{:id=>3,:first_name=>'samanderson'},{:id=>4,:first_name=>'samuel'}]
arr.find_all{|obj| obj.first_name == 'sam'}
# => [{:id=>1,:first_name=>'sam'},{:id=>2,:first_name=>'sam'}]

What is an eloquent way to sort an array of hashes based on whether a key is empty in Ruby?

array = [{ name:'Joe', foo:'bar' },
{ name:'Bob', foo:'' },
{ name:'Hal', foo:'baz' }
]
What is an eloquent way to sort so that if foo is empty, then put it at the end, and not change the order of the other elements?
Ruby 1.9.3
array.partition { |h| !h[:foo].empty? }.flatten
array.find_all{|elem| !elem[:foo].empty?} + array.find_all{|elem| elem[:foo].empty?}
returns
[{:name=>"Joe", :foo=>"bar"}, {:name=>"Hal", :foo=>"baz"}, {:name=>"Bob", :foo=>""}]
array = [
{ name:'Joe', foo:'bar' },
{ name:'Bob', foo:'' },
{ name:'Hal', foo:'baz' }
]
arraydup = array.dup
array.delete_if{ |h| h[:foo].empty? }
array += (arraydup - array)
Which results in:
[
[0] {
:name => "Joe",
:foo => "bar"
},
[1] {
:name => "Hal",
:foo => "baz"
},
[2] {
:name => "Bob",
:foo => ""
}
]
With a little refactoring:
array += ((array.dup) - array.delete_if{ |h| h[:foo].empty? })
One can produce keys as tuples, where the first part indicates null/not-null, and the second part is the original index, then sort_by [nulls_last, original_index].
def sort_nulls_last_preserving_original_order array
array.map.with_index.
sort_by { |h,i| [ (h[:foo].empty? ? 1 : 0), i ] }.
map(&:first)
end
Note this avoids all the gross array mutation of some of the other answers and is constructed from pure functional transforms.
array.each_with_index do |item, index|
array << (array.delete_at(index)) if item[:foo].blank?
end
Use whatever you have in place of blank?.

Resources