【问题标题】:Algorithm to reduce and limit memory usage of a set combination search减少和限制集合组合搜索的内存使用的算法
【发布时间】:2020-08-06 08:54:10
【问题描述】:

我想减少和限制在将集合中的所有项目组合相互比较时使用的内存占用,其中集合可以增长到任何大小。我正在考虑将集合分成更小的部分,但由于需要所有组合,我无法看到如何做到这一点,而最终在某个时候需要内存中的所有组合。

例如如果我有 A、B、C、D、E、F 项,我需要比较所有不同的组合

  A B C D E F
A  
B x
C x x
D x x x
E x x x x
F x x x x x

等等。这些集合通常包含 100 到 10,000 个文档,其中包含要使用各种启发式检查的元数据。

我目前正在实现它(无需一次将所有项目加载到内存中),方法是在两个相同的嵌套数据库查询中使用一个游标在两个相同的嵌套数据库查询中对集合进行两次迭代,以遍历组合的两个维度。从理论上讲,这在规模上是无限的,并且使用的内存很少,但感觉有点浪费,因为我将查询每个项目 N+1 次(其中 N 是集合的大小)。当然,它给数据库带来了一些压力。

这是当前的简单算法:

  • 为集合准备查询
  • 当 cursor.next A:
    • 为集合准备查询,不包括 A
    • 当 cursor.next B:
      • 比较 A 和 B

这导致序列 AB、AC、AD、AE、AF、BA、BC、BD 等,而我一次只在内存中保存两个文档,但它有两个问题。首先,内部查询发生 N 次。如果我没有在查询中排除 A,那将是相同的查询重新运行 N 次,这似乎很浪费。第二个问题是排列,所以我需要做两倍的工作并且必须对结果进行重复数据删除。

我曾考虑在进展过程中缓存这些项目,但意识到它只会增长到最终包含所有项目以完成所有组合。因此,这导致了一个基本思想,即仅将整个集合选择一次到内存中并从一个数组中扫描组合。这很简单,但当然不可扩展。

那么,有没有一种算法可以在任何时候只使用集合的分区来比较集合中不同对的所有组合,从而保证总和覆盖所有组合?

我天真地想不出一个。例如如果你把它分成两半,你仍然需要在某个时候加载两个子集的组合。也许是“所有可能性”和“所有偶数”,但这只会使可扩展性问题减半。

  B D F
B 
D x  
F x x  

然后

  A C E
A  
C x  
E x x 

但这错过了一半的连击。

我感觉这在理论上是不可能的,但我想知道那里是否有一个聪明的数学技巧。或者我错过了一些非常明显的东西。

更新 - 在最初的 cmets 之后,问题已编辑并希望得到澄清。

Nikos.M 给了我预生成组合对的“索引”的想法,然后我可以查询每一对。

我最初希望实现 MicSim 所说的一些批量大小的中间地带的“最佳位置”。所以不是在一个极端原子加载每一对,也不是在另一端加载整个集合,而是一些固定大小的批处理方法来保持处理足迹平坦。

【问题讨论】:

  • 有一种方法可以系统地生成n个对象的所有组合或子集,并且内存中只有一个组合处于活动状态。如果这解决了问题,请告诉我我发布算法
  • 但是我需要澄清一下你所说的组合到底是什么意思?您发布的示例既不是组合(即从 n 中选择 k)也不是子集(n 项的所有子集),除非您错过了什么
  • 我认为您正在尝试迭代所有无序的项目对,而不是一次将所有项目加载到内存中,并最大限度地减少项目的加载次数。这是对您问题的正确解释吗?
  • @NikosM。是的,我需要这些组合。我只是将子集作为可扩展性问题的一种可能解决方案放在那里,但对想法持开放态度。
  • @kaya3 是的,非常简洁。我希望我写的是那一句话,而不是我漫无边际的问题!

标签: javascript algorithm combinations scalability set-theory


【解决方案1】:

更新 ===========================================

如果我正确理解了这个问题。没有没有方法将集合划分为不重叠独立子集,以减少内存使用,因为根据定义一切都必须与其他一切进行比较。所以没有这样的切割可以分割集合。但是,通过使用组合可以最大限度地减少影响,在每个实例中只有 2 个活动文档在内存中,并在下一个组合实际引用不同文档时更新文档(从之前的组合引用 2 个不同的文档实际上很少见,平均只有一个文档引用从一个组合更改为下一个组合)。此外,通过使用以下组合方法,进程可以在某个点停止并将最后一个组合保存在磁盘上,并在稍后的某个时间从该点恢复进程。所以它可以是高效的,但某种意义上仍然存在一种N+1 problem。对于组合方法,请参阅下面的原始答案。

