Ruby:在转换数组中的对象后传递键/值

鉴于数据:

data = [ {"id":14, "sort":1, "content":"9", foo: "2022"}, {"id":14, "sort":4, "content":"5", foo: "2022"}, {"id":14, "sort":2, "content":"1", foo: "2022"}, {"id":14, "sort":3, "content":"0", foo: "2022"}, {"id":15, "sort":4, "content":"4", foo: "2888"}, {"id":15, "sort":2, "content":"1", foo: "2888"}, {"id":15, "sort":1, "content":"3", foo: "2888"}, {"id":15, "sort":3, "content":"3", foo: "2888"}, {"id":16, "sort":1, "content":"8", foo: "3112"}, {"id":16, "sort":3, "content":"4", foo: "3112"}, {"id":16, "sort":2, "content":"4", foo: "3112"}, {"id":16, "sort":4, "content":"9", foo: "3112"} ] 

得到的内容由他们的sortids连接:

 formatted = data.group_by { |d| d[:id]}.transform_values do |value_array| value_array.sort_by { |b| b[:sort] } .map { |c| c[:content] }.join end puts formatted #=> {14=>"9105", 15=>"3134", 16=>"8449"} 

我知道foo存在于value_array但想知道如何在formatted变量中包含foo ,以便我可以映射它以获得所需的输出或者是否可能?

期望的输出:

 [ {"id":14, "concated_value":"9105", foo: "2022"}, {"id":15, "concated_value":"3134", foo: "2888"}, {"id":16, "concated_value":"8449", foo: "3112"} ] 

因为:foo是唯一的:id 。 你可以这样做:

 data.group_by {|h| h[:id]}.map do |_,sa| sa.map(&:dup).sort_by {|h| h.delete(:sort) }.reduce do |m,h| m.merge(h) {|key,old,new| key == :content ? old + new : old } end.tap {|h| h[:concated_value] = h.delete(:content) } end #=> [ # {"id":14, foo: "2022", "concated_value":"9105"}, # {"id":15, foo: "2888", "concated_value":"3134"}, # {"id":16, foo: "3112", "concated_value":"8449"} # ] 
  • 首先我们按ID分组。 group_by {|h| h[:id]}
  • 然后我们复制组中的哈希值(以免破坏原始组合)。 map(&:dup)
  • 然后我们按排序排序并同时删除它。 .sort_by {|h| h.delete(:sort) }
  • 然后我们将这些组合并在一起并仅连接内容密钥。 m.merge(h) {|key,old,new| key == :content ? old + new : old }
  • 然后我们只需将content的密钥更改为concated_value tap {|h| h[:concated_value] = h.delete(:content) } tap {|h| h[:concated_value] = h.delete(:content) }

我们可以使用value_array中的第一个值来获取我们的:id&:foo值

 formatted = data.group_by { |d| d[:id]}.values.map do |value_array| concated_value = value_array.sort_by { |b| b[:sort] } .map { |c| c[:content] }.join value_array.first.slice(:id, :foo) .merge concated_value: concated_value end 

我认为这是reduce一个很好的用例,因为在分组后你需要首先从group_by删除结果[ID, VALUES]数组中的[ID, VALUES]然后返回VALUES部分的简化版本 – 这一切都可以在没有任何ActiveSupport等依赖项:

 data .group_by{ |d| d[:id] } # Get an array of [ID, [VALUES]] .reduce([]) do |a, v| # Reduce it into a new empty array # Append a new hash to the new array a << { id: v[1].first[:id], # Just take the ID of the first entry foo: v[1].first[:foo], # Dito for foo concatenated: v[1] .sort_by{ |s| s[:sort] } # now sort all hashes by its sort key .collect{ |s| s[:content] } # collect the content .join # and merge it into a string } end 

输出:

 [{:id=>14, :foo=>"2022", :concatenated=>"9105"}, {:id=>15, :foo=>"2888", :concatenated=>"3134"}, {:id=>16, :foo=>"3112", :concatenated=>"8449"}] 

编辑

当我开始编写前面的解决方案时,我还有其他一些方法, reduce并不是必需的,因为group_by之后的数组大小不会改变,因此map就足够了。

但是在重写代码时,我认为使用所有键创建一个新哈希并复制VALUES中第一个哈希的所有值有点太多工作,所以拒绝开销键会更容易:

 keys_to_ignore = [:sort, :content] data .group_by{ |d| d[:id] } # Get an array of [ID, [VALUES]] .map do |v| v[1] .first # Take the first hash from [VALUES] .merge({'concatenated': v[1] # Insert the concatenated values .sort_by{ |s| s[:sort] } # now sort all hashes by its sort key .collect{ |s| s[:content] } # collect the content .join # and merge it into a string }) .select { |k, _| !keys_to_ignore.include? k } end 

产量

 [{:id=>14, :foo=>"2022", :concatenated=>"9105"}, {:id=>15, :foo=>"2888", :concatenated=>"3134"}, {:id=>16, :foo=>"3112", :concatenated=>"8449"}] 

在线演示在这里

即使没有Rails,这也可以工作:

 $irb> formatted = [] $irb> data.sort_by!{|a| a[:sort]}.map {|z| z[:id]}.uniq.each_with_index { |id, index| formatted << {id: id, concated_value: data.map{|c| (c[:id] == id ? c[:content] : nil)}.join, foo: data[index][:foo]}} $irb> formatted [{:id=>14, :concated_value=>"9105", :foo=>"2022"}, {:id=>15, :concated_value=>"3134", :foo=>"2888"}, {:id=>16, :concated_value=>"8449", :foo=>"3112"}] 
 data.sort_by { |h| h[:sort] }. each_with_object({}) do |g,h| h.update(g[:id]=>{ id: g[:id], concatenated_value: g[:content].to_s, foo: g[:foo] }) { |_,o,n| o.merge(concatenated_value: o[:concatenated_value]+n[:concatenated_value]) } end.values #=> [{:id=>14, :concatenated_value=>"9105", :foo=>"2022"}, # {:id=>15, :concatenated_value=>"3134", :foo=>"2888"}, # {:id=>16, :concatenated_value=>"8449", :foo=>"3112"}] 

这使用Hash #update (aka merge! )的forms,它使用一个块来确定两个哈希中合并的键的值(这里是:id的值)。 有关三个块变量(此处为_on )的说明,请参阅doc。

注意values的接收者(在结尾处)如下。

 { 14=>{ :id=>14, :concatenated_value=>"9105", :foo=>"2022" }, 15=>{ :id=>15, :concatenated_value=>"3134", :foo=>"2888" }, 16=>{ :id=>16, :concatenated_value=>"8449", :foo=>"3112" } }