Display all the values for every date in JSON - ruby

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]

Related

Can we do below problem by initializing hash's key with a default value?

Write a method that groups the above hash into 2 groups of 'even' and 'odd' length using 'inject'.
input - "['abc','def','1234','234','abcd','x','mnop','5','zZzZ']"
My code listed below already works. But I want to know better way to do it using default value for hash's key. I meant to say something like below -
h=Hash.new { |hash, key| hash[key] = []}
Solution :
class Array
def group_even_odd
key_hash = group_by(&:length)
key_array = %w(even odd)
key_hash.each_with_object('odd' => [], 'even' => []) do |(key, value), even_odd_hash|
even_odd_hash[key_array[key % 2]].push(value)
even_odd_hash
end
end
end
if ARGV.empty?
puts 'Please provide an input'
else
input = ARGV[0].scan(/\w+/).map(&:to_s)
puts input.group_even_odd
end
Expected and actual are same, code is working.
Expected result -
{"odd"=>[["abc", "def", "234"], ["x", "5"]], "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}
One possible option, given
ary = ["abc", "def", "1234", "234", "abcd", "x", "mnop", "5", "zZzZ"]
First group by even odd, then group by size:
ary.group_by { |e| e.size.even? ? 'even' : 'odd' }
.transform_values { |v| v.group_by(&:size).values }
#= {"odd"=>[["abc", "def", "234"], ["x", "5"]], "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}
First step, to explain:
ary.group_by { |e| e.size.even? ? 'even' : 'odd' }
#=> {"odd"=>["abc", "def", "234", "x", "5"], "even"=>["1234", "abcd", "mnop", "zZzZ"]}
Then Hash#transform_values grouping each by size.
The following does not meet the requirement that inject (aka reduce) be used, but it is how I would do it.
arr = ['abc', 'def', '1234', '234', 'abcd', 'x', 'mnop', '5', 'zZzZ']
odd, even = arr.each_with_object(Hash.new { |h,k| h[k]=[] }) do |s,h|
h[s.size] << s
end.
values.
partition { |a| a.first.size.odd? }
#=> [[["abc", "def", "234"], ["x", "5"]],
# [["1234", "abcd", "mnop", "zZzZ"]]]
{ "odd"=>odd, "even"=>even }
#=> {"odd"=>[["abc", "def", "234"], ["x", "5"]],
# "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}
The steps are as follows.
h = arr.each_with_object(Hash.new {|h,k| h[k]=[]}) do |s,h|
h[s.size] << s
end
#=> {3=>["abc", "def", "234"], 4=>["1234", "abcd", "mnop", "zZzZ"],
# 1=>["x", "5"]}
a = h.values
#=> [["abc", "def", "234"], ["1234", "abcd", "mnop", "zZzZ"],
# ["x", "5"]]
odd, even = a.partition { |a| a.first.size.odd? }
#=> [[["abc", "def", "234"], ["x", "5"]],
# [["1234", "abcd", "mnop", "zZzZ"]]]
{ "odd"=>odd, "even"=>even }
#=> {"odd"=>[["abc", "def", "234"], ["x", "5"]],
# "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}
If one insists on fitting a square peg into a round hold (using inject/reduce), I suppose that could be done as follows.
arr.reduce({ "odd"=>[], "even"=>[] }) do |g,s|
oe = s.size.odd? ? "odd" : "even"
i = g[oe].find_index { |a| a.any? && a.first.size == s.size }
case i.nil?
when true then g[oe] << [s]
else g[oe][i] << s
end
g
end
#=> {"odd"=>[["abc", "def", "234"], ["x", "5"]],
# "even"=>[["1234", "abcd", "mnop", "zZzZ"]]}

MapReduce arrays in Ruby

I have two arrays like this:
["1","7","8","10"]
and
["1","2","3","6","9","11"]
These arrays represents ids from a class called Place that a user selected. I want to select the places ids with most votes. I tried transpose but as the arrays have different sizes, they cannot be transposed.
The expected output for this example is:
{ "1" => 2, "7" => 1, "8" => 1, "10" => 1, "2" => 1, "3" => 1, "6" => 1, "9" => 1, "11" => 1 }
You can join all arrays and calculate the number of identical elements like this:
arrays = [["1","7","8","10"], ["1","2","3","6","9","11"]].reduce(:+)
arrays.inject(Hash.new(0)) { |memo, e| memo.update(e => memo[e] + 1) }
# "{ "1" => 2, "7" => 1, "8" => 1, "10" => 1, "2" => 1, "3" => 1, "6" => 1, "9" => 1, "11" => 1 }"
Once you have this intermediate result use max_by to select the key with the max value from the hash:
arrays = [["1","7","8","10"], ["1","2","3","6","9","11"]].reduce(:+)
arrays.inject(Hash.new(0)) { |memo, e| memo.update(e => memo[e] + 1) }
.max_by { |_, count| count }[0]
#=> "1"
This is another way:
arr = [["1","7","8","10"], ["1","2","3","6","9","11"], ["1","2","7"]]
h = arr.flatten.sort_by(&:to_i).group_by(&:itself)
h.update(h) { |_,v| v.size }
#=> {"1"=>3, "2"=>2, "3"=>1, "6"=>1, "7"=>2, "8"=>1, "9"=>1, "10"=>1, "11"=>1}
The steps:
a = arr.flatten
#=> ["1", "7", "8", "10", "1", "2", "3", "6", "9", "11", "1", "2", "7"]
b = a.sort_by(&:to_i)
#=> ["1", "1", "1", "2", "2", "3", "6", "7", "7", "8", "9", "10", "11"]
h = b.group_by(&:itself)
#=> {"1"=>["1", "1", "1"], "2"=>["2", "2"], "3"=>["3"], "6"=>["6"],
# "7"=>["7", "7"], "8"=>["8"], "9"=>["9"], "10"=>["10"], "11"=>["11"]}
If you are using a version of Ruby prior to 2.2 (when Object#itself was introduced) you will need to instead write:
h = b.group_by { |s| s }
Lastly:
h.update(h) { |_,v| v.size }
#=> {"1"=>["1", "1", "1"], "2"=>["2", "2"], "3"=>["3"], "6"=>["6"],
# "7"=>["7", "7"], "8"=>["8"], "9"=>["9"], "10"=>["10"], "11"=>["11"]}
This uses the form of Hash#update (aka merge!) that employs a block (here { |_,v| v.size }) to determine the values of keys that are present in both hashes being merged (which in this case is all of the keys).
Update: the method Hash#transform_values made its debut in Ruby v2.4. This allows us to write the following.
arr.flatten.
sort_by(&:to_i).
group_by(&:itself).
transform_values(&:size)

Optimize grouping array of hashes by date

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}]

