[英]Merging hash values in an array of hashes based on key
我有一系列類似於以下的哈希值:
[
{"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 } ] }
]
除了遍歷數組並查找重復項然后將它們組合在一起之外,是否有一種更簡單的方法來獲得類似於此的輸出?
[
{"student": "a","scores": [{"subject": "math","quantity": 12},{"subject": "english", "quantity": 5},{"subject": "science", "quantity": 5 } ]},
{"student": "b", "scores": [{"subject": "math","quantity": 1 }, {"subject": "english","quantity": 2 } ]}
]
合並重復對象的規則:
除了遍歷數組並查找重復項然后將它們組合在一起之外,是否有一種更簡單的方法來獲得類似於此的輸出?
從來沒聽說過。 如果您解釋這些數據來自何處,答案可能會有所不同,但僅基於Hash
對象Array
,我認為您將進行迭代和合並。
雖然不優雅,但您可以使用這樣的解決方案
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 } ] }
]
#Group the array by student
arr.group_by{|student| student["student"]}.map do |student_name,student_values|
{"student" => student_name,
#combine all the scores and group by subject
"scores" => student_values.map{|student| student["scores"]}.flatten.group_by{|score| score["subject"]}.map do |subject,subject_values|
{"subject" => subject,
#combine all the quantities into an array and reduce using `+`
"quantity" => subject_values.map{|h| h["quantity"]}.reduce(:+)
}
end
}
end
#=> [
{"student"=>"a", "scores"=>[
{"subject"=>"math", "quantity"=>12},
{"subject"=>"english", "quantity"=>5},
{"subject"=>"science", "quantity"=>5}]},
{"student"=>"b", "scores"=>[
{"subject"=>"math", "quantity"=>1},
{"subject"=>"english", "quantity"=>2}]}
]
我知道您指定了預期的結果,但我想指出的是,使輸出更簡單使代碼更簡單。
arr.map(&:dup).group_by{|a| a.delete("student")}.each_with_object({}) do |(student, scores),record|
record[student] = scores.map(&:values).flatten.map(&:values).each_with_object(Hash.new(0)) do |(subject,score),obj|
obj[subject] += score
obj
end
record
end
#=>{"a"=>{"math"=>12, "english"=>5, "science"=>5}, "b"=>{"math"=>1, "english"=>2}}
通過這種結構,獲取學生就像調用.keys
一樣容易,並且分數也同樣簡單。 我在想類似的東西
above_result.each do |student,scores|
puts student
scores.each do |subject,score|
puts " #{subject.capitalize}: #{score}"
end
end
end
輸出的控制台將是
a
Math: 12
English: 5
Science: 5
b
Math: 1
English: 2
在這種情況下,有兩種常見的匯總值方法。 首先是采用方法Enumerable#group_by ,就像@engineersmnky在他的回答中所做的那樣。 第二個方法是使用方法Hash#update (也稱為merge!
)的形式構建哈希,該方法使用一個塊來解析兩個合並的哈希中都存在的鍵的值。 我的解決方案使用后一種方法,不是因為我不喜歡group_by
,而是只是向您展示了另一種方法。 (如果Engineersmnky使用過update
,那么我會選擇group_by
。)
您正在使用的特定數據結構使您的問題有些復雜。 我發現,可以通過首先將數據轉換為不同的結構,更新分數,然后將結果轉換回您的數據結構來簡化該解決方案,並使其更易於遵循。 您可能需要考慮更改數據結構(如果可以的話)。 我已經在“討論”部分解決了該問題。
碼
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
(散列)被合並到一個最初為空的散列, 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
生成的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 } }
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.