【问题标题】:Efficient way of matching 2 hashes having maximum similarity匹配具有最大相似度的 2 个哈希的有效方法
【发布时间】:2021-05-11 17:22:23
【问题描述】:
[
  {"id"=>11486, "opt_ids"=> [3545, 3546, 3548, 3550] },
  {"id"=>12624, "opt_ids"=> [3545, 3396, 3548, 3550] }, 
  {"id"=>14588, "opt_ids"=> [3394, 3396, 3397, 3399] }, 
  {"id"=>14589, "opt_ids"=> [3394, 3545, 3398, 3548] }, 
  {"id"=>14590, "opt_ids"=> [3394, 3396, 3397, 3399, 3545, 3547, 3548, 3551, 3653, 3655, 3657, 3660, 3772, 3775, 3777, 3778]},
  .....
  .....
  ...
  ...

]

有没有一种有效的方法可以找到 2 个 ID 相似 option_id 的最大数量?

上面例子的答案是

[[11486, 12624], [14588, 14590]]

我已经尝试过的是-

  1. 获取每个哈希并将其 opt_ids 与数组中其他剩余哈希的 opt_ids 进行比较。
  2. 当前哈希的 opt_ids 最匹配的哈希我将这 2 个 id 配对。
  3. 所以我实际上循环每个哈希的次数与数组中哈希的数量一样多 - O(n^2)

【问题讨论】:

  • 以上示例的答案是什么?你试过什么? “循环”是模糊的。
  • @Schwern 谢谢你的提问,我已经更新了我的问题。如果现在更清楚了,请告诉我?
  • 好的,谢谢。你有多少数据,它多久改变一次?你在寻找什么样的表现?将其放入 SQLite 可能是有意义的。
  • @Schwern 该数据已经是数据库的一部分,我从那里汇总了它。目前,数组中的哈希数可以在 50 到 500 之间变化。我正在寻找的性能是基于最大相似 option_ids 匹配 2 个 id,我不必遍历每个哈希。 option_id 的数量每周都会增加。
  • 在 SQL 中执行它可能会更容易和更快。你能展示一下原来的表格吗?

标签: ruby-on-rails ruby algorithm hashmap ruby-hash


【解决方案1】:

sqlfiddle我把表格放在这里,数据和上面一样。我已经从许多不同的表中创建了一个视图。

用SQL做,这是它擅长的。

使用自联接获取每对的重叠数。

select
  a.emp_id emp_id1,
  b.emp_id emp_id2,
  count(a.option_id) as overlap
from data a
join data b on
  -- Ensure we count each pair only once
  a.emp_id < b.emp_id and
  a.option_id = b.option_id
group by a.emp_id, b.emp_id

然后将其用作 CTE 以选择重叠最多的对。

with overlaps as (
  select
    a.emp_id emp_id1,
    b.emp_id emp_id2,
    count(a.option_id) as overlap
  from data a
  join data b on
    a.emp_id < b.emp_id and
    a.option_id = b.option_id
  group by a.emp_id, b.emp_id
)
select *
from overlaps
where overlap = (
  select max(overlap)
  from overlaps
)

只要您被编入索引,这应该比将所有数据提取到 Ruby 中执行得更好。

create index idx_option_emp_ids on data(option_id, emp_id);

即使没有索引,它的性能也应该比将其全部放入 Ruby 中要好得多。

【讨论】:

  • 谢谢你的回答,让我分析一下再回来。
【解决方案2】:

假设给定一个非负整数max_diff,这样两个整数n1n2被认为是“相似的”如果

|n1 - n2| <= max_diff

进一步假设我们得到以下哈希数组:

arr = [
  { "id"=>11486, "opt_ids"=> [3545, 3546, 3548, 3550] },
  { "id"=>12624, "opt_ids"=> [3545, 3396, 3548, 3550] }, 
  { "id"=>14588, "opt_ids"=> [3394, 3396, 3397, 3399] }, 
  { "id"=>14589, "opt_ids"=> [3394, 3545, 3398, 3548] }, 
  { "id"=>14590, "opt_ids"=> [3394, 3396, 3397, 3399] },
  { "id"=>14591, "opt_ids"=> [3545, 3547, 3548, 3551] },
  { "id"=>14592, "opt_ids"=> [3653, 3655, 3657, 3660] },
  { "id"=>14593, "opt_ids"=> [3772, 3775, 3777, 3778] }
]