How to find some arrays with certain value and sum their values?

I have an array of arrays with a lot of value. Where always the first and third element is repeated and I need to sum only the second element, because this is a number that I want totalizing.
Then make a new array with all values totalizing.
Example
# My array
=> [ ['abc', 13, 40 ], [ 'abc', 20, 40 ], [ 'abc', 2, 40 ], [ 'cde', 90, 20 ], [ 'cde', 60, 20 ], [ 'fgh', 20, 50 ] ]
# My Expected Result
=> [ ['abc', 35, 40 ], ['cde', 150, 20], ['fgh', 20, 50] ]
What would be the optimal way to do that? What functions could be used to reach that result?
You can do that like this:
arr = [[ 'abc', 13, 40 ], [ 'abc', 20, 40 ], [ 'abc', 2, 40 ],
[ 'cde', 90, 20 ], [ 'cde', 60, 20 ], [ 'fgh', 20, 50 ] ]
arr.group_by { |a,_,c| [a,c] }
.map { |(a,b),arr| [a,arr.reduce(0) { |t,(_,e,_)| t + e },b] }
#=> [["abc", 35, 40],
# ["cde", 150, 20],
# ["fgh", 20, 50]]
This is how this works:
f = arr.group_by { |a,_,c| [a,c] }
#=> {["abc", 40]=>[["abc", 13, 40], ["abc", 20, 40], ["abc", 2, 40]],
# ["cde", 20]=>[["cde", 90, 20], ["cde", 60, 20]],
# ["fgh", 50]=>[["fgh", 20, 50]]}
map passes the first key-value pair of the hash f into its block, assigning the block variables (using decomposition) as follows:
a,b = ["abc", 40]
arr = [["abc", 13, 40], ["abc", 20, 40], ["abc", 2, 40]]
and then computes:
[a,g,b]
#=> ["abc", g, 40]
where
g = arr.reduce(0) { |t,(_,e,_)| t + e }
#=> 35
so
["abc", 40]=>[["abc", 13, 40], ["abc", 20, 40], ["abc", 2, 40]]
is mapped to:
["abc", 35, 40]
The two other elements of the hash f are computed similarly.
arr.group_by { |a,_,c| [a,c] }.values.map{|x|[x[0][0],x.map{|n|n[1]}.reduce(&:+),x[0][2]]}

