How effectively join ruby hashes recieved from json lists - ruby

I'm trying to make page with table which content is data from two arrays.
I have two lists(arrays) with hashes:
arr1 = [
{ "device"=>100, "phone"=>"12345" },
...,
{ "device"=>102, "phone"=>"12346" }
]
arr2 = [
{ "device"=>100, "type"=>"mobile", "name"=>"nokia" },
...,
{ "device"=>102, "type"=>"VIOP", "name"=>"smth" }
]
How can I join hashes from arr1 and arr2 by "device" to get a result array:
result = [
{ "device"=>100, "phone"=>"12345", "type"=>"mobile", "name"=>"nokia" },
...,
{ "device"=>102, "phone"=>"12346", "type"=>"VIOP", "name"=>"smth" }
]
Page which consist table with result array, loads very slowly and I need to find the fastest way to generate result_array.
Help me please.

This would work:
(arr1 + arr2).group_by { |i| i["device"] }.map { |d,(i1,i2)| i1.merge(i2)}
#=> [{"device"=>100, "phone"=>"12345", "type"=>"mobile", "name"=>"nokia"}, {"device"=>102, "phone"=>"12346", "type"=>"VIOP", "name"=>"smth"}]

Multiple ways to tackle it. Here is a quite readable way to do it:
# prepare an index hash for easier access of data by device
first_by_device = arr1.group_by {|a| a['device'] }
# build a new array joining both data hashes for each item
result = arr2.map do |item|
device = item['device']
item.merge first_by_device(device)
end

(arr1 + arr2).group_by { |i| i["device"] }.values.map{|x|x.reduce(&:merge)}

Maybe it's not the prettiest one, but it works:
result = arr1.collect{|a| h = arr2.select{|b| b["device"] ==
a["device"]}.first; h ? a.merge(h) : a }
Do you need something faster for large amount of data?
h = Hash.new
arr1.each do |a|
h[ a["device" ] ] ||= Hash.new
h[ a["device" ] ].merge!(a)
end
arr2.each do |a|
h[ a["device" ] ] ||= Hash.new
h[ a["device" ] ].merge!(a)
end
result = h.values

Related

Serialize an array of hashes

