微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

是否有一种快速算法可以将集合的所有分区生成大小为 2 的子集和一个大小为 1 的子集?

如何解决是否有一种快速算法可以将集合的所有分区生成大小为 2 的子集和一个大小为 1 的子集?

标题所述,我试图生成一组大小为 n 的所有分区,其中所有子集的大小为 2,如果 n 不均匀,则有 ne 个单例集。我非常轻微地修改了一些用于生成所有分区的 SO 代码以得到这个:

def partitionIntoPairs(collection):
    if len(collection) == 1:
        yield [ collection ]
        return

    first = collection[0]
    for smaller in partition2(collection[1:]):
        for n,subset in enumerate(smaller):
            if len(subset):
                yield smaller[:n] + [[ first ] + subset]  + smaller[n+1:]
        yield [ [ first ] ] + smaller

这有效,但遗憾的是太慢了。我的第二个想法是使用 itertools.combinations 为某个集合生成所有对,然后在没有删除给定对的情况下为每个 det 递归调用函数,但我猜这会更慢。实现也不正确,它只返回一个可能的分区,我不确定如何让它返回所有分区:

from itertools import combinations

def partitionIntoPairs2(collection):
    if not collection:
        return []
    elif len(collection) == 1:
        return [(next(iter(collection)))]
    else: 
        pairs = set(combinations(collection,2))
        for pair in pairs: 
            collection.remove(pair[0])
            collection.remove(pair[1])
            return partition3(collection) + [pair]

我偶然发现了一些用于具有给定集合数的分区的算法,以及生成所有可能分区的算法的各种实现,但就我所见,这些算法都没有有效地解决我的问题。

那么,提出一个更具体的问题:如果第二种算法是一个可行的选择,那么正确的实现是什么?当然,有没有更快的方法来做到这一点?如果是,如何?

解决方法

应将分区视为一组,仅按顺序不同的两个分区应视为同一个。所以数字集只有3个分区(1,2,3,4)。

分区数应该是N!/(N/2)!/2^(N/2)。使用斯特林公式,它大约是。 Sqrt(2)*(N/e)^(N/2) 其中 e=2.71828...而且非常大。

我利用了@VirtualScooter 的代码并提供了分区的递归版本,它比他的 itertools 版本运行得更快(请注意,这不是苹果与苹果的比较,因为我的分区没有重复)。


import itertools
import timeit
t3 = (1,3)
t4 = (1,4)
t6 = (1,4,5,6)

def grouper(iterable,n,fillvalue=None):
    """Collect data into fixed-length chunks or blocks.
        Code from Python itertools page
    """
    # grouper('ABCDEFG','x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.zip_longest(*args,fillvalue=fillvalue)

def partitionIntoPairs(collection):
    perms = itertools.permutations(collection)
    for p in perms:
        group = list(grouper(p,2))
        if group[-1][-1] is None:
            group[-1] = (group[-1][0],)
        yield group

def Partition(Indexable):
    if len(Indexable)<=2:
        yield [Indexable]
    elif len(Indexable)%2==1:
        for i,x in enumerate(Indexable):
            for s in Partition(Indexable[:i]+Indexable[i+1:]):
                yield [[x]]+s
    else:
        for i,x in enumerate(Indexable):
            if i==0:
                x0=x
            else:
                for s in Partition(Indexable[1:i]+Indexable[i+1:]):
                    yield [[x0,x]]+s
def comp_timeit(collection,repeats=1_000):
    s1 = f"l1 = list(Partition({collection}))"
    s2 = f"l1 = list(partitionIntoPairs({collection}))"
    t1 = timeit.timeit(s1,globals=globals(),number=repeats)
    t2 = timeit.timeit(s2,number=repeats)
    print(f"partition,{repeats:_} runs: {t1:,.4f}")
    print(f"itertools,{repeats:_} runs: {t2:,.4f}")
for p in Partition(t4):
    print(p)
comp_timeit(t3)
comp_timeit(t4)
comp_timeit(t6)
,

