我有一个带有以下结构的哈希:
{
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 公共键由下划线表示,表示它未用于块计算。