============================================== ======

有一些算法可以系统地一个接一个地生成组合,您不需要一次将所有组合存储在内存中,而是在每个瞬间只激活一个。

该算法的工作原理是将一个组合作为输入并返回下一个组合(例如按字典顺序),直到到达最后一个组合。

n(其中n >= 2)选择2的初始组合是[0,1]

注意如果 n < 2 没有任何组合可以从少于 2 元素的集合中选择 2 元素。

后继算法是(在python中):

def next_combination( item, n, k ):
    MIN = 0
    MAX = k-1
    j = n-k
    i = MAX
    index = -1
    # find index to move
    while(MIN<=i and i<=MAX):
        if item[i] < j+i:
            index = i
            break
        i -= 1
    # adjust next indexes after the moved index
    if MIN<=index and index<=MAX:
        curr = item[index]+1
        j = n-k+index
        if curr == j:
            item[index] = curr
        elif curr < j:
            i = index
            while(MIN<=i and i<=MAX):
                item[i] = curr
                curr += 1
                i += 1
    else:
        # last item
        item = None
    return item

你像下面这样使用:

comb = [0, 1] # first combination
doc1 = None
doc2 = None
prevcomb = None
while (comb):
    # process combination
    # eg:
    # doc1 = docs.get(comb[0]) if (not prevcomb) or (prevcomb[0]!=comb[0]) else doc1
    # doc2 = docs.get(comb[1]) if (not prevcomb) or (prevcomb[1]!=comb[1]) else doc2
    # compare(doc1, doc2)
    # when finished, compute next combination untill last
    prevcomb = comb[:] # copy
    comb = next_combination(comb, n, 2) # get next combination in order

Online test for k=2, n=6

note2上述算法的时间复杂度是高效的,实际上它是一个CAT算法(即每个组合的平均时间恒定)来生成整组组合.

note3 对于特殊情况(例如 n 很小),还有更快的算法。一种这样的算法仅对32bit64bit 无符号整数使用智能按位运算(因此仅适用于n &lt;= 64

note4 上述算法(用于python)也可以调整为使用iterator 模式或generator 模式(即yield),但可以在任何语言中轻松实现甚至不支持生成器的那些

note5 对于k=2,组合算法也可以使用嵌套循环来实现(因为在这种情况下它们是重合的) 即:

def next_combination2(n):
    for i in range(n-1):
        for j in range(i+1, n):
            yield [i, j]

note6如果使用了另一种语言,请告诉我如果可能的话用另一种语言重新发布算法(例如:php、javascript、c)

【讨论】:

  • 感谢您的更新,听起来您已经更好地理解了我的问题(为了清晰起见,我也对其进行了改进)。我没有考虑过预先生成组合本身,因此最初并不担心组合“索引”的内存,即。只是元数据,但我现在可以看到即使这样也是一个问题。我尝试使用您的网站生成 100,000 个组合,但它冻结了我的浏览器 :)
  • 我喜欢使用组合生成器的想法——我没有想到这一点(我只是通过遍历数组或游标来隐式地做到这一点)。我在 python 中使用过生成器。我目前正在使用 Node javascript,但我可以翻译。我能做的是运行生成器 X 个周期,其中 X 是我的批量大小。然后在数据库中查询 X 组合中的所有项目,然后执行比较。然后生成下一个 X 组合。
  • @scipilot yeap 也注意到了延迟,但在这种情况下,瓶颈不是生成快速的实际组合(使用此算法),而是使用大整数阶乘和二项式计算组合的总数这是在任何实际生成发生之前完成的。虽然对于 k=2,总数很容易计算,但使用 100000 的阶乘从二项式系数的定义计算它会减慢处理速度
  • @scipilot,事实上,我用于大整数计算的 biginteger.js 库出现 "too much recursion" 错误。这是您在浏览器冻结时看到的内容。它与实际的生成算法本身无关
  • @scipilot,固定总计算在现场和库中冻结,您可以test for combinations of 100000
猜你喜欢
  • 2014-07-24
  • 1970-01-01
  • 2016-10-19
  • 2016-10-03
  • 2023-03-03
  • 1970-01-01
  • 2012-09-24
  • 1970-01-01
相关资源
最近更新 更多