Given array
[
{date: '2014-01-01', a: 5, b:1},
{date: '2014-01-01', xyz: 11},
{date: '2014-10-10', qbz: 5},
{date: '2014-10-10', v: 4, q: 1, strpm: -99}
]
I want to group by date and output an array of hashes
ouput = [
{date: 2014-01-01, a: 5, b:1, xyz: 11 },
{date: 2014-10-10, qbz: 5, v: 4, q: 1, strpm: -99},
]
I am solving it in Ruby and have a solution but it seems inefficient.
def arrayt(inputarray)
outputarray=[];
inputarray.each do |x|
tindex = includes(outputarray,x[:date])
if tindex == -1
outputarray.push(x)
else
outputarray[tindex].merge!(x)
end
end
return outputarray
end
def includes(array,date)
array.each_with_index do |temp,index|
if date==temp[:date]
return index
end
end
return -1
end
Any help with a more elegant solution would be appreciated
[
{date: '2014-01-01', a: 5, b:1},
{date: '2014-01-01', xyz: 11},
{date: '2014-10-10', qbz: 5},
{date: '2014-10-10', v: 4, q: 1, strpm: -99}
]
.group_by(&:first).map{|_, v| v.inject(:merge)}
Here's a way that employs the form of Hash#update (aka merge!) that uses a block to determine the values of keys that are present in both hashes being merged:
arr.each_with_object({}) { |g,h|
h.update({ g[:date]=>g }) { |_,ov,nv| ov.merge(nv) } }.values
To wit:
hash = arr.each_with_object({}) { |g,h|
h.update({ g[:date]=>g }) { |_,ov,nv| ov.merge(nv) } }
#=>{"2014-01-01"=>{:date=>"2014-01-01", :a=>5, :b=>1, :xyz=>11},
# "2014-10-10"=>{:date=>"2014-10-10", :qbz=>5, :v=>4, :q=>1, :strpm=>-99}}
hash.values
#=> [{:date=>"2014-01-01", :a=>5, :b=>1, :xyz=>11},
# {:date=>"2014-10-10", :qbz=>5, :v=>4, :q=>1, :strpm=>-99}]
Related
I want to sum the values of same keys like
arr = [{"69120090" => [1, 2, 3]}, {"69120090" => [4, 5, 6]}]
I need to result in:
result = [{"69120090" => [5, 7, 9]}]
Reduce by Hash#merge! with a block:
arr = [{"69120090"=> [1, 2, 3] }, {"69120090"=> [4, 5, 6] }]
arr.each_with_object({}) do |h, acc|
acc.merge!(h) { |_, v1, v2| v1.zip(v2).map(&:sum) }
end
#⇒ {"69120090"=>[5, 7, 9]}
The above accepts any number of hashes with any number of keys each.
Just to have another option, given the array:
arr = [{ a: [1, 2, 3], b:[8,9,0] }, { a: [4, 5, 6], c: [1,2,3] }, { b: [0,1,2], c: [1,2,3] } ]
You could write:
tmp = Hash.new{ |k,v| k[v] = [] }
arr.each { |h| h.each { |k,v| tmp[k] << v } }
tmp.transform_values { |k| k.transpose.map(&:sum) }
Which returns
tmp #=> {:a=>[5, 7, 9], :b=>[8, 10, 2], :c=>[2, 4, 6]}
As one liner:
(arr.each_with_object(Hash.new{ |k,v| k[v] = [] }) { |h, tmp| h.each { |k,v| tmp[k] << v } }).transform_values { |k| k.transpose.map(&:sum) }
I have the following JSON:
{
"groups" : [
{
"values": "21",
"date": "2013-02-22"
},
{
"values": "25",
"date": "2013-02-22"
},
{
"values": "20",
"date": "2013-02-22"
},
{
"values": "19",
"date": "2013-02-22"
},
{
"values": "42",
"date": "2013-02-10"
},
{
"values": "30",
"date": "2013-02-10"
},
{
"values": "11",
"date": "2013-02-10"
}
]
}
I have the values and the date already extracted in a Ruby Class. I want to find the "highest" and "lowest" value for every date. How do I do that?
Also I want to create parallel arrays for the same. For instance:
low = [12, 22, 11, 45]
high = [34, 50, 15, 60]
dates = ["2013-02-22", "2013-02-10", "2013-02-06", "2013-02-01"]
I would also like to display all the values for every date.
Could someone please give me some direction for this?
You can group_by :date and iterate through the dates. Then create an array of :values in the group.
Then use minmax to get the proper values and transpose the final array to get your arrays and assign to dates, low and high.
json = {
"groups": [
{ "values": "21", "date": "2013-02-22" },
{ "values": "25", "date": "2013-02-22" },
{ "values": "20", "date": "2013-02-22" },
{ "values": "19", "date": "2013-02-22" },
{ "values": "42", "date": "2013-02-10" },
{ "values": "30", "date": "2013-02-10" },
{ "values": "11", "date": "2013-02-10" }
]
}
dates, low, high = json[:groups].group_by { |g| g[:date] }.map do |date, grouped|
values = grouped.map { |group| group[:values] }
[date, *values.minmax]
end.transpose
# => => [["2013-02-22", "2013-02-10"], ["19", "11"], ["25", "42"]]
dates
# => ["2013-02-22", "2013-02-10"]
low
# => ["19", "11"]
high
# => ["25", "42"]
If str is your JSON string:
require 'json'
arr = JSON.parse(str)["groups"]
#=> [{"values"=>"21", "date"=>"2013-02-22"},
# {"values"=>"25", "date"=>"2013-02-22"},
# {"values"=>"20", "date"=>"2013-02-22"},
# {"values"=>"19", "date"=>"2013-02-22"},
# {"values"=>"42", "date"=>"2013-02-10"},
# {"values"=>"30", "date"=>"2013-02-10"},
# {"values"=>"11", "date"=>"2013-02-10"}]
by_date = arr.each_with_object(Hash.new {|h,k| h[k] = []}) { |g,h|
h[g["date"]] << g["values"].to_i }
# => {"2013-02-22"=>[21, 25, 20, 19], "2013-02-10"=>[42, 30, 11]}
dates = by_date.keys
#=> ["2013-02-22", "2013-02-10"]
min_vals, max_vals = *by_date.map { |_,vals| vals.minmax }
#=> [[19, 25], [11, 42]]
min_vals
#=> [19, 25]
max_vals
#=> [11, 42]
The method Enumerable#each_with_object takes an argument that is the initial value of the object that will be constructed and returned by the method. It's value is given by the second block variable, h. I made that argument an empty hash with a default value given by the block:
{|h,k| h[k] = []}
What is the "default value"? All it means is that if the hash h does not have a key k, h[k] returns an empty array. Let's see how that works here.
Initially, h #=> {} and each_with_object sets the first block variable, g equal to the first value of arr:
g = {"values"=>"21", "date"=>"2013-02-22"}
and block calculation is performed:
h[g["date"]] << g["values"].to_i
#=> h["2013-02-22"] << 21
Since h does not have a key "2013-02-22", h["2013-02-22"] is first set equal to the default value, an empty array:
h["2013-02-22"] = []
then
h["2013-02-22"] << 21
#=> [21]
h #=> {"2013-02-22"=>[21]}
When the next value of arr is passed to the block:
g = {"values"=>"25", "date"=>"2013-02-22"}
and h is as above. So now the block calculation is:
h[g["date"]] << g["values"].to_i
#=> h["2013-02-22"] << 25
#=> [21, 25]
h #=> {"2013-02-22"=>[21, 25]}
The default value is not used this time, as h has a key "2013-02-22".
One other thing may require explanation: the "splat" * in:
min_vals, max_vals = *by_date.map { |_,vals| vals.minmax }
We see that:
by_date.map { |date, vals| vals.minmax }
#=> [[19, 25], [11, 42]]
If *by_date.map { |date, vals| vals.minmax } is on the right side of an equality, the splat causes the two elements of [[19, 25], [11, 42]] are assigned to variables on the left side of the equality using parallel assignment. The weird and wonderful splat operator needs to be in every Rubiest's bag of tricks.
Since I'm not using date in the block calculation, I've drawn attention to that by replacing date with the local variable _.
Edit: To answer the question you posted in a comment, if:
id = [1,1,1,2,2,3,4]
high = [100,100,100,90,90,100,100]
low = [20,20,20,10,10,30,40]
and I understand your question correctly, you could first compute:
indices = id.each_with_index.to_a.uniq(&:first).map(&:last)
#=> [0, 3, 5, 6]
Then the three arrays you want are:
id.values_at(*indices)
#=> [1, 2, 3, 4]
high.values_at(*indices)
#=> [100, 90, 100, 100]
low.values_at(*indices)
#=> [20, 10, 30, 40]
I have the following logic:
some_array.each do |element|
if element[:apples] == another_hash[:apples] &&
element[:oranges] == another_hash[:oranges] &&
element[:pineapple] == another_hash[:pineapple]
match = element
break
end
end
I iterate through a list of key value pairs. If I can match the required keys (3 of 5), then I toss the element in a var for later use. If I find a match, I break out of the loop.
I am looking for the most idiomatic way to optimize this conditional. Thank you in advance.
How about:
match = some_array.find do |element|
[:apples, :oranges, :pinapple].all? {|key| element[key] == another_hash[key]}
end
If you want to select any element which has at least 3 matching keys from 5 keys given then:
match = some_array.find do |element|
element.keys.select {|key| element[key| == another_hash[key]}.size > 2
end
This is how I'd do it.
Code
def fruit_match(some_array, another_hash, fruit)
other_vals = another_hash.values_at(*fruit)
return nil if other_vals.include?(nil)
some_array.find { |h| h.values_at(*fruit) == other_vals }
end
Examples
some_array = [ { apple: 1, orange: 2, pineapple: 3, plum: 4 },
{ apple: 1, cherry: 7, pineapple: 6, plum: 2 },
{ apple: 6, cherry: 2, pineapple: 8, fig: 3 } ]
another_hash = { apple: 6, cherry: 4, pineapple: 8, quamquat: 5 }
fruit = [:apple, :pineapple]
fruit_match(some_array, another_hash, fruit)
#=> { :apple=>6, :cherry=>2, :pineapple=>8, :fig=>3 }
fruit = [:apple, :plum]
fruit_match(some_array, another_hash, fruit)
#=> nil
[Edit: I didn't notice the "3-5" matches until I saw #7stud's answer. Requiring the number of matches to fall within a given range is an interesting variation. Here's how I would address that requirement.
Code
def fruit_match(some_array, another_hash, fruit, limits)
other_vals = another_hash.values_at(*fruit)
some_array.select { |h| limits.cover?(h.values_at(*fruit)
.zip(other_vals)
.count {|e,o| e==o && e}) }
end
Example
some_array = [ { apple: 1, orange: 2, pineapple: 1, cherry: 1 },
{ apple: 2, cherry: 7, pineapple: 6, plum: 2 },
{ apple: 6, cherry: 1, pineapple: 8, fig: 3 },
{ apple: 1, banana: 2, pineapple: 1, fig: 3 } ]
another_hash = { apple: 1, cherry: 1, pineapple: 1, quamquat: 1 }
fruit = [:apple, :pineapple, :cherry]
limits = (1..2)
fruit_match(some_array, another_hash, fruit, limits)
#=> [{:apple=>6, :cherry=>1, :pineapple=>8, :fig=>3},
# {:apple=>1, :banana=>2, :pineapple=>1, :fig=>3}]
tidE]
If I can match the required keys (3 of 5)
I don't think any of the posted answers addresses that.
target_keys = %i[
apples
oranges
pineapples
strawberries
bananas
]
data = [
{beer: 0, apples: 1, oranges: 2, pineapples: 3, strawberries: 4, bananas: 5},
{beer: 1, apples: 6, oranges: 7, pineapples: 8, strawberries: 9, bananas: 10},
{beer: 2, apples: 6, oranges: 2, pineapples: 3, strawberries: 9, bananas: 10},
]
match_hash = {
apples: 6, oranges: 2, pineapples: 3, strawberries: 9, bananas: 10
}
required_matches = 3
required_values = match_hash.values_at(*target_keys).to_enum
found_match = nil
catch :done do
data.each do |hash|
found_values = hash.values_at(*target_keys).to_enum
match_count = 0
loop do
match_count += 1 if found_values.next == required_values.next
if match_count == required_matches
found_match = hash
throw :done
end
end
required_values.rewind
end
end
p found_match
--output:--
{:beer=>1, :apples=>6, :oranges=>7, :pineapple=>8, :strawberry=>9, :banana=>10
More readable version I could think is slice:
keys = [:apples, :oranges, :pinapple]
match = some_array.find {|e| e.slice( *keys ) == another_hash.slice( *keys )}
UPDATE
Slice is not a pure ruby method of Hash, it includes in Rails' ActiveSupport library.
If you don't want to be using Rails, you can just load Active Support.
Add active_support to your Gemfile and require "active_support/core_ext/hash/slice".
Or you could just paste the contents of slice.rb into your app somewhere. The URL can be found here.
I have an array of hashes (edited):
data = [
{id: 1, name: "Amy", win: 1, defeat: 0},
{id: 1, name: "Amy", win: 1, defeat: 3},
{id: 2, name: "Carl", win: 0, defeat: 1},
{id: 2, name: "Carl", win: 2, defeat: 1}
]
How can I group or merge into something like this using the key "name" as reference:
data = [
{id: 1, name: "Amy", win: 2, defeat: 3},
{id: 2, name: "Carl", win: 2, defeat: 2}
]
edited I forgot to mention that I have an ID too that can't be added.
Here is my try
#!/usr/bin/env ruby
data = [
{"name"=> "Amy", "win" => 1, "defeat" => 0},
{"name"=> "Amy", "win" => 1, "defeat" => 3},
{"name"=> "Carl", "win" => 0, "defeat" => 1},
{"name"=> "Carl", "win" => 2, "defeat" => 1}
]
merged_hash = data.group_by { |h| h['name'] }.map do |_,val|
val.inject do |h1,h2|
h1.merge(h2) do |k,o,n|
k == 'name' ? o : o + n
end
end
end
merged_hash
# => [{"name"=>"Amy", "win"=>2, "defeat"=>3},
# {"name"=>"Carl", "win"=>2, "defeat"=>2}]
Answer to the edited post :-
#!/usr/bin/env ruby
data = [
{id: 1, name: "Amy", win: 1, defeat: 0},
{id: 1, name: "Amy", win: 1, defeat: 3},
{id: 2, name: "Carl", win: 0, defeat: 1},
{id: 2, name: "Carl", win: 2, defeat: 1}
]
merged_hash = data.group_by { |h| h.values_at(:name, :id) }.map do |_,val|
val.inject do |h1,h2|
h1.merge(h2) do |k,o,n|
%i(id name).include?(k) ? o : o + n
end
end
end
merged_hash
# => [{:id=>1, :name=>"Amy", :win=>2, :defeat=>3},
# {:id=>2, :name=>"Carl", :win=>2, :defeat=>2}]
You can do it in one pass with each_with_object and a Hash-memo with an appropriate default. For example:
data.each_with_object(Hash.new { |h, k| h[k] = { :id => k.first, :name => k.last, :win => 0, :defeat => 0 } }) do |h, m|
k = h.values_at(:id, :name)
m[k][:win ] += h[:win ]
m[k][:defeat] += h[:defeat]
end.values
The basic trick is to cache the results indexed by an appropriate key ([ h[:id], h[:name] ] in this case) and use the values to store what you're after. The default proc on the m Hash autovivifies cached values and then you can apply simple summations during iteration. And a final values call to unwrap the cache.
Good place where you can use group_by
result = []
data.group_by{|d| d[:id]}.each do {|name, records|
win = 0
defeat = 0
records.each do |r|
win += r[:win]
defeat += r[:defeat]
end
f = records.first
results << {:id => f[:id], :name => f[:name], :win => win, :defeat => defeat}
end
I have the following array
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
how can I get this as result?
{nil => [1,9,0],10 => [2,2,4], 16 => [4,0,0], 5 => [10,2,9], 17=>[0,3,1]}
I've seen that I can use something like this
t.group_by{|h| h['key']}
but I'm not sure if I can put a regexp inside the brackets
Thanks in advance
Javier
EDIT:
Is just want to group by each key of each hash inside the array, if the key is not present then the value is 0 for that hash
How about this one for illegibility:
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
# Create hash of possible keys
keys = t.reduce({}) { |m, h| h.each_key { |k| m[k] = [] }; m }
# Iterate through array, for each hash, for each key, append the
# value if key is in hash or zero otherwise
t.reduce(keys) { |m, h| m.each_key { |k| m[k] << (h[k] || 0) }; m }
puts keys
#=> {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}
Not the most elegant code I've ever written, but it does the job and is easy to understand:
def jqq(a)
keys = []
result = {}
a.each do |h|
keys += h.keys
end
keys.uniq.each do |key|
result[key] = []
a.each do |h|
h.default = 0
result[key] << h[key]
end
end
result
end
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
puts jqq(t)
# {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}
I do not think there is any any function available
Just gave a try with hash
def do_my_work(data)
hash = {}
#get all keys first
arr.map{|m| m.keys}.flatten.uniq.each {|a| hash[a]=[]}
# Now iterate and fill the values
arr.each do |elm|
hash.each do |k,v|
hash[k] << (elm[k].nil? ? 0 : elm[k])
end
end
end
hash = do_my_work(t)
puts hash
# => {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}