Filling wildcard values in a hash with every possible value

Given a set of possible values, and a hash of arbitrary number of values, how can I replace every nil value with every possible combination of the possible values?
For example:
values = %w[a b c]
hash = { x:1, y:2, z:nil }
fill_wildcards( hash, values )
#=> [{ x:1, y:2, z:'a' },
#=> { x:1, y:2, z:'b' },
#=> { x:1, y:2, z:'c' }]
hash = { x:1, y:nil, z:nil }
fill_wildcards( hash, values )
#=> [{ x:1, y:'a', z:'a' },
#=> { x:1, y:'a', z:'b' },
#=> { x:1, y:'a', z:'c' },
#=> { x:1, y:'b', z:'a' },
#=> { x:1, y:'b', z:'b' },
#=> { x:1, y:'b', z:'c' },
#=> { x:1, y:'c', z:'a' },
#=> { x:1, y:'c', z:'b' },
#=> { x:1, y:'c', z:'c' }]
I can find the keys that need to be replaced:
wildkeys = hash.select{ |k,v| v.nil? }.map(&:first)
#=> [:y, :z]
And thus I can find all the permutations of values needed:
wildvalues = values.repeated_permutation( wildkeys.length ).to_a
#=> [["a", "a"], ["a", "b"], ["a", "c"], ["b", "a"],
#=> ["b", "b"], ["b", "c"], ["c", "a"], ["c", "b"], ["c", "c"]]
But I can't think of a simple way to merge these two into the original.
Might be something like this:
rest = hash.reject { |k,v| v.nil? }.to_a
wildvalues.map { |wv| Hash[rest + wildkeys.zip(wv)] }
or even
wildvalues.map { |wv| hash.merge(Hash[wildkeys.zip(wv)]) }
def fill_wildcards( hsh, values )
values.repeated_permutation(hsh.values.count(nil)).to_a.map {|combo| hsh.each_with_object(hsh.dup) {|(k,v),hsh| hsh[k] = combo.shift unless v } }
end
Another way:
Code
def doit(hash, values)
a = hash.map { |k,v| [k].product(v ? [v] : values) }
a.shift.product(*a).map(&:to_h)
end
Demo
values = %w[a b c]
hash = { x:1, y:2, z:nil }
p doit(hash, values)
#=> [{:x=>1, :y=>2, :z=>"a"},
# {:x=>1, :y=>2, :z=>"b"},
# {:x=>1, :y=>2, :z=>"c"}]
hash = { x:1, y:nil, z:nil }
p doit(hash, values)
#=> [{:x=>1, :y=>"a", :z=>"a"},
# {:x=>1, :y=>"a", :z=>"b"},
# {:x=>1, :y=>"a", :z=>"c"},
# {:x=>1, :y=>"b", :z=>"a"},
# {:x=>1, :y=>"b", :z=>"b"},
# {:x=>1, :y=>"b", :z=>"c"},
# {:x=>1, :y=>"c", :z=>"a"},
# {:x=>1, :y=>"c", :z=>"b"},
# {:x=>1, :y=>"c", :z=>"c"}]
hash = { x:nil, y:nil, z:nil }
p doit(hash, values).size #=> 27

Resources