I have an array of hashes:
records = [
{
ID: 'BOATY',
Name: 'McBoatface, Boaty'
},
{
ID: 'TRAINY',
Name: 'McTrainface, Trainy'
}
]
I'm trying to combine them into an array of strings:
["ID,BOATY","Name,McBoatface, Boaty","ID,TRAINY","Name,McTrainface, Trainy"]
This doesn't seem to do anything:
irb> records.collect{|r| r.each{|k,v| "\"#{k},#{v}\"" }}
#=> [{:ID=>"BOATY", :Name=>"McBoatface, Boaty"}, {:ID=>"TRAINY", :Name=>"McTrainface, Trainy"}]
** edit **
Formatting (i.e. ["Key0,Value0","Key1,Value1",...] is required to match a vendor's interface.
** /edit **
What am I missing?
records.flat_map(&:to_a).map { |a| a.join(',') }
#=> ["ID,BOATY", "Name,McBoatface, Boaty", "ID,TRAINY", "Name,McTrainface, Trainy"]
records = [
{
ID: 'BOATY',
Name: 'McBoatface, Boaty'
},
{
ID: 'TRAINY',
Name: 'McTrainface, Trainy'
}
]
# strait forward code
result= []
records.each do |hash|
hash.each do |key, value|
result<< key.to_s
result<< value
end
end
puts result.inspect
# a rubyish way (probably less efficient, I've not done the benchmark)
puts records.map(&:to_a).flatten.map(&:to_s).inspect
Hope it helps.
li = []
records.each do |rec|
rec.each do |k,v|
li << "#{k.to_s},#{v.to_s}".to_s
end
end
print li
["ID,BOATY", "Name,McBoatface, Boaty", "ID,TRAINY", "Name,McTrainface,
Trainy"]
You sure you wanna do it this way?
Check out Marshal. Or JSON.
You could even do it this stupid way using Hash#inspect and eval:
serialized_hashes = records.map(&:inspect) # ["{ID: 'Boaty'...", ...]
unserialized = serialized_hashes.map { |s| eval(s) }

Merge array of hashes by some keys and sum values of other keys

I have a array like
array = [
{"point"=>6, "score"=>4, "team"=>"Challenger"},
{"point"=>4, "score"=>2, "team"=>"INB"},
{"point"=>2, "score"=>2, "team"=>"Super-11"},
{"point"=>3, "score"=>7, "team"=>"INB"}
]
I want to merge hashes by "team" and sum the values of "point" and "score". Additionally want to insert an key "qualified" in each hash if point is greater than 5. So the final result will be:
result= [
{"point"=>6, "score"=>4, "qualified"=> "yes", "team"=>"Challenger"},
{"point"=>7, "score"=>9, "qualified"=> "yes", "team"=>"INB"},
{"point"=>2, "score"=>2, "qualified"=> "no", "team"=>"Super-11"}
]
Any help would be appreciated. Thanks!
One more possible solution :)
array.group_by { |item| item['team'] }.map do |_, items|
result = items.inject({}) { |hash, item| hash.merge(item) { |_, old, new| Integer(old) + new rescue old } }
result.merge("qualified" => result['point'] > 5 ? "yes" : "no")
end
Combination of group_by and map should help
result =
array.group_by {|item| item['team'] }
.map do |team, items|
total_points = items.map{|item| item['point']}.reduce(0, :+)
total_score = items.map{|item| item['score']}.reduce(0, :+)
qualified = points > 5
{
'point' => total_points,
'score' => total_score,
'qualified' => qualified ,
'team' => team
}
end
result = array.group_by{|i| i['team']}
.map do |k,v|
points = v.map{|i| i['point']}.inject(0, :+)
score = v.map{|i| i['score']}.inject(0, :+)
{
'point' => points,
'score' => score,
'qualified' => points > 5 ? 'yes' : 'no',
'team' => k
}
end
This is an alternative version. group_by is mandatory, I guess.
I used a temporary hash with keys as symbol to store data during iterations.
result = array.group_by { |hash| hash['team'] }.map do |team|
tmp_hash = {point: 0, score: 0, team: team[0], qualified: 'no'}
team[1].each { |h| tmp_hash[:point] += h['point'] ; tmp_hash[:score] += h['score'] }
tmp_hash[:qualified] = 'yes' if tmp_hash[:point] > 5
tmp_hash
end
this gives as result:
# => [
# {:point=>6, :score=>4, :team=>"Challenger", :qualified=>"yes"},
# {:point=>7, :score=>9, :team=>"INB", :qualified=>"yes"},
# {:point=>2, :score=>2, :team=>"Super-11", :qualified=>"no"}
# ]
After doing group_by, a simple map operation which takes the first element as the mapped value, sums up point and score within it and then merges the qualified condition into it is easy enough:
array
.group_by { |h| h["team"] }
.map do |_, a|
["point", "score"].each { |k| a.first[k] = a.sum { |h| h[k] } }
a.first.merge({"qualified": a.first["score"] > 5 ? 'yes' : 'no'})
end
Online demo here
array.each_with_object({}) do |g,h|
h.update(g["team"]=>g.merge("qualified"=>g["score"] > 5 ? "yes" : "no")) do |_,o,n|
{ "point" =>o["point"]+n["point"],
"score" =>o["score"]+n["score"],
"team" =>o["team"],
"qualified"=>(o["score"]+n["score"]) > 5 ? "yes" : "no" }
end
end.values
#=> [{"point"=>6, "score"=>4, "team"=>"Challenger", "qualified"=>"no"},
# {"point"=>7, "score"=>9, "team"=>"INB", "qualified"=>"yes"},
# {"point"=>2, "score"=>2, "team"=>"Super-11", "qualified"=>"no"}]
This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys (here the value of :id) that are present in both hashes being merged. See the doc for the description of the three block variables (here _, o and n).
Note that the receiver of values (at the end) is
{"Challenger"=>{"point"=>6, "score"=>4, "team"=>"Challenger", "qualified"=>"no"},
"INB"=>{"point"=>7, "score"=>9, "team"=>"INB", "qualified"=>"yes"},
"Super-11"=>{"point"=>2, "score"=>2, "team"=>"Super-11", "qualified"=>"no"}}
One could alternatively make a separate pass at the end to add the key "qualified':
array.each_with_object({}) do |g,h|
h.update(g["team"]=>g) do |_,o,n|
{ "point" =>o["point"]+n["point"],
"score" =>o["score"]+n["score"],
"team" =>o["team"] }
end
end.values.
map { |h| h.merge("qualified"=>(h["score"] > 5) ? "yes" : "no") }

Dump YAML-like key names of Hash

What's a convenient way to get a list of all Hash keys (with nesting) separated by dots?
Given I have a hash:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}
Desired output (array of strings) which represents all key paths in a hash:
level1.level21.level31
level1.level21.level32
level1.level22
My current solution:
class HashKeysDumper
def self.dump(hash)
hash.map do |k, v|
if v.is_a? Hash
keys = dump(v)
keys.map { |k1| [k, k1].join('.') }
else
k.to_s
end
end.flatten
end
end
It also available as gist (with specs).
Well, it depends on what you mean by cleaner, but here's a smaller version that…
Will work on subclasses Hashes or Hash-alikes
Extends Hash, making it look cleaner in your code.
class Hash
def keydump
map{|k,v|v.keydump.map{|a|"#{k}.#{a}"} rescue k.to_s}.flatten
end
end
results:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}.keydump
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Here is my vision of this:
h = { 'level1' => { 'level2' => { 'level31' => 'val1', 'level32' => 'val2' } } }
class Hash
def nested_keys
self.inject([]) { |f, (k,v)| f += [k, v.is_a?(Hash) ? v.nested_keys : []] }.flatten
end
end
keys = h.nested_keys
p keys
#=> ["level1", "level2", "level31", "level32"]
k1, k2 = keys.shift, keys.shift
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level31
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level32
Here is a Working Demo
I just committed some code to RubyTree that adds from_hash() which would allow you to do this:
require 'rubytree'
Tree::TreeNode.from_hash(hash).each_leaf.map{|n| "#{n.name}.#{n.parentage.map(&:name).reverse.join('.')}" }
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Aside from the gem require, it's a one-liner :)

