有在這種情況下聚集值的兩種常用方法。首先是採用方法Enumerable#group_by,正如@engineersmnky在他的回答中所做的那樣。第二是構建一個使用一個塊,以解決其存在於兩個哈希被合併鍵的值使用該方法Hash#update(又名merge!
)形式的哈希值。我的解決方案採用了後一種方式,而不是因爲我喜歡它的group_by
,但只是向你展示一個不同的方式是可以做到的。 (如果工程師使用了update
,我會用group_by
去掉。)
由於您使用的特定數據結構,您的問題有點複雜。我發現,該解決方案可以simplfied並變得更容易,首先將數據轉換爲不同的結構遵循,更新分數,然後將結果轉換回你的數據結構。您可能需要考慮更改數據結構(如果這是您的選擇)。我在「討論」部分討論了這個問題。
代碼
def combine_scores(arr)
reconstruct(update_scores(simplify(arr)))
end
def simplify(arr)
arr.map do |h|
hash = Hash[h[:scores].map { |g| g.values }]
hash.default = 0
{ h[:student]=> hash }
end
end
def update_scores(arr)
arr.each_with_object({}) do |g,h|
h.update(g) do |_, h_scores, g_scores|
g_scores.each { |subject,score| h_scores[subject] += score }
h_scores
end
end
end
def reconstruct(h)
h.map { |k,v| { student: k, scores: v.map { |subject, score|
{ subject: subject, score: score } } } }
end
例
arr = [
{ student: "a", scores: [{ subject: "math", quantity: 10 },
{ subject: "english", quantity: 5 }] },
{ student: "b", scores: [{ subject: "math", quantity: 1 },
{ subject: "english", quantity: 2 } ] },
{ student: "a", scores: [{ subject: "math", quantity: 2 },
{ subject: "science", quantity: 5 } ] }]
combine_scores(arr)
#=> [{ :student=>"a",
# :scores=>[{ :subject=>"math", :score=>12 },
# { :subject=>"english", :score=> 5 },
# { :subject=>"science", :score=> 5 }] },
# { :student=>"b",
# :scores=>[{ :subject=>"math", :score=> 1 },
# { :subject=>"english", :score=> 2 }] }]
說明
首先考慮兩個中間計算:
a = simplify(arr)
#=> [{ "a"=>{ "math"=>10, "english"=>5 } },
# { "b"=>{ "math"=> 1, "english"=>2 } },
# { "a"=>{ "math"=> 2, "science"=>5 } }]
h = update_scores(a)
#=> {"a"=>{"math"=>12, "english"=>5, "science"=>5}
# "b"=>{"math"=> 1, "english"=>2}}
然後
reconstruct(h)
返回上面所示的結果。
+簡化
arr.map do |h|
hash = Hash[h[:scores].map { |g| g.values }]
hash.default = 0
{ h[:student]=> hash }
end
這每個散列爲更簡單的一個映射。例如,arr
第一個元素:
h = { student: "a", scores: [{ subject: "math", quantity: 10 },
{ subject: "english", quantity: 5 }] }
被映射到:
{ "a"=>Hash[[{ subject: "math", quantity: 10 },
{ subject: "english", quantity: 5 }].map { |g| g.values }] }
#=> { "a"=>Hash[[["math", 10], ["english", 5]]] }
#=> { "a"=>{"math"=>10, "english"=>5}}
每個散列的默認值設置爲零簡化了更新步驟,該步驟如下。
+update_scores
對於散列a
陣列由simplify
返回,我們計算:
a.each_with_object({}) do |g,h|
h.update(g) do |_, h_scores, g_scores|
g_scores.each { |subject,score| h_scores[subject] += score }
h_scores
end
end
的a
(散列)的每個元素被合併成一個initially-空的散列,h
。由於update
(與merge!
相同)用於合併,因此修改了h
。如果兩個哈希共享相同的密鑰(例如「數學」),則將這些值相加;否則subject=>score
被添加到h
。
注意,如果h_scores
不具有鍵subject
,則:
h_scores[subject] += score
#=> h_scores[subject] = h_scores[subject] + score
#=> h_scores[subject] = 0 + score (because the default value is zero)
#=> h_scores[subject] = score
也就是說,從g_scores
的鍵 - 值對僅添加到h_scores
。
我用佔位符_
替換了代表主體的塊變量,以減少出錯的機率並通知讀者它在塊中未被使用。
+重建
的最後一步是要由update_scores
返回到原始數據結構,這是簡單的散列變換。
討論
如果你改變了數據結構,並滿足您的要求,您不妨考慮將其改爲由combine_scores
生產:
h = { "a"=>{ math: 10, english: 5 }, "b"=>{ math: 1, english: 2 } }
然後更新與分數:
g = { "a"=>{ math: 2, science: 5 }, "b"=>{ english: 3 }, "c"=>{ science: 4 } }
你只會以下幾點:
h.merge(g) { |_,oh,nh| oh.merge(nh) { |_,ohv,nhv| ohv+nhv } }
#=> { "a"=>{ :math=>12, :english=>5, :science=>5 },
# "b"=>{ :math=> 1, :english=>5 },
# "c"=>{ :science=>4 } }
數據即將作爲web服務請求。在繼續進行其餘的處理之前,我必須整理數據。 – user3075906 2014-12-05 16:55:38
@ user3075906我用更簡單的返回結構更新了我的答案,我認爲它更適合於所描述的場景。 – engineersmnky 2014-12-05 20:32:22