这个递归生成器函数yield在分区的长度与原始输入相同时进行分区,并且仅在它可以添加到正在进行的子分区或保留单个子分区(如果len(data)%2 == 1) :

data = {1,3}
def partition(d,m,c = []):
   if len(l:=[j for k in c for j in k]) == len(d):
      yield c
   for i in filter(lambda x:x not in l,d):
      if not c or len(c[-1]) == m:
         yield from partition(d,c=c+[[i]])
      else:
         if sum(len(i) == 1 for i in c) == 1 and len(data)%2:
            yield from partition(d,c=c+[[i]])
         yield from partition(d,c=[*c[:-1],c[-1]+[i]])


print(list(partition(list(data),2)))

输出:

[[[1],[2,3]],[[1,2],[3]],[[1],[3,2]],3],[2]],[[2],[1,[[2,1],1]],[1]],[[3],[[3,[1]]]

len(data)%2 == 0

data = {1,4}
print(list(partition(list(data),2)))

输出:

[[[1,4]],[4,4],[[4,1]]]
,

这可以用 itertools 完成,可能比递归算法更快, 像另一个答案 (https://stackoverflow.com/a/66972507/5660315) 中的 partition。我在我的时间序列中测量了 t6 的 4.5 秒运行时间,如下所示, 相对于 mi_partition 的时间低于 0.2 秒。

第一个想法是先列出集合的所有排列,然后拆分 每个在子集中,使用 grouper 文档中的 itertools 算法 页。然后,我们剔除最终奇数大小子集的填充物(如果适用)。

正如@Bing Wang 所指出的,在这种类型的序列中会出现重复。所以, 相反,我调用了 more_itertools.set_partitions 函数,它 减少重复。这也会生成长度更大的子集 大于 2,所以这些被 itertools.filterfalse 过滤掉了。

import itertools
import timeit
import more_itertools

t3 = (1,6)

def mi_partition(collection):
    k = len(collection) // 2 + len(collection) % 2
    s1 = more_itertools.set_partitions(collection,k)
    if False:
        p1,p2 = itertools.tee(s1)
        print(len(list(p1)))
        s1 = p2
    return itertools.filterfalse(lambda x: any(len(y)>2 for y in x),s1)

print(list(mi_partition(t3)))
print(list(mi_partition(t4)))

输出:

[[[1],3]]]
[[[1,4]]]

Partition 算法的小时间比较 @Bing Wang 的回答表明他们的解决方案更快:

def comp_timeit(collection,repeats=1_000):
    s3 = f"l1 = list(mi_partition({collection}))"
    s4 = f"l1 = list(Partition({collection}))"
    t3 = timeit.timeit(s3,number=repeats)
    print(f"more_itertools,{repeats:_} runs: {t3:,.4f}")
    t4 = timeit.timeit(s4,number=repeats)
    print(f"Partition,{repeats:_} runs: {t4:,.4f}")
comp_timeit(t3)
comp_timeit(t4)
comp_timeit(t6)

输出如下。请注意,对于 t3 到 t4,结果列表具有 两种情况下的长度均为 3,而对于 t5,长度为 15。 似乎 Partitions 解决方案稍快,可能 因为它不需要过滤任何解决方案。对于 t6, set_partitions(t6,3) 生成 90 个分区,只有 15 个 使其成为最终答案。

more_itertools,1_000 runs: 0.0051
Partition,1_000 runs: 0.0024
more_itertools,1_000 runs: 0.0111
Partition,1_000 runs: 0.0026
more_itertools,1_000 runs: 0.1333
Partition,1_000 runs: 0.0160```
,

您的示例没有显示“所有子集”的含义。 如果您需要获取给定集合中所有可能的值对,请尝试使用 set() 和frozenset()

my_set = {1,}

res = set()
for value in my_set:
    current_set = set()
    current_set.add(value)
    for value in my_set:
        new_set = current_set.copy()
        new_set.add(value)
        res.add(frozenset(new_set))
        
if not len(my_set) % 2:
    res = [list(new_set) for new_set in res if len(new_set) > 1]
else:
    res = list(map(list,res))
print(res)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?