2014-12-05 60 views
0

我有類似這樣的散列數組的數組合並哈希值:在哈希的基於關鍵

[ 
    {"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 } ]} 
] 

規則合併重複的對象:

  • 學生上匹配「值」(例如學生「一」,學生「B」)被添加在相同的受試者
  • 學生的分數合併(例如,一個學生的數學成績2和10變成12時合併)

回答

2

有沒有得到類似這樣的輸出,除了循環t的更簡單的方法通過數組找到一個重複然後合併它們?

不,我知道的。如果你能解釋一下其中該數據comeing形式的答案可能是不同的,但只是基於HashArray的對象我想你會haev迭代和組合。

雖然這是不優雅,你可以使用這樣

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 
+0

數據即將作爲web服務請求。在繼續進行其餘的處理之前,我必須整理數據。 – user3075906 2014-12-05 16:55:38

+0

@ user3075906我用更簡單的返回結構更新了我的答案,我認爲它更適合於所描述的場景。 – engineersmnky 2014-12-05 20:32:22

0

有在這種情況下聚集值的兩種常用方法。首先是採用方法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 } }