0

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. 
8
  • Perhaps you would get more assistance in optimization if you posted your algorithm over on CodeReview.SE? Commented Apr 8, 2015 at 18:45
  • Welcome. When asking a question on how to code something we expect to see your code written to solve the problem. It's much better, and easier, for us to correct your code than it is for us to write something from scratch, especially if you have to try to fit what we wrote into your existing code. If you don't show us your code it really looks like you're trolling for solutions written by someone else, which doesn't help you in the long run. Commented Apr 8, 2015 at 19:13
  • I would expect it would be easier to use the result if the value of fruit processed on just one service call were returned in array; e.g., { Kiwi: [[2015-04-05, 3]] } rather than { Kiwi: [2015-04-05, 3] }. Commented Apr 8, 2015 at 21:12
  • @theTinMan, I am perplexed as to why this question has been put on hold. I am addressing you because (as usual) the others who voted to code are all unfamiliar to me. The "off-topic" reason given is very vague and I don't see why it applies here. I thought it was quite a good question, and very clear. Commented Apr 8, 2015 at 21:23
  • The question is clear but doesn't show any attempt to solve it. Remember that SO is not a "give me code" site. People, in their enthusiasm to "help", or more likely, be first to give code in the hope of getting points, will throw up answers, but that doesn't really help anyone searching for a problem with their code, only those who are trying to find a solution with that data. Historically there were reasons for closing because of lack of code, which migrated to the current off-topic due to lack of code for debugging. This could have also been lack of detail I think. Commented Apr 8, 2015 at 21:35

4 Answers 4

2

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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

1 Comment

Yeah if my final data holder was a hash, it would have become much easier. But I need an array as the receiver is a third party who needs an array. I could have created a hash and then converted it to an array, but I am trying to see if what I want can be done more quickly in a single iteration.
0

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

2 Comments

When answering questions with code, it really helps if you explain what the code does, which educates the person attempting to use it. Handing out code is akin to handing a starving person a fish. Explaining the code is akin to teaching the starving person how to fish. There's a big difference in what they can do with the two different responses.
Thanks @theTinMan! I've added a quick summary of what the code does :)
0

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; and
  • n (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]}] 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.