Reputation: 7110
The following data is available to me in my method:
data from first service call:
date: 2015-04-01
my_array = [{Apple: 3}, {Banana: 2}, {Oranges: 4}]
data from second service call:
date: 2015-04-05
my_array = [{Apple: 4}, {Banana: 5}, {Oranges: 1}, {Kiwi: 3}]
At the end of the method, I would like to return an array of hashes which would have data collected from multiple service calls.
The logic should check if the key is already present in the hash, if yes then add the values to the existing key and if not then create a key-value object for that new key. As for this example, my hash after the first service call would look like:
my_final_array = [{Apple: [2015-04-01, 3]}, {Banana: [2015-04-01, 2]}, {Oranges: [2015-04-01, 4]}]
However after we get the data from the second service call, I want my final array to be:
my_final_array = [{Apple: [[2015-04-01, 3], [2015-04-05, 4]]}, {Banana: [[2015-04-01, 2], [2015-04-05, 5]]}, {Oranges: [[2015-04-01, 4], [2015-04-05, 1]]}, {Kiwi: [2015-04-05, 3]}]
Is there an easy way I can get what I am expecting?
The algorithm which I have is iterating through the data two times i.e. once I create an array to collect the data from all the service calls and then when I iterate over the array to group by keys.
Here is the way I was trying to solve it initially:
dates_array.each do |week_date|
my_array = #Collect data returned by service for each week_date.
my_array.each do |sample_data|
sample_array << [date, sample_data.keys.first, sample_data.values.first]
end
end
sample_hash = sample_array.each_with_object({}) { |data_value, key_name|
(key_name[data_value[1]] ||= []) << data_value.values_at(0,2)
}
#Convert sample_hash to my_final_array for third party input.
Upvotes: 0
Views: 2090
Reputation: 110725
If you are storing the data like this:
data1 = [{ date: "2015-04-01",
my_array: [{Apple: 3}, {Banana: 2}, {Oranges: 4}] },
{ date: "2015-04-05",
my_array: [{Apple: 4}, {Banana: 5}, {Oranges: 1}, {Kiwi: 3}] }]
consider changing that to:
data2 = data1.map { |g|
{ date: g[:date],
my_hash: Hash[g[:my_array].flat_map(&:to_a)] }
}
#=> [{:date=>"2015-04-01",
# :my_hash=>{:Apple=>3, :Banana=>2, :Oranges=>4}},
# {:date=>"2015-04-05",
# :my_hash=>{:Apple=>4, :Banana=>5, :Oranges=>1, :Kiwi=>3}}]
I don't know if that would work better for you purposes, but I wanted you to see it. Then you could get the desired grouping as follows:
result = data2.each_with_object({}) do |g,h|
g[:my_hash].each do |k,v|
h.update(k=>[g[:date],v]) do |_,o,n|
case o.first
when Array then o.concat(n)
else [o,n]
end
end
end
end
#=> {:Apple=> [["2015-04-01", 3], ["2015-04-05", 4]],
# :Banana=> [["2015-04-01", 2], ["2015-04-05", 5]],
# :Oranges=>[["2015-04-01", 4], ["2015-04-05", 1]],
# :Kiwi=> ["2015-04-05", 3]}
Well, no, that's not quite what you asked for, but I wanted you see that as well, should you find it a more useful data structure. It's easy to convert this to what you asked for, and I will do that below, but first, I want to explain a few things about the above calculation.
The calculation of result
employs the form of Hash#update (aka merge!
) that uses a block to to determine the values of keys that are present in both hashes being merged. The block variables are k,o,n
, where:
k
is the common key (which I've changed to _
to signify that it's not being used in the block);o
(for "old") is the value of k
in h
, the hash being constructed; andn
(for " new") is the value of k
in g
, the hash being merged. If you want the value of :Kiwi
above to to be [["2015-04-05", 3]]
(which I think would make life easier when processing the result), simplify update
to:
h.update(k=>[[g[:date],v]]) { |_,o,n| o+n }
To convert result
to the form you asked for:
result.map { |k,a| { k=>a } }
#=> [{:Apple=> [["2015-04-01", 3], ["2015-04-05", 4]]},
# {:Banana=> [["2015-04-01", 2], ["2015-04-05", 5]]},
# {:Oranges=>[["2015-04-01", 4], ["2015-04-05", 1]]},
# {:Kiwi=>["2015-04-05", 3]}]
Upvotes: 0
Reputation: 1039
Here's a function that accepts the current version of the array, the date, and the new array to be processed.
If it's the first service call, a new array is created based on the parameter time and array to be processed. For succeeding service calls, a hash is created based on the current version of the array and then the parameter (new) array is processed to add values to the hash. Finally, the hash is converted back to its original array form.
Kindly refer to the example code below:
Solution
def process_array(old_array: nil, date: date, my_array: my_array)
unless old_array
# service call # 1
my_array.each do |key_value_pair|
pair = key_value_pair.to_a.first
key = pair[0]
value = pair[1]
key_value_pair[key] = [date, value]
end
return my_array
else
# service call # 2 onwards
hash = {}
old_array.each do |key_value_pair|
pair = key_value_pair.to_a.first
key = pair[0]
value = pair[1]
hash[key] = value
end
my_array.each do |key_value_pair|
pair = key_value_pair.to_a.first
key = pair[0]
value = pair[1]
if hash.has_key?(key)
unless hash[key].first.kind_of?(Array)
hash[key] = [hash[key]]
end
hash[key] << [date, value]
else
hash[key] = [date, value]
end
end
output_array = []
hash.each do |key, value|
new_hash = {}
new_hash[key] = value
output_array << new_hash
end
output_array
end
end
Usage
service_1 = [{Apple: 3}, {Banana: 2}, {Oranges: 4}]
array_1 = process_array(old_array: nil, date: "2015-04-01", my_array: service_1)
puts array_1.to_s
# => [{:Apple=>["2015-04-01", 3]}, {:Banana=>["2015-04-01", 2]}, {:Oranges=>["2015-04-01", 4]}]
service_2 = [{Apple: 4}, {Banana: 5}, {Oranges: 1}, {Kiwi: 3}]
array_2 = process_array(old_array: array_1, date: "2015-04-05", my_array: service_2)
puts array_2.to_s
# => [{:Apple=>[["2015-04-01", 3], ["2015-04-05", 4]]}, {:Banana=>[["2015-04-01", 2], ["2015-04-05", 5]]}, {:Oranges=>[["2015-04-01", 4], ["2015-04-05", 1]]}, {:Kiwi=>["2015-04-05", 3]}]
Upvotes: 0
Reputation: 27971
When you have these sorts of specific requirements, it's best to just create your own class - so you can store the data internally however is best. Eg.
class FunkyThing
def initialize
@s = {}
end
def add date, arr
arr.each do |e|
k, v = e.flatten
( @s[k] ||= [] ) << [ date, v ]
end
end
def val
@s.map { |k, v| { k => v } }
end
end
So then:
[142] pry(main)> a = FunkyThing.new
=> #<FunkyThing:0x007fbc23ed5cb0 @s={}>
[143] pry(main)> a.add '2015-04-01', [{Apple: 3}, {Banana: 2}, {Oranges: 4}]
=> [{:Apple=>3}, {:Banana=>2}, {:Oranges=>4}]
[144] pry(main)> a.val
=> [{:Apple=>[["2015-04-01", 3]]}, {:Banana=>[["2015-04-01", 2]]}, {:Oranges=>[["2015-04-01", 4]]}]
[145] pry(main)> a.add '2015-04-05', [{Apple: 4}, {Banana: 5}, {Oranges: 1}, {Kiwi: 3}]
=> [{:Apple=>4}, {:Banana=>5}, {:Oranges=>1}, {:Kiwi=>3}]
[146] pry(main)> a.val
=> [{:Apple=>[["2015-04-01", 3], ["2015-04-05", 4]]}, {:Banana=>[["2015-04-01", 2], ["2015-04-05", 5]]}, {:Oranges=>[["2015-04-01", 4], ["2015-04-05", 1]]}, {:Kiwi=>[["2015-04-05", 3]]}]
[147] pry(main)>
Note that the first output is different from what you asked for in your question, because the values are already nested at a second level, I think this is probably what you'd want anyway so I left it as is.
Upvotes: 2
Reputation: 760
Something like this, maybe:
array_of_possible_keys.each do |key|
if my_final_hash.has_key?(key)
do something
else
do other thing
end
end
You won't have to iterate through your arrays if you'll use hashes instead. And I don't see any reason for not replacing
my_array = [{Apple: 4}, {Banana: 5}, {Oranges: 1}, {Kiwi: 3}]
my_final_array = [{Apple: [2015-04-01, 3]}, {Banana: [2015-04-01, 2]}, {Oranges: [2015-04-01, 4]}]
with
my_hash= {Apple: 4, Banana: 5, Oranges: 1, Kiwi: 3}
my_final_hash = {Apple: [2015-04-01, 3], Banana: [2015-04-01, 2], Oranges: [2015-04-01, 4]}
Upvotes: 0