创建散列并检查密钥是否已存在

Creating hash and checking if the key already exists

我的方法中可以使用以下数据:

在该方法的最后,我想 return 一个散列数组,其中包含从多个服务调用中收集的数据。

逻辑应该检查该键是否已存在于哈希中,如果是,则将值添加到现有键,如果不存在,则为该新键创建一个键值对象。对于这个例子,我在第一次服务调用后的哈希看起来像:

my_final_array = [{Apple: [2015-04-01, 3]}, {Banana: [2015-04-01, 2]}, {Oranges: [2015-04-01, 4]}]

然而,在我们从第二次服务调用中获取数据后,我希望我的最终数组为:

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

有没有一种简单的方法可以获得我所期望的?

我拥有的算法对数据进行两次迭代,即创建一个数组以从所有服务调用中收集数据,然后对数组进行迭代以按键分组。

这是我最初尝试解决它的方法:

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.

当您有这些特定要求时,最好只创建您自己的 class - 这样您就可以在内部存储数据,但这是最好的。例如

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

那么:

[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)> 

请注意,第一个输出与您在问题中要求的不同,因为这些值已经嵌套在第二层,我认为这可能是您想要的,所以我将其保留原样。

可能是这样的:

array_of_possible_keys.each do |key|
    if my_final_hash.has_key?(key)
       do something
    else
       do other thing
    end
end

如果您改用散列,则不必遍历数组。而且我看不出有任何理由不更换

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

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

这是一个函数,它接受数组的当前版本、日期和要处理的新数组。

如果是第一次服务调用,则根据参数时间和要处理的数组创建一个新的数组。对于后续的服务调用,将根据数组的当前版本创建散列,然后处理参数(新)数组以将值添加到散列中。最后,哈希被转换回其原始数组形式。

请参考以下示例代码:

解决方案

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

用法

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

如果您像这样存储数据:

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

考虑将其更改为:

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

我不知道这是否更适合您的目的,但我希望您能看到它。然后你可以得到想要的分组如下:

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

嗯,不,这不是您所要求的,但我希望您也看到它,如果您发现它是一种更有用的数据结构。将其转换为您所要求的内容很容易,我将在下面进行转换,但首先,我想解释一下有关上述计算的一些事情。

result 的计算采用 Hash#update(又名 merge!)的形式,它使用块来确定要合并的两个哈希中存在的键的值。块变量是 k,o,n,其中:

  • k 是公共密钥(我已将其更改为 _ 以表示它未在块中使用);
  • o(for "old")是hk的值,正在构造的hash;和
  • n(表示“new”)是gk的值,被合并的散列。

如果你想让上面的:Kiwi的值变成[["2015-04-05", 3]](我认为这样在处理结果时会更容易),将update简化为:

h.update(k=>[[g[:date],v]]) { |_,o,n| o+n } 

要将 result 转换为您要求的形式:

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