Building a hash out of hash

I'm trying to write my code more compact. I have three hashes. The first hash (#hash) is a collection of sub-hashes (value_1, value_2)
#hash = {
"Key" => ["value_1", "value_2"]
}
#value_1 = {
"Foo" => ["bar_1", "bar_2"]
}
#value_2 = {
"Foo2" => ["bar2_1", "bar2_2"]
}
Now, in my haml-view i'm trying to make something like this:
- i = 0
- #hash.each_value do |value|
- #value_[i].each_pair do |k, v|
= k
= v[0]
- i = i +1
I don't want to write one hash after the other. It's a bit similar to making a symbol out of a string, where you can write somthing like "value_#{i}".to_sym. I hope, somebody can follow and help me.
#hashes = [
{
"Foo" => ["bar_1", "bar_2"]
},
{
"Foo2" => ["bar2_1", "bar2_2"]
}
]
Then
#hashes.each do |v|
v.each_pair do |k,v|
= k
= v[0]
And if you need the index use each_with_index.
EDIT
Try this:
- #hash.each_value do |value|
- value.each do |v|
- instance_variable_get(:"##{v}").each_pair do |k, val|
= k
= val[0]
API

In Ruby, how do I make a hash from an array?

I have a simple array:
arr = ["apples", "bananas", "coconuts", "watermelons"]
I also have a function f that will perform an operation on a single string input and return a value. This operation is very expensive, so I would like to memoize the results in the hash.
I know I can make the desired hash with something like this:
h = {}
arr.each { |a| h[a] = f(a) }
What I'd like to do is not have to initialize h, so that I can just write something like this:
h = arr.(???) { |a| a => f(a) }
Can that be done?
Say you have a function with a funtastic name: "f"
def f(fruit)
fruit + "!"
end
arr = ["apples", "bananas", "coconuts", "watermelons"]
h = Hash[ *arr.collect { |v| [ v, f(v) ] }.flatten ]
will give you:
{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}
Updated:
As mentioned in the comments, Ruby 1.8.7 introduces a nicer syntax for this:
h = Hash[arr.collect { |v| [v, f(v)] }]
Did some quick, dirty benchmarks on some of the given answers. (These findings may not be exactly identical with yours based on Ruby version, weird caching, etc. but the general results will be similar.)
arr is a collection of ActiveRecord objects.
Benchmark.measure {
100000.times {
Hash[arr.map{ |a| [a.id, a] }]
}
}
Benchmark #real=0.860651, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.8500000000000005, #total=0.8500000000000005
Benchmark.measure {
100000.times {
h = Hash[arr.collect { |v| [v.id, v] }]
}
}
Benchmark #real=0.74612, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.740000000000002, #total=0.750000000000002
Benchmark.measure {
100000.times {
hash = {}
arr.each { |a| hash[a.id] = a }
}
}
Benchmark #real=0.627355, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.6199999999999974, #total=0.6299999999999975
Benchmark.measure {
100000.times {
arr.each_with_object({}) { |v, h| h[v.id] = v }
}
}
Benchmark #real=1.650568, #cstime=0.0, #cutime=0.0, #stime=0.12999999999999998, #utime=1.51, #total=1.64
In conclusion
Just because Ruby is expressive and dynamic, doesn't mean you should always go for the prettiest solution. The basic each loop was the fastest in creating a hash.
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }
Ruby 2.6.0 enables a shorter syntax by passing a block to the to_h method:
arr.to_h { |a| [a, f(a)] }
This is what I would probably write:
h = Hash[arr.zip(arr.map(&method(:f)))]
Simple, clear, obvious, declarative. What more could you want?
I'm doing it like described in this great article http://robots.thoughtbot.com/iteration-as-an-anti-pattern#build-a-hash-from-an-array
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h.merge(fruit => f(fruit)) }
More info about inject method: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject
Another one, slightly clearer IMHO -
Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]
Using length as f() -
2.1.5 :026 > array = ["apples", "bananas", "coconuts", "watermelons"]
=> ["apples", "bananas", "coconuts", "watermelons"]
2.1.5 :027 > Hash[*array.reduce([]) { |memo, fruit| memo << fruit << fruit.length }]
=> {"apples"=>6, "bananas"=>7, "coconuts"=>8, "watermelons"=>11}
2.1.5 :028 >
in addition to the answer of Vlado Cingel (I cannot add a comment yet, so I added an answer).
Inject can also be used in this way: the block has to return the accumulator. Only the assignment in the block returns the value of the assignment, and an error is reported.
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }

Resources