簡體   English   中英

查找具有最少元素數的列表的所有無序分區

[英]Find all unordered partitions of a list with a minimum number of elements

給出了一個元素列表。 我想有所有的可能性把這個列表分成任意數量的分區,這樣每個分區至少有 x 個元素。 列表中分區的順序和分區中元素的順序無關緊要。 例如:List = [1,2,3,4] get_partitions(list,2) 應該返回:

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

List = [1,2,3,4] get_partitions(list,1) 應該返回:

[[1,2,3,4],
 [[1,2],[3,4]],
 [[1,3],[2,4]],
 [[1,4],[2,3]],
 [[1],[2],[3,4],
  ...]

我已經開始在Python中遞歸實現這個,但是我創建了太多冗余案例。 出於運行時的原因,我想提前減少這些情況,而不是事后使用 frozensets 刪除它們,例如。

from itertools import combinations
import numpy as np
def get_partitions(liste,min,max=None):

    if max is None:
        # Default setting
        max = len(liste)
    if len(liste) == min :
        # Termination Criterium
        yield [liste]
    else:
        for r in range(np.min([len(liste),max]),min-1,-1):
            # max for avoiding cases like: [[1,2,3,4],[2,6]] and [[2,6],[1,2,3,4]]
            for perm in combinations(liste,r):
                rest = [i for i in liste if i not in perm]
                if len(rest) >= min:
                    for recurse in get_partitions(rest,min,r):
                        yield [list(perm)] + list(recurse)
                if len(rest) == 0:
                    # r == len(liste)
                    yield [list(perm)]

這將導致:

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

在此先感謝您的幫助。


嘗試使用@mozway 的答案並將其擴展為遞歸版本導致我:

def get_partitions(iterable, minl=2):
    s = set(iterable)

    for r in range(minl, len(s)//2+1):
        if len(s)//2 != r:
            for c in combinations(s, r):
                for recurse in get_partitions(list(s.difference(c)), minl):
                    yield [list(c),*recurse]
        else:
            for c in islice(combinations(s, r), comb(len(s),r)//2):
                for recurse in get_partitions(list(s.difference(c)), minl):
                    yield [list(c),*recurse]

    yield [list(s)]

對於示例 list = [1,2,3,4], x=1 ,它將可能性的數量從 47(我最初的嘗試)減少到 19。不過,仍然有很多多余的情況。

[[[1], [2], [3], [4]], <----
 [[1], [2], [3, 4]],
 [[1], [2, 3, 4]],
 [[2], [1], [3], [4]], <----
 [[2], [1], [3, 4]],
 [[2], [1, 3, 4]],
 [[3], [1], [2], [4]], <----
 [[3], [1], [2, 4]],
 [[3], [1, 2, 4]],
 [[4], [1], [2], [3]], <----
 [[4], [1], [2, 3]],
 [[4], [1, 2, 3]],
 [[1, 2], [3], [4]],
 [[1, 2], [3, 4]],
 [[1, 3], [2], [4]],
 [[1, 3], [2, 4]],
 [[1, 4], [2], [3]],
 [[1, 4], [2, 3]],
 [[1, 2, 3, 4]]]

這是一個很長的解決方案。 在生成分區時沒有使用拒絕,所以從這個意義上說,這可能有點有效。 不過,還有很多東西需要優化。

例子:

list(get_partitions(range(3), 1))
# [[[0, 1, 2]], [[0], [1, 2]], [[1], [0, 2]], [[2], [0, 1]], [[0], [1], [2]]]

以下是其工作原理的概述:

  • 首先,定義一個 helper split function 是很有用的,它接受一個列表lst和一個 integer n並返回將列表分成兩組的所有方法,一組大小為n ,另一組大小為len(lst) - n
  • 其次,我們需要解決一個更簡單的問題版本:如何將列表lst分成n組,每組大小為k 當然,這只有在len(lst) = n * k時才有可能。 這是在get_partitions_same_size function中實現的,思路是總是把lst的第一個元素包含在第一組中,然后遞歸。
  • 第三,我們需要找到len(lst)的所有integer 個分區。 我從這個線程復制了代碼。
  • 第四,對於len(lst)的每個 integer 分區p ,我們需要找到所有根據p分區lst的方法。
    • 例如,說len(lst) == 7p = 3 + 2 + 2 在這種情況下,我們可以為第一組選擇任意三個元素,為第二組選擇剩余的任意兩個元素,最后的第三組沒有選擇。
    • 這會引入冗余,因為我們可以獲得兩個僅在最后兩組的順序上不同的分區。
    • 為了處理這個問題,我們可以通過計算每個大小的組數來表示一個分區。 在此示例中, p對應於p_scheme = [(3, 1), (2, 2)] function get_partitions_helper接受一個列表lst和一個“分區方案” p_scheme ,並返回所有相應的分區而不重復計算。 這是使用第二步中的get_partitions_same_size的地方。
  • 最后,所有內容都集中在get_partitions中:我們遍歷len(lst)的 integer 個分區,並返回對應於每個可能的 integer 分區的所有可能列表分區。

這是一個有趣的問題,非常歡迎對錯誤和優化提出意見。


from itertools import combinations
from collections import Counter

# from this thread:
# https://stackoverflow.com/questions/10035752/elegant-python-code-for-integer-partitioning
def partitions(n, I=1):
    yield (n,)
    for i in range(I, n//2 + 1):
        for p in partitions(n-i, i):
            yield (i,) + p


def split(lst, n):
  '''
  return all ways to split lst into two groups, 
  with n and len(lst) - n elements respectively
  '''
  assert len(lst) >= n
  # handle special case of len(lst) == 2 * n
  if len(lst) == 2 * n:
    for first, second in split(lst[1:], n-1):
      yield [lst[0], *first], second
  else:
    for comb in combinations(range(len(lst)), n):
      comb = set(comb)
      first = [x for i, x in enumerate(lst) if i in comb]
      second = [x for i, x in enumerate(lst) if i not in comb]
      yield first, second


def get_partitions_same_size(lst, n, k):
  # print(lst, n, k)
  'return all ways to partition lst into n parts each of size k up to order'
  if len(lst) != n * k:
    print(lst, n, k)
  assert len(lst) == n * k
  if n == 0 and len(lst) == 0:
    yield []
  # case when group size is one
  elif k == 1:
    yield [[x] for x in lst]
  # otherwise, without loss, the first element of lst goes into the first group
  else:
    for first, rest in split(lst[1:], k-1):
      for rec_call in get_partitions_same_size(rest, n-1, k):
        yield [[lst[0], *first], *rec_call]


def get_partitions_helper(lst, p_scheme):
  """
  return all ways to partition lst into groups according to a partition scheme p_scheme

  p_scheme describes an integer partition of len(lst)
  for example, if len(lst) == 5, then possible integer partitions are:
  [(5,), (1, 4), (1, 1, 3), (1, 1, 1, 2), (1, 1, 1, 1, 1), (1, 2, 2), (2, 3)]
  for each, we count the number of groups of a given size
  the corresponding partition schemes are:
  [[(5, 1)],
  [(1, 1), (4, 1)],
  [(1, 2), (3, 1)],
  [(1, 3), (2, 1)],
  [(1, 5)],
  [(1, 1), (2, 2)],
  [(2, 1), (3, 1)]]
  """
  if not lst and not p_scheme:
    yield []
    return 
  assert len(lst) == sum(a * b for a, b in p_scheme)
  group_size, group_count = p_scheme[0]
  num_elts = group_size * group_count
  for first, second in split(lst, num_elts):
    for firsts in get_partitions_same_size(first, group_count, group_size):
      for seconds in get_partitions_helper(second, p_scheme[1:]):
        yield [*firsts, *seconds]


def get_partitions(lst, min_):
  """
  get all partitions of lst into groups s.t. each group has at least min_ elements
  up to order (of groups and elements within a group)
  """
  for partition in partitions(len(lst), min_):
    p_scheme = list(Counter(partition).items())
    yield from get_partitions_helper(lst, p_scheme)

這看起來像是帶有補碼的部分冪集。

您不需要定義最大值,設置最小值后它就固定了。

此外,包括完整集是一種特殊情況(從技術上講,完整集是集合 + 空集,因此它違反了最小條件)

要限制組合的數量,只需在總長度的前半部分停止即可。 如果您有一個偶數輸入並且正在選擇一半分區,則只計算一半的組合(使用itertools.islice ):

你可以使用:

from itertools import combinations
from math import comb

def get_partitions(iterable, minl=2):
    s = set(iterable)
    return [list(s)]+\
           [[list(c), list(s.difference(c))]
            for r in range(minl, len(s)//2+1)
            for c in ( combinations(s, r) if len(s)//2 != r else
             islice(combinations(s, r), comb(len(s),r)//2))
           ]

out = get_partitions([1,2,3,4])

output:

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

其他例子:

>>> get_partitions([1,2,3,4,5,6], 1)

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM