【问题标题】:Find all subsets from list of sets that appears in at least N different sets从至少出现在 N 个不同集合中的集合列表中查找所有子集
【发布时间】:2016-05-12 22:35:15
【问题描述】:

我正在研究根据 SERP 中相同网址的数量对来自搜索引擎的关键字进行分组的算法。每组代表一个url,每个值是url出现的SERP关键字的id。


我有组列表:

groups = [
    [1],
    [1, 2 ,3],
    [1, 2, 3, 4, 5],
    [1, 2, 3 ,4], 
    [2, 3],
    [4, 5, 6],
    [4, 5, 7]
]

我需要获取至少出现在 N 个组中的 ALL 组项目,按“大小”减小的顺序排列:

在上面 N=3 的示例中,我们有两个子集: [1, 2 ,3] 和 [4, 5]


我知道如何获取它:

迭代 1: 找到至少出现 3 次的最大集合(它是 [1, 2 ,3])并从所有集合中删除它出现的位置。

迭代后我们有:

groups = [
        [1],
        [4, 5],
        [4], 
        [2, 3],
        [4, 5, 6],
        [4, 5, 7]
    ]

迭代 2: 找到至少出现 3 次的最大值(它是 [4, 5])

迭代后我们有:

groups = [
        [1],
        [4], 
        [2, 3],
        [6],
        [7]
    ]

算法结束:因为没有更多的集合在组中出现至少 3 次。


您对获取它们的算法有任何想法吗?

N 介于 1 和 10 之间。

附言组列表很大,从 1000 到 10000 项。 Numbers 是 db 中对象的 id。

【问题讨论】:

  • 为什么 [1, 2, 3] 是一个组,而不是 [1], [1, 2] 或 [1, 2, 3, 4] ?
  • 措辞不当。目前尚不清楚问题是什么。更精确地定义组约束。虽然我想我明白了,但它会有很大帮助。
  • @mcmlxxxvi,再看看问题。
  • 为什么[4, 5] 是一个组,而[2, 3] 不是?
  • 好吧,列表多达 10000 套,N 高达 10,我的 itertools 想法可能已经落伍了。二项式系数C(10000,10) 大约为 2.7 x 10**33。将 10 个组的所有集合相交的蛮力方法行不通。所有组的联合有多大? (在您的示例中,它有 7 个项目)。如果这个数字与组的数量相比很小(如果有很多重复,就会出现这种情况)如果有一些可行的想法。

标签: python algorithm


【解决方案1】:

这是一个itertools 方法,用于您称为Iteration 1 的第一部分。如果运行它是可行的,那么你可以在一个循环中重复运行它,在每个阶段删除找到的项目。正如我在 cmets 中指出的,它仅适用于小型 n

import itertools

def intersect(sets):
    #forms the intersection of all s in sets
    #assumes that there is at least one

    I = sets[0].copy()
    for s in sets[1:]:
        I = I & s
    return I

def largestIntersection(sets,n):
    maxSize = 0
    maxSets = [set()]
    for groupCombo in itertools.combinations(sets,n):
        I = intersect(groupCombo)
        if len(I) > maxSize:
            maxSize = len(I)
            maxSets = [I]
        elif len(I) == maxSize:
            maxSets.append(I)
    return maxSets

例如:

>>> groups = [
    [1],
    [1, 2 ,3],
    [1, 2, 3, 4, 5],
    [1, 2, 3 ,4],
    [2, 3],
    [4, 5, 6],
    [4, 5, 7]
]
>>> groups = [set(group) for group in groups]
>>> largestIntersection(groups,3)
[{1, 2, 3}]

编辑时:以下修改可能有所帮助——取决于组中的数字分布和组的大小:

def guessSize(sets,n,trials):
    return max(len(intersect(random.sample(sets,n))) for trial in range(trials))

def maxIntersection(sets,n,trials = 100000):
    k = guessSize(sets,n,trials)
    filteredSets = [s for s in sets if len(s) >= k]
    return largestIntersection(filteredSets,n)

