Ruby 合并 n 个哈希元素并获取中间键



我有一个带有以下结构的哈希:

{
timestamp1 => { attr1 => 1, attri2 => 2, attr3 => 3},
timestamp2 => { attr1 => 1, attri2 => 2, attr3 => 3},
timestamp3 => { attr1 => 1, attri2 => 2, attr3 => 3},
...
}

我所做的是将 n 个元素相加,并取中间时间戳。 因此,例如在这种情况下,如果将 3 天合并在一起,我希望得到以下结果:

{
timestamp2 => {attr1 => 3. attr2 => 6, attr3 => 9}
timestamp5 => ... # Here the next summed values
...
}

我现在这样做了 allready 并且它正在工作,但代码并不好,它只是合并第一个和最后一个元素,直到只剩下中间的元素:

hash.each_slice(n) do |part|
iterator = 0
while(part.length > 1) do
hash_a = part.first.last
hash_b = part.last.last
new_values = hash_a.merge(hash_b){ | key, oldval, newval| (newval + oldval)
if iteration % 2 == 0
part[0][1] = new_values
part.delete_at(part.count-1)
else 
part.delete_at(0)
part[part.count-1][1] = new_values
end
iteration += 1
end

注意:我知道这段代码不漂亮,但你不必纠正它,它只是一个示例,不是我的实际代码。

现在我想知道什么。有没有更好的方法可以通过一些减少、选择、group_by或合并来实现这一点。如果我能摆脱 while 循环,那就太好了。

提前谢谢你。

N = 3
hash.each_slice(N).map do |part|
[part[part.size / 2].first, part.each_with_object({}) do |(_, v), acc|
acc.merge!(v) { |_, v1, v2| v1 + v2 }
end]
end.to_h

代码

def group(h,n)
h.each_slice(n).with_object({}) do |a,g|
key = a[(a.size-1)/2].first
a.each {|k,f| g.update(key=>f) {|_,oh,nh| oh.merge(nh) {|_,ov,nv| ov+nv}}}
end
end  

这不会h突变。

为了演示代码,我将使用包含键文字的哈希。

h = {
:timestamp1 => { :attr1 => 1, :attri2 => 2, :attr3 => 3},
:timestamp2 => { :attr1 => 1, :attri2 => 2, :attr3 => 3},
:timestamp3 => { :attr1 => 1, :attri2 => 2, :attr3 => 3},
:timestamp4 => { :attr1 => 4, :attri2 => 5, :attr3 => 6},
:timestamp5 => { :attr1 => 4, :attri2 => 5, :attr3 => 6},
:timestamp6 => { :attr1 => 4, :attri2 => 5, :attr3 => 6},
}
group(h,1) == h
#=> true 
group(h,2)
#=> {:timestamp1=>{:attr1=>2,  :attri2=>4,  :attr3=>6},
#    :timestamp3=>{:attr1=>5,  :attri2=>7,  :attr3=>9},
#    :timestamp5=>{:attr1=>8,  :attri2=>10, :attr3=>12}} 
group(h,3)
#=> {:timestamp2=>{:attr1=>3,  :attri2=>6,  :attr3=>9},
#    :timestamp5=>{:attr1=>12, :attri2=>15, :attr3=>18}} 
group(h,4)
#=> {:timestamp2=>{:attr1=>7,  :attri2=>11, :attr3=>15},
#    :timestamp5=>{:attr1=>8,  :attri2=>10, :attr3=>12}} 
group(h,5)
#=> {:timestamp3=>{:attr1=>11, :attri2=>16, :attr3=>21},
#    :timestamp6=>{:attr1=>4,  :attri2=>5,  :attr3=>6}} 
group(h,6)
#=> {:timestamp4=>{:attr1=>15, :attri2=>21, :attr3=>27}}

解释

我使用了 Hash#update(又名Hash.merge!)和 Hash#merge 的形式,它们使用一个块来确定正在合并的两个哈希中存在的键的值。有关块变量_(公共键1)、oh("旧哈希")、nh("新哈希")、ov("旧值")和nv("新值")的解释,请参阅文档。

步骤如下。

n = 3
enum0 = h.each_slice(n)
#=> #<Enumerator: {:timestamp1=>{:attr1=>1, :attri2=>2, :attr3=>3},
#                  :timestamp2=>{:attr1=>1, :attri2=>2, :attr3=>3},
#                  ...
#                  :timestamp6=>{:attr1=>4, :attri2=>5, :attr3=>6}}
#     :each_slice(3)> 
enum1 = enum0.with_object({})
#=> #<Enumerator:
#     #<Enumerator: {:timestamp1=>{:attr1=>1, :attri2=>2, :attr3=>3},
#                    :timestamp2=>{:attr1=>1, :attri2=>2, :attr3=>3},
#                    ...
#                    :timestamp6=>{:attr1=>4, :attri2=>5, :attr3=>6}}
#     :each_slice(3)>:with_object({})>

我们可以检查将由enum1生成并通过将enum1转换为数组传递给块的(两个)元素。

enum1.to_a
#=> [
#     [
#       [
#         [:timestamp1, {:attr1=>1, :attri2=>2, :attr3=>3}],
#         [:timestamp2, {:attr1=>1, :attri2=>2, :attr3=>3}],
#         [:timestamp3, {:attr1=>1, :attri2=>2, :attr3=>3}]],
#         {}
#       ],
#     [
#       [
#         [:timestamp4, {:attr1=>4, :attri2=>5, :attr3=>6}],
#         [:timestamp5, {:attr1=>4, :attri2=>5, :attr3=>6}],
#         [:timestamp6, {:attr1=>4, :attri2=>5, :attr3=>6}]],
#         {}
#       ]
#     ] 

空数组由块变量g表示。将由该方法生成并返回。enum1生成的第一个元素被传递给块,并使用并行赋值分配两个块变量。

a,g = enum1.next
#=> [
#     [
#       [:timestamp1, {:attr1=>1, :attri2=>2, :attr3=>3}],
#       [:timestamp2, {:attr1=>1, :attri2=>2, :attr3=>3}],
#       [:timestamp3, {:attr1=>1, :attri2=>2, :attr3=>3}]
#     ]
#     {}
#   ]
a #=> [
#     [:timestamp1, {:attr1=>1, :attri2=>2, :attr3=>3}],
#     [:timestamp2, {:attr1=>1, :attri2=>2, :attr3=>3}],
#     [:timestamp3, {:attr1=>1, :attri2=>2, :attr3=>3}]
#   ] 
g #=> {} 

现在可以执行块计算。

key = a[(a.size-1)/2].first
#=> a[2].first
#=> [:timestamp2, {:attr1=>1, :attri2=>2, :attr3=>3}].first 
#=> :timestamp2 
enum2 = a.each
#=> #<Enumerator: [[:timestamp1, {:attr1=>1, :attri2=>2, :attr3=>3}],
#                  [:timestamp2, {:attr1=>1, :attri2=>2, :attr3=>3}],
#                  [:timestamp3, {:attr1=>1, :attri2=>2, :attr3=>3}]]
#     :each> 
enum2.to_a # for display purposes only
#=> [[:timestamp1, {:attr1=>1, :attri2=>2, :attr3=>3}],
#    [:timestamp2, {:attr1=>1, :attri2=>2, :attr3=>3}],
#    [:timestamp3, {:attr1=>1, :attri2=>2, :attr3=>3}]] 
k,f = enum2.next
#=> [:timestamp1, {:attr1=>1, :attri2=>2, :attr3=>3}] 
k #=> :timestamp1 
f #=> {:attr1=>1, :attri2=>2, :attr3=>3} 
g.update(key=>f) {|_,oh,nh|
oh.merge(nh) {|_,ov,nv| g.update(key=>f) {|_,oh,nh| oh.merge(nh) {|_,ov,nv| ov+nv}}}}
#=> g.update(:timestamp2=>{:attr1=>1, :attri2=>2, :attr3=>3} ) {|_,oh,nh|
#     oh.merge(nh) {|_,ov,nv| ov+nv}}}}
#=> {:timestamp2=>{:attr1=>1, :attri2=>2, :attr3=>3}}

然后

k,f = enum2.next
#=> [:timestamp2, {:attr1=>1, :attri2=>2, :attr3=>3}] 
k #=> :timestamp2 
f #=> {:attr1=>1, :attri2=>2, :attr3=>3} 
g.update(key=>f) {|_,oh,nh| oh.merge(nh) {|_,ov,nv| ov+nv}}
# g.update(:timestamp2=>{:attr1=>1, :attri2=>2, :attr3=>3} {|_,oh,nh|
#   oh.merge(nh) {|_,ov,nv| ov+nv}}
#=> {:timestamp2=>{:attr1=>2, :attri2=>4, :attr3=>6}} 

其余计算类似。

1 公共键由下划线表示,表示它未用于块计算。

最新更新