簡體   English   中英

如何在Ruby中對數組進行分組和求和?

[英]How to group and sum arrays in Ruby?

我有一個像這樣的數組:

ar = [[5, "2014-01-27"],
[20, "2014-01-28"],
[5, "2014-01-28"],
[10, "2014-01-28"],
[15, "2014-01-29"],
[5, "2014-01-29"],
[5, "2014-01-30"],
[10, "2014-01-30"],
[5, "2014-01-30"]]

我最終需要做的是按日期對數組項進行分組,並總結每個子數組的第一項中的數字。

所以輸出將是這樣的:

[[5, "2014-01-27"],
[35, "2014-01-28"],
[20, "2014-01-29"],
[20, "2014-01-30"]]

ar.group_by(&:last).map{ |x, y| [y.inject(0){ |sum, i| sum + i.first }, x] }

編輯以添加說明:
我們按最后一個值(日期)分組,產生一個哈希值:

{"2014-01-27"=>[[5, "2014-01-27"]], "2014-01-28"=>[[20, "2014-01-28"], [5, "2014-01-28"], [10, "2014-01-28"]], "2014-01-29"=>[[15, "2014-01-29"], [5, "2014-01-29"]], "2014-01-30"=>[[5, "2014-01-30"], [10, "2014-01-30"], [5, "2014-01-30"]]}

然后用x作為散列鍵映射, y作為[[number, date], [number, date]]對的數組映射。

.inject(0)表示sum0開始,然后我們將每個數組的第一項(數字)添加到該總和,直到迭代所有數組並添加所有數字。

然后我們做[y, x] ,其中x是散列鍵(日期), y是所有數字的總和。

這種方法很有效,因為我們使用inject來避免映射數組兩次,之后不必反轉值,因為我們在映射時交換了它們的位置。

編輯:有趣的是@bjhaid和我的答案之間的基准很接近:

    user     system      total        real
5.117000   0.000000   5.117000 (  5.110292)
5.632000   0.000000   5.632000 (  5.644323)

1000000次迭代 - 我的方法是最慢的

h = ar.group_by(&:last)
h.keys.each{|k| h[k] = h[k].map(&:first).inject(:+)}
h.map(&:reverse)
result = {}
ar.map{|v,date| result[date] ||= 0; result[date] += v}

然后你得到一個哈希,鍵是日期,值是總和,你真的需要結果成為一個數組嗎? 看起來你需要一個哈希,但我不知道上下文

也許你甚至不必在ruby中這樣做,如果這一切都來自一個數據庫你可以分組並與查詢求和

ar.each_with_object(Hash.new(0)) { |x,hash| hash[x[1]] += x[0] }.map(&:reverse)
=> [[5, "2014-01-27"], [35, "2014-01-28"], [20, "2014-01-29"], [20, "2014-01-30"]]

解釋

第一部分使用Hash.new作為提供給Enumerable#each_with_object的對象來生成HashHash將其鍵設置為日期(數組的第二個索引),並將值設置為數組的第一個索引的總和

[29] pry(main)> ar.each_with_object(Hash.new(0)) { |x,hash| hash[x[1]] += x[0] }
=> {"2014-01-27"=>5, "2014-01-28"=>35, "2014-01-29"=>20, "2014-01-30"=>20}

第二部分使用可枚舉#地圖 ,它把每個keyvalue從散列作為陣列,其產生所述塊的/ proc對, 陣列#反向調用的每個產生對扭轉並產生最終的陣列

[30] pry(main)> {"2014-01-27"=>5, "2014-01-28"=>35, "2014-01-29"=>20, "2014-01-30"=>20}.map(&:reverse)
=> [[5, "2014-01-27"], [35, "2014-01-28"], [20, "2014-01-29"], [20, "2014-01-30"]]

我更喜歡@ sawa的解決方案,它使用group_by ,但這是另一種有助於說明這里可能的方法多樣性的方法。

首先將數組轉換為哈希值,將日期作為鍵

h = ar.each_with_object(Hash.new {|h,k| h[k] = []}) { |(x,d),h| h[d] << x }
  # => {"2014-01-27"=>[5],
  #     "2014-01-28"=>[20, 5, 10],
  #     "2014-01-29"=>[15, 5],

接下來,將此哈希中的每個值(數組)替換為其元素的總和:

h.keys.each { |k| h[k] = h[k].reduce(:+) }
  #   => ["2014-01-27", "2014-01-28", "2014-01-29", "2014-01-30"]
  # h => {"2014-01-27"=>5 , "2014-01-28"=>35,
  #       "2014-01-29"=>20, "2014-01-30"=>20}

請注意,此表達式返回鍵的數組,但哈希h現在具有所需的值。 出於這個原因,我們無法鏈接到最終聲明:

h.map(&:reverse).sort_by(&:first)
  # => [[ 5, "2014-01-27"], [35, "2014-01-28"],
  #     [20, "2014-01-29"], [20, "2014-01-30"]]

我用這種方式編寫它的原因之一是鼓勵你考慮使用hash g作為最終結果,而不是另一個數組。 (這也是@sawa解決方案中前兩行之后的h的值)。 考慮在代碼中的后續操作中這是否更有意義。

大部分內容都是直截了當的,但第each with object需要一些解釋。 object是一個哈希,由塊局部變量h 此哈希由以下內容創建:

Hash.new { |h,k| h[k] = [] }

這使得默認值為空數組。 第一次穿過街區, d => "2014-01-27" 由於哈希最初為空,因此沒有密鑰"2014-01-27" 結果, h["2014-01-27"]被賦予默認值[] ,之后h["2014-01-27"] << 5 ,導致h => {"2014-01-27" => 5}

ar.group_by(&:last).map{ |d,g| [g.map(&:first).inject(:+), d] }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM