2014-12-05 58 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 } }