如何将组长度和组内元素的所有可能组合中的列表拆分为n个组?

时间:2016-08-28 15:19:36

标签: python

我想在所有可能的组合中将列表拆分为n个组(允许变量组长度)。

说,我有以下列表:

lst=[1,2,3,4]

如果我指定n = 2,则可以将列表分成1个元素-3元素或2个元素-2元素的组。在这两种分割列表的方式中,每个列表中都有各种元素的唯一组合。

当n = 2时,这些将是:

(1),(2,3,4)
(2),(1,3,4)
(3),(1,2,4)
(4),(1,2,3)
(1,2),(3,4)
(1,3),(2,4)
(1,4),(2,3)

当n = 1时,这些将是:

(1,2,3,4)

当n = 3时,这些将是:

(1),(2),(3,4)
(1),(3),(2,4)
(1),(4),(2,3)
(2),(3),(1,4)
(2),(4),(1,3)
(3),(4),(1,2)

我对长度为0的组不感兴趣,组内的顺序无关紧要。

我发现了两个类似的问题,但他们并没有完全回答我的问题。

This问题将列表拆分为所有组合,其中每个组的长度为n(我发现@tokland的答案)特别有用)。但是,我并不是要寻找所有组长度相同的组。

然后this问题的第一步获得拆分位置的唯一组合,将列表拆分为n个组。但是,此处保留了列表顺序,并且未确定这些组中元素的唯一组合。

我正在寻找这两个问题的组合 - 列表在组长度的所有可能组合以及组内元素的组合中被分成n组。

3 个答案:

答案 0 :(得分:6)

我们可以使用this answer中的基本递归算法并对其进行修改以生成特定长度的分区,而无需生成和过滤掉不需要的分区。

def sorted_k_partitions(seq, k):
    """Returns a list of all unique k-partitions of `seq`.

    Each partition is a list of parts, and each part is a tuple.

    The parts in each individual partition will be sorted in shortlex
    order (i.e., by length first, then lexicographically).

    The overall list of partitions will then be sorted by the length
    of their first part, the length of their second part, ...,
    the length of their last part, and then lexicographically.
    """
    n = len(seq)
    groups = []  # a list of lists, currently empty

    def generate_partitions(i):
        if i >= n:
            yield list(map(tuple, groups))
        else:
            if n - i > k - len(groups):
                for group in groups:
                    group.append(seq[i])
                    yield from generate_partitions(i + 1)
                    group.pop()

            if len(groups) < k:
                groups.append([seq[i]])
                yield from generate_partitions(i + 1)
                groups.pop()

    result = generate_partitions(0)

    # Sort the parts in each partition in shortlex order
    result = [sorted(ps, key = lambda p: (len(p), p)) for ps in result]
    # Sort partitions by the length of each part, then lexicographically.
    result = sorted(result, key = lambda ps: (*map(len, ps), ps))

    return result

这里有很多事情,所以让我解释一下。

首先,我们从同一aforementioned recursive algorithm的程序性,自下而上(teminology?)实现开始:

def partitions(seq):
    """-> a list of all unique partitions of `seq` in no particular order.

    Each partition is a list of parts, and each part is a tuple.
    """
    n = len(seq)
    groups = []  # a list of lists, currently empty

    def generate_partitions(i):
        if i >= n:
            yield list(map(tuple, groups))
        else:
            for group in groups
                group.append(seq[i])
                yield from generate_partitions(i + 1)
                group.pop()

            groups.append([seq[i]])
            yield from generate_partitions(i + 1)
            groups.pop()

    if n > 0:
        return list(generate_partitions(0))
    else:
        return [[()]]

主要算法在嵌套generate_partitions函数中。基本上,它遍历序列,并且对于每个项目,它:1)将项目放入工作集中的每个当前组(a.k.a部分)并递归; 2)将项目放在自己的新组中。

当我们到达序列的末尾(i == n)时,我们会生成我们正在构建的工作集的(深层)副本。

现在,为了获得特定长度的分区,我们可以简单地过滤或分组我们正在寻找并完成它们的结果,但这种方法执行了很多如果我们只想要一些长度为k的分区,那就是不必要的工作(即递归调用)。

请注意,在上面的函数中,分区的长度(即组的数量)随时增加:

            # this adds a new group (or part) to the partition
            groups.append([seq[i]])
            yield from generate_partitions(i + 1)
            groups.pop()

...被执行。因此,我们通过简单地在该块上放置一个保护来限制分区的大小,如下所示:

def partitions(seq, k):
    ...

    def generate_partitions(i):
        ...

            # only add a new group if the total number would not exceed k
            if len(groups) < k:
                groups.append([seq[i]])
                yield from generate_partitions(i + 1)
                groups.pop()

将新参数和该行添加到partitions函数现在将使其仅生成长度最多 k的分区。这几乎就是我们想要的。问题是for循环有时会生成长度小于 k的分区。

为了修剪那些递归分支,我们只需要确保在序列中有足够的剩余元素将工作集扩展为总共{{1} }组。剩余元素的数量 - 或尚未放入群组的元素 - 为for(或k)。 n - i是我们需要添加以生成有效k分区的新组的数量。如果len(seq) - i,那么我们无法通过添加其中一个当前组来浪费项目 - 我们必须创建一个新组。

所以我们只需添加另一个后卫,这次是另一个递归分支:

k - len(groups)

然后,你有一个工作的k分区生成器。您甚至可以进一步折叠一些递归调用(例如,如果剩下3个项目,我们还需要3个组,那么您已经知道必须将每个项目拆分成他们自己的组),但我想显示作为生成所有分区的基本算法的略微修改。

