在Ruby中填充多维数组中的空白的最佳方法

时间:2011-01-25 22:20:00

标签: ruby arrays

我有一个类似于下面例子的多维数组,我想用Ruby的zip方法将它们组合在一起。当每个内部数组具有相同数量的元素时,我可以正常工作,但是当它们的长度不同时会遇到问题。

在下面的示例中,第二组在00:15缺少记录。 我如何填写此遗失记录?

我在考虑差距是什么?

  

这是构成一个时间戳的时间戳   间隙。看看我的第一个代码   我有评论的样本   差距在00:15。所有其他的   数组有一个散列   时间戳,所以我认为这是一个   “缺失记录”或“缺口”。该   时间戳确实可能是其他一些   独特的字符串所以他们的事实   相隔15分钟是无关紧要的。   价值观也无关紧要。

想到的唯一方法是将数组循环两次。第一次是构建uniq时间戳的数组,第二次是填写时间戳不存在的缺失记录。我很乐意编写这种方法,但它看起来有点笨拙,Ruby似乎总是以一种优雅而简洁的解决方案让我感到惊讶。

我从这开始:

values = [
  [
    {:timestamp => "2011-01-01 00:00", :value => 1},
    {:timestamp => "2011-01-01 00:15", :value => 2},
    {:timestamp => "2011-01-01 00:30", :value => 3}
  ],
  [ # There's a gap here at 00:15
    {:timestamp => "2011-01-01 00:00", :value => 1},
    {:timestamp => "2011-01-01 00:30", :value => 3}
  ],
  [
    {:timestamp => "2011-01-01 00:00", :value => 1},
    {:timestamp => "2011-01-01 00:15", :value => 2},
    {:timestamp => "2011-01-01 00:30", :value => 3}
  ]
]

我想以此结束:

values = [
  [
    {:timestamp => "2011-01-01 00:00", :value => 1},
    {:timestamp => "2011-01-01 00:15", :value => 2},
    {:timestamp => "2011-01-01 00:30", :value => 3}
  ],
  [ # The gap has been filled with a nil value
    {:timestamp => "2011-01-01 00:00", :value => 1},
    {:timestamp => "2011-01-01 00:15", :value => nil},
    {:timestamp => "2011-01-01 00:30", :value => 3}
  ],
  [
    {:timestamp => "2011-01-01 00:00", :value => 1},
    {:timestamp => "2011-01-01 00:15", :value => 2},
    {:timestamp => "2011-01-01 00:30", :value => 3}
  ]
]

当所有数组的大小相同时,values.transpose将产生:

[
  [
   {:value=>1, :timestamp=>"2011-01-01 00:00"}, 
   {:value=>1, :timestamp=>"2011-01-01 00:00"}, 
   {:value=>1, :timestamp=>"2011-01-01 00:00"}
  ], 
  [
    {:value=>2, :timestamp=>"2011-01-01 00:15"}, 
    {:value=>nil, :timestamp=>"2011-01-01 00:15"},
    {:value=>2, :timestamp=>"2011-01-01 00:15"}
  ], 
  [
    {:value=>3, :timestamp=>"2011-01-01 00:30"}, 
    {:value=>3, :timestamp=>"2011-01-01 00:30"}, 
    {:value=>3, :timestamp=>"2011-01-01 00:30"}
  ]
]

3 个答案:

答案 0 :(得分:1)

你概述的方法是正确的,但事实证明ruby非常适合优雅地采用这种方法。这样做可以做到,例如:

stamps = values.map{ |logs| logs.map{ |row| row[:timestamp] } }.flatten.uniq.sort
values.map!{ |logs| stamps.map { |ts| logs.select{ |row| row[:timestamp] == ts }.first || { :timestamp => ts, :value => nil } } }

第一行获取一个唯一时间戳列表(将所有日志映射到时间戳数组中,将数组展平为单个数组,仅保留唯一时间,并对时间戳进行排序)。

第二行填充空白(循环遍历日志,并且对于该日志中的每个时间戳使用那里有什么东西,否则插入新的零值行。)

答案 1 :(得分:1)

这是一个有效的解决方案;它找到所有时间戳,找到每个集合中缺少的时间戳,然后注入它们。在使用Ruby 1.9.2进行小改进的解决方案之后,请参阅注释:

values = [[
  {:timestamp => "2011-01-01 00:00", :value => 1},
  {:timestamp => "2011-01-01 00:15", :value => 2},
  {:timestamp => "2011-01-01 00:30", :value => 3}
],[
  {:timestamp => "2011-01-01 00:00", :value => 1},
  {:timestamp => "2011-01-01 00:30", :value => 3}
],[
  {:timestamp => "2011-01-01 00:00", :value => 1},
  {:timestamp => "2011-01-01 00:15", :value => 2},
  {:timestamp => "2011-01-01 00:30", :value => 3}
]]

all_stamps = values.flatten.map{|x| x[:timestamp]}.uniq.sort
values.each do |set|
  my_stamps = set.map{ |x| x[:timestamp] }.uniq
  missing   = all_stamps - my_stamps
  set.concat( missing.map{ |stamp| {timestamp:stamp, value:nil} } )
  set.replace( set.sort_by{ |x| x[:timestamp] } )
end

require 'pp'
pp values
#=> [[{:timestamp=>"2011-01-01 00:00", :value=>1},
#=>   {:timestamp=>"2011-01-01 00:15", :value=>2},
#=>   {:timestamp=>"2011-01-01 00:30", :value=>3}],
#=>  [{:timestamp=>"2011-01-01 00:00", :value=>1},
#=>   {:timestamp=>"2011-01-01 00:15", :value=>nil},
#=>   {:timestamp=>"2011-01-01 00:30", :value=>3}],
#=>  [{:timestamp=>"2011-01-01 00:00", :value=>1},
#=>   {:timestamp=>"2011-01-01 00:15", :value=>2},
#=>   {:timestamp=>"2011-01-01 00:30", :value=>3}]]

使用Ruby 1.9.2,您可以使用set.replace( set.sort_by{...} )替换set.sort_by!{ ... }。另请注意,我假设您在我的哈希文字中使用了Ruby 1.9(见missing.map...)。

答案 2 :(得分:0)

如果您使用的是Rails,请结帐Array#in_groups_of

%w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g}
["1", "2", "3"]
["4", "5", "6"]
["7", nil, nil]

http://weblog.rubyonrails.org/2006/3/1/new-in-rails-enumerable-group_by-and-array-in_groups_of