这个想法是在尝试迭代交叉点之前首先减少组的数量。例如:

#stress test:

import random
nums = list(range(1,101))
testGroups = [set(random.sample(nums,random.randint(1,100))) for n in range(1000)]
foundGroups = maxIntersection(testGroups,3)

计算foundGroups 只需几秒钟,而如果我直接使用largestIntersection(testGroups) 则需要几分钟。另一方面,随着随机参数的不同选择,节省的时间变得可以忽略不计。

进一步编辑:也许您可以更积极地进行过滤:

import collections
def maxIntersection(sets,n,trials = 100000):
    k = guessSize(sets,n,trials)
    filteredSets = [s for s in sets if len(s) >= k]
    counts = collections.Counter()
    for s in filteredSets:
        for x in s:
            counts[x] +=1
    t = {x for x,c in counts.items() if c >= k}
    filteredSets = [s & t for s in filteredSets if len(s & t) >= k]
    return largestIntersection(filteredSets,n)

【讨论】:

  • 它可以工作,但真的很慢.. 使用集合是个坏主意。谢谢。
  • 它与作为数据结构本身的集合无关。这是关于我们需要尝试的组合。
  • @sascha,当然是。我的意思是解决一般问题的方法。
  • @AlexT 这似乎是一个算法难题。看看编辑中的优化是否有帮助。
【解决方案2】:

第一个原型方法/hack 结合了递归之美、伪函数式编程和我身边的 ***-up 东西。有很多改进可能,尤其是在迭代器/列表方面。也许这甚至可以称为意大利面条代码:-)。

警告:请参阅@John Coleman 关于二项式系数的评论。我们在每次迭代中生成剩余值的所有可能子集。如果懒惰地使用生成器可能会有所改善(但对于大量唯一数字集仍然不可行)。

import itertools

groups = [
    [1],
    [1, 2 ,3],
    [1, 2, 3, 4, 5],
    [1, 2, 3 ,4],
    [2, 3],
    [4, 5, 6],
    [4, 5, 7]
]

def solve(groups, N, sol=[]):
    if len(groups) == 0:
        return sol

    rem_vals = list(set(itertools.chain(*groups)))
    combs = list(itertools.product(range(2), repeat=len(rem_vals)))
    combs_ = [[rem_vals[ind] for ind, i in enumerate(combs[comb]) if i] for comb in range(len(combs))]

    for cand in reversed(sorted(combs_, key=lambda x: len(list(itertools.chain(x))))):
        if len(cand) == 0:
            continue
        else:
            counter = 0
            inds = []
            for ind, g in enumerate(groups):
                if set(cand).issubset(g):
                    counter += 1
                    inds.append(ind)

            if counter >= N:
                sol.append(cand)
                for i in inds:
                    for j in cand:
                        groups[i].remove(j)
                return solve(groups, N, sol)

    return sol

print(solve(groups, 3))

输出

[[1, 2, 3], [4, 5]]

【讨论】:

  • 对于 200 个短语和 1880 个链接(即 len(groups) ),程序在 10 秒内的内存使用量超过了 12gb,所以我无法对真实数据进行解决 :)
  • 我认为有人要求您提供集合中唯一数量的 val,但您没有回应。如果这个集合很大,你的这些就行不通了。约翰科尔曼的解决方案也是如此。您可以为生成器创建列表以提高记忆力,但与时间相关的性能不会改变。
  • John Coleman 的解决方案有效,但需要几分钟才能得到结果。我会寻找另一种方法来解决这个问题..
  • 我指出了方法的不同。它只是一些修改。一种使用更多内存并且速度更快,另一种更慢但需要更少的内存。你选择。
  • 抱歉,我错过了有关唯一值的问题。对于 1880 个集合,有 180 个唯一值。类似的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-20
  • 2021-03-14
  • 1970-01-01
相关资源
最近更新 更多