唯一要做的就是对结果进行排序。不幸的是,我没有弄清楚如何以所需的顺序直接生成分区(一个聪明的狗的练习),而是欺骗了,只是对后代进行了分类。

n - i <= k - len(groups)

除了关键功能外,有些不言自明。第一个:

    def generate_partitions(i):
        ...

            # only add to current groups if the number of remaining items
            # exceeds the number of required new groups.
            if n - i > k - len(groups):
                for group in groups:
                    group.append(seq[i])
                    yield from generate_partitions(i + 1)
                    group.pop()

            # only add a new group if the total number would not exceed k
            if len(groups) < k:
                groups.append([seq[i]])
                yield from generate_partitions(i + 1)
                groups.pop()

表示按长度排序序列,然后按序列本身排序(在Python中,默认情况下按字典顺序排序)。 def sorted_k_partitions(seq, k): ... result = generate_partitions(0) # Sort the parts in each partition in shortlex order result = [sorted(ps, key = lambda p: (len(p), p)) for ps in result] # Sort partitions by the length of each part, then lexicographically. result = sorted(result, key = lambda ps: (*map(len, ps), ps)) return result 代表&#34; part&#34;。这用于对分区中的部件/组进行排序。例如,此键表示key = lambda p: (len(p), p) 位于p之前,因此(4,)的排序方式为(1, 2, 3)

[(1, 2, 3), (4,)]

这里的[(4,), (1, 2, 3)]代表&#34;部分&#34;,复数。这个说法是按照每个元素的长度(必须是序列本身)对序列进行排序,然后(按字典顺序)由序列本身进行排序。这用于相对于彼此对分区进行排序,例如,key = lambda ps: (*map(len, ps), ps) # or for Python versions <3.5: lambda ps: tuple(map(len, ps)) + (ps,) ps之前。

以下内容:

[(4,), (1, 2, 3)]

产生

[(1, 2), (3, 4)]

答案 1 :(得分:2)

最简单的选择是使用 more_itertools 库。

from more_itertools import set_partitions

a = [1, 2, 3, 4]

# pass as the second argument the number of splits
list(set_patitions(a, 2))
...   
 [[[1], [2, 3, 4]],
 [[1, 2], [3, 4]],
 [[2], [1, 3, 4]],
 [[1, 2, 3], [4]],
 [[2, 3], [1, 4]],
 [[1, 3], [2, 4]],
 [[3], [1, 2, 4]]]
>>>

list(set_patitions(a, 3))
...
[[[1], [2], [3, 4]],
 [[1], [2, 3], [4]],
 [[1], [3], [2, 4]],
 [[1, 2], [3], [4]],
 [[2], [1, 3], [4]],
 [[2], [3], [1, 4]]]
>>>

set_partitions 使用生成器,允许立即创建数千个集合。

答案 2 :(得分:0)

根据@friendly_dog的有用链接,我试图通过调整this帖子中使用的功能来回答我自己的问题。我有一个粗略的解决方案,虽然我担心它不是特别有效,可以使用一些改进。我最终生成了比我需要的更多的分区集,然后筛选出仅按排序顺序不同的分区。

首先,我从Set partitions in Python获取这3个函数:

import itertools
from copy import deepcopy

def slice_by_lengths(lengths, the_list):
    for length in lengths:
        new = []
        for i in range(length):
            new.append(the_list.pop(0))
        yield new

def partition(number):
    return {(x,) + y for x in range(1, number) for y in partition(number-x)} | {(number,)}

def subgrups(my_list):
    partitions = partition(len(my_list))
    permed = []
    for each_partition in partitions:
        permed.append(set(itertools.permutations(each_partition, len(each_partition))))

    for each_tuple in itertools.chain(*permed):
        yield list(slice_by_lengths(each_tuple, deepcopy(my_list)))

然后我编写了一个包装subgrups函数的函数,并将其应用于原始列表的每个排列。我遍历这些子组排列,如果它们的长度与所需的分区数相等,我会以允许我识别重复的方式对它们进行排序。我不确定是否有更好的方法。

def return_partition(my_list,num_groups):
    filtered=[]
    for perm in itertools.permutations(my_list,len(my_list)):
        for sub_group_perm in subgrups(list(perm)):
            if len(sub_group_perm)==num_groups:
                #sort  within each partition
                sort1=[sorted(i) for i in sub_group_perm]
                #sort by first element of each partition
                sort2=sorted(sort1, key=lambda t:t[0])
                #sort by the number of elements in each partition
                sort3=sorted(sort2, key=lambda t:len(t))
                #if this new sorted set of partitions has not been added, add it
                if sort3 not in filtered:
                    filtered.append(sort3)
    return filtered

在原始示例列表中运行它,我看到它产生了所需的输出,在两个分区和三个分区上进行了测试。

>>> for i in return_partition([1,2,3,4],2):
...     print i    
... 
[[1], [2, 3, 4]]
[[4], [1, 2, 3]]
[[1, 2], [3, 4]]
[[3], [1, 2, 4]]
[[1, 3], [2, 4]]
[[2], [1, 3, 4]]
[[1, 4], [2, 3]]
>>> 

>>> for i in return_partition([1,2,3,4],3):
...     print i        
... 
[[1], [4], [2, 3]]
[[3], [4], [1, 2]]
[[1], [2], [3, 4]]
[[1], [3], [2, 4]]
[[2], [4], [1, 3]]
[[2], [3], [1, 4]]
>>>