簡體   English   中英

基於密鑰合並哈希數組中的哈希值

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

合並重復對象的規則:

  • 將學生合並到匹配的“值”上(例如,學生“ a”,學生“ b”)
  • 將相同科目的學生分數相加(例如,學生a的數學分數2和10合並后變為12)

除了遍歷數組並查找重復項然后將它們組合在一起之外,是否有一種更簡單的方法來獲得類似於此的輸出?

從來沒聽說過。 如果您解釋這些數據來自何處,答案可能會有所不同,但僅基於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.

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