并指定:

max_diff = 3

我将假设我们将根据我的定义“相似”的元素对数 [h1["opt_ids"][i], h2["opt_ids"][j]] 对这些哈希对(以及因此关联的 "id" 值对)进行排名更多。这可能与您的想法不一致,但它可能是一个有用的起点。我简要说明max_diff 始终为零的情况。


考虑"opt_ids" 的值作为散列arr[0]arr[3]

a0 = arr[0]["opt_ids"]
  #=> [3545, 3546, 3548, 3550]
a3 = arr[3]["opt_ids"]
  #=> [3394, 3545, 3398, 3548]

如下表所示,a0中的3545类似于a335453548这两个元素。类似地,a0 中的354635483550 分别类似于a3221 元素,分别表示哈希arr[0] 和@9876可以认为相似度得分为7

  a0      a3      #
____________________
3545: 3545, 3548 (2)
3546: 3545, 3548 (2)
3548: 3545, 3548 (2)
3550: 3548       (1)

我们可以使用下面的辅助方法来实现这个计数。

def count_similar(a1, a2, max_diff)
  a1.product(a2).count { |n1,n2| (n1-n2).abs <= max_diff }
end

例如,

count_similar(a0, a3, max_diff)
  #=> 7

如果我误解了这个问题并且max_diff 始终为零,并且如果所有数组arr[i]["opt_ids"] 都包含唯一元素(正如问题示例中所做的那样),则可以这样写

def count_similar(a1, a2)
  (a1 & a2).size
end

并删除 max_diff 参数。


我们现在可以按如下方式计算 id 对的相似性度量:

def similarities(arr, max_diff)
  arr.combination(2)
     .map do |h1,h2|
       [[h1["id"], h2["id"]],
        count_similar(h1["opt_ids"], h2["opt_ids"], max_diff)]
      end.to_h
end
similarities(arr, max_diff)
  #=> {[11486, 12624]=> 9, [11486, 14588]=>0, [11486, 14589]=> 7, [11486, 14590]=>0,
  #    [11486, 14591]=>13, [11486, 14592]=>0, [11486, 14593]=> 0, [12624, 14588]=>4,
  #    [12624, 14589]=> 7, [12624, 14590]=>4, [12624, 14591]=>10, [12624, 14592]=>0,
  #    [12624, 14593]=> 0, [14588, 14589]=>6, [14588, 14590]=>14, [14588, 14591]=>0,
  #    [14588, 14592]=> 0, [14588, 14593]=>0, [14589, 14590]=> 6, [14589, 14591]=>7,
  #    [14589, 14592]=> 0, [14589, 14593]=>0, [14590, 14591]=> 0, [14590, 14592]=>0,
  #    [14590, 14593]=> 0, [14591, 14592]=>0, [14591, 14593]=> 0, [14592, 14593]=>0}

然后我们可能会计算度量,以确定具有最高相似度分数的五对:

similarities(arr, max_diff).max_by(5, &:last)
  #=> [[[14588, 14590], 14], [[11486, 14591], 13], [[12624, 14591], 10],
  #    [[11486, 12624],  9], [[12624, 14589],  7]]

这表明1458814590这对id(来自arr[2]arr[4])具有最高的相似度得分,为14

【讨论】:

  • 谢谢你的回答,让我分析一下再回来。
  • 感谢您在这个方向上指导我,虽然有点不同,但它帮助我有了另一个视角@cary
【解决方案3】:

您可以检查数组中的差异。因此,如果它返回空数组或元素数量较少,则意味着它们相等或几乎相等。

例如:

a = [1,2,3,4,5]
b = [1,3,2,5,4]
c = [2,4,3,5,9]
d = [2,7,8,8,9]

a-b = []            // similar
a-c = [1]           // almost similar
a-d = [1,3,4,5]     // least similar

【讨论】:

  • 正确,但我仍然需要将 a 与 b、c 和 d 进行一一比较。试图避免这种情况。
  • 虽然数组有不同的长度。所以一个空的差异并不意味着什么:a - b # =&gt; [] 可能只是意味着a 是空的。设置交点 (&amp;) 可能是一个更好的主意。
猜你喜欢
  • 2022-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-15
  • 1970-01-01
  • 2014-08-26
  • 2015-05-29
  • 1970-01-01
相关资源
最近更新 更多