在具有困难匹配条件的两个数组之间查找匹配项

时间:2020-08-10 21:52:04

标签: ruby algorithm ruby-on-rails-5

我们有两个列表,一个事件列表,每个事件都有一个id,一个start_time和一个start_time_ragestart_time_rangestart_time周围放置了一个容差,以查找未命中的对象。

目标是过滤current_matches,以仅包括出现在先前比赛中的比赛。如果id匹配并且start_time在范围内,则列表中将出现一个项目。

要实现这一点,我有一个循环,但是随着我们数据大小的不断增加,它变得非常慢。我需要对其进行优化:

current_matches.select! do |match_row|
  previous_matches_collection.any? do |previous_match|
    previous_match[:item_id] == match_row[:item_id] &&
      previous_match[:start_time_range].include?(match_row[:start_time].to_f)
  end
end

如果只是我需要的item_id,我可以这样做:

previous_ids = previous_matches_collection.collect{|i| i[:item_id] }
current_matches.select! do |match_row|
   previous_ids.include?(match_row[:item_id])
end

但是我看不到在匹配每个项目中的时间条件时使用该方法的一种方法。

就数据而言,current_matches可以为300,而previous_matches_collection可以为1k +。有没有一种方法可以不重复遍历30万种组合?

编辑-样本数据:

previous_matches_collection = [
  { item_id: 1, start_time: 1597094395.1195982, start_time_range: (1597094393.6195982..1597094396.6195982) },
  { item_id: 1, start_time: 1597095083.116646, start_time_range: (1597095081.616646..1597095084.616646) },
  { item_id: 1, start_time: 1597095403.028223, start_time_range: (1597095401.528223..1597095404.528223) },
  { item_id: 2, start_time: 1597098035.056944, start_time_range: (1597098033.556944..1597098036.556944) },
  { item_id: 3, start_time: 1597096073.4109557, start_time_range: (1597096071.9109557..1597096074.9109557) },
  { item_id: 4, start_time: 1597094785.6987526, start_time_range: (1597094784.1987526..1597094787.1987526) },
  { item_id: 4, start_time: 1597098077.41271, start_time_range: (1597098075.91271..1597098078.91271) }
]


current_matches = [
  { item_id: 1, start_time: 1597094395.9195982 },
  { item_id: 1, start_time: 1597095085.116646, },
  { item_id: 1, start_time: 1597095404.228223, },
  { item_id: 2, start_time: 1597094395.1195982 },
  { item_id: 4, start_time: 1597094395.1195982 },
  { item_id: 6, start_time: 1597094395.1195982 },
  { item_id: 17, start_time: 1597094395.1195982 }
]

3 个答案:

答案 0 :(得分:2)

一个简单的优化方法是不使用any?查找正确的id。而是进行查找哈希,以获取具有正确previous_matches_collection O(1)的所有id元素。

要进行的另一种优化是使用cover?而不是include?。区别在于cover?仅将元素与范围的beginend进行比较。尽管include?1.succ #=> 2元素上使用succ(成功,例如begin)来生成一个集合,但该集合仍在寻找该元素。

("a".."z").include?("cc") #=> false
# is similar to:
# ["a", "b", "c", ..., "x", "y", "z"].include?("cc") #=> false

("a".."z").cover?("cc") #=> true
# is similar to:
# "a" <= "cc" && "cc <= "z" #=> true

上面的代码块演示了两者之间的区别。在您的方案中,您只想知道该值是否在范围内,因此cover?更适合并且是更快的选择。

start_time_ranges_by_item_id = previous_matches_collection
  .group_by { |match| match[:item_id] }
  .transform_values { |matches| matches.map { |match| match[:start_time_range] } }
start_time_ranges_by_item_id.default = []

现在使用start_time_ranges_by_item_id哈希构建,我们应该能够直接跳到相关范围并从那里开始检查。

current_matches.select! do |match_row|
  item_id, start_time = match_row.values_at(:item_id, :start_time)
  start_time_ranges = start_time_ranges_by_item_id[item_id]
  start_time_ranges.any? { |range| range.cover?(start_time) }
end

答案 1 :(得分:1)

只需创建一个Hash即可将先前的匹配项映射到它开始的时间戳。

然后对于每个current_match,执行[matroska @ 0xe446f0] Unknown option 'map'以获取时间戳(如果存在),然后测试时间戳是否满足条件。

如果fetch具有previous_matches_collection事物,而1000具有current_matches,则这是300哈希运算,每个哈希运算都是1300。这应该比您当前的解决方案更好地扩展。

答案 2 :(得分:1)

h = previous_matches_collection.each_with_object({}) do |g,h|
  id = g[:item_id]
  h[id] = (h[id] || []) << g[:start_time_range]
end
  #=> {1=>[1597094393.6195982..1597094396.6195982,
  #        1597095081.616646..1597095084.616646,
  #        1597095401.528223..1597095404.528223],
  #    2=>[1597098033.556944..1597098036.556944],
  #    3=>[1597096071.9109557..1597096074.9109557],
  #    4=>[1597094784.1987526..1597094787.1987526,
  #        1597098075.91271..1597098078.91271]}   
current_matches.select do |g|
  id = g[:item_id]
  h.key?(id) && h[id].any? { |a| a.cover?(g[:start_time]) }
end
  #=> [{:item_id=>1, :start_time=>1597094395.919598},
  #    {:item_id=>1, :start_time=>1597095404.228223}] 

请参见Range#cover?Enumerable#any?

如果第一个表达式h没有键id = g[:item_id],则h[id] = (h[id] || [])设置h[id] #=> [](因为(h[id] || []) => (nil || []) => []),之后h[id] << g[:start_time_range]被执行。一个人也可以写

h = previous_matches_collection.
    each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
  h[g[:item_id]] << g[:start_time_range]
end

如果h没有键h[k] = []时执行了h[k],则使对象h成为具有默认proc的,初始为空的哈希,并执行k 。请参见Hash::new的第三种形式。