tech_human
tech_human

Reputation: 7110

Creating hash and checking if the key already exists

The following data is available to me in my method:

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

Answers (4)

Cary Swoveland
Cary Swoveland

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

Upvotes: 0

Joshua Arvin Lat
Joshua Arvin Lat

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

smathy
smathy

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

IvanSelivanov
IvanSelivanov

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

Related Questions