【问题标题】:Best way to fill in gaps within multidimensional array in Ruby在 Ruby 中填补多维数组中的空白的最佳方法
【发布时间】:2011-01-25 22:19:59
【问题描述】:

我有一个类似于下面示例的多维数组,我想使用 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"}
  ]
]

【问题讨论】:

  • 更清楚地定义什么是“差距”。它是否对时间戳时间进行了一些检查以确定这一点?价值?还有什么?
  • 好问题本。我用关于什么构成差距的解释更新了我的问题。希望这会有所帮助。

标签: ruby arrays


【解决方案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 } } }

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

第二行填补了空白(循环通过日志,对于该日志中的每个时间戳,如果有什么,则使用那里的内容,否则插入新的 nil 值行)。

【讨论】:

  • 又好又短(如果行长); select 的使用似乎对性能很危险,但是在最坏的情况下使用 O(n^2)。
  • @Phrogz,是的,它在计算效率方面非常糟糕,但除非你的 values 数组有几百万行,或者你使用的是 1995 年之前建造的计算机,否则实际计算速度仍然只有几毫秒。我很久以前就 ruby​​ 学到的东西是不要试图节省处理器周期,除非你真的确实有理由——不要在瓶颈存在之前就寻找它们。单个数据库访问的延迟远远超过现代计算机上数千个处理器周期。单个网络访问的延迟超过数百万。
  • 性能绝对是一个考虑因素,所以我对你们提供的答案进行了基准测试。我期待@Phrogz 的代码根据您的 cmets 更高效,但事实恰恰相反。 @Ben 的代码运行速度始终快 2 倍。我在 OS X 上使用 Ruby 1.8.7。这是我的基准代码,也许我遗漏了什么? gist.github.com/03ea4920421986700257
  • @Beerlington,Phrogz 上次我没有发布他的答案,所以我现在才看到它。看起来他的解决方案实例化了更多的 ruby​​ 对象,最重要的是,整个外部数组上的 flatten 创建了一个非常大的 ruby​​ 对象(相对于我们正在使用的所有其他对象的大小)。每次循环时,它还使用计算量大的操作(数组减法很重要)。我的代码在计算上很糟糕,但他的代码在计算上更糟糕。
  • 如果你真的想加快这个操作,如果可能的话,我建议你改变原始值输入的数据结构。
【解决方案2】:

这是一个可行的解决方案;它找到所有时间戳,在每个集合中找到丢失的时间戳,然后注入它们。请参阅解决方案后的 cmets,了解您可以使用 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...)。

【讨论】:

    【解决方案3】:

    如果您使用的是 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-20
      • 2022-11-30
      • 1970-01-01
      • 1970-01-01
      • 2013-05-10
      • 2011-03-07
      相关资源
      最近更新 更多