【问题标题】:Generating a list of repetitions regardless of the order无论顺序如何,都生成重复列表
【发布时间】:2023-03-11 12:16:01
【问题描述】:

我想生成将列表中的索引与“插槽”相关联的组合。例如,(0, 0, 1) 表示 0 和 1 属于同一个 slot,而 2 属于另一个 slot。 (0, 1, 1, 1) 表示 1、2、3 属于同一个 slot,而 0 是自己的。在本例中,0 和 1 只是识别这些插槽的方式,但不携带信息供我使用。

因此,就我的目的而言,(0, 0, 0) 与 (1, 1, 1) 完全相同,(0, 0, 1) 等同于 (1, 1, 0)。

经典的笛卡尔积会产生很多我想摆脱的重复。

这是我通过itertools.product 获得的:

>>> LEN, SIZE = (3,1)
>>> list(itertools.product(range(SIZE+1), repeat=LEN))
>>>
[(0, 0, 0),
(0, 0, 1),
(0, 1, 0),
(0, 1, 1),
(1, 0, 0),
(1, 0, 1),
(1, 1, 0),
(1, 1, 1)]

这就是我想要得到的:

>>> [(0, 0, 0),
(0, 0, 1),
(0, 1, 0),
(0, 1, 1)]

使用小列表很容易,但我不太明白如何使用更大的集合来做到这一点。你有什么建议吗?

如果不清楚,请告诉我,以便我澄清我的问题。谢谢!

编辑:根据 Sneftel 的回答,这个函数似乎可以工作,但我不知道它是否真的产生了所有结果:

def test():
    for p in product(range(2), repeat=3):
        j=-1
        good = True
        for k in p:
            if k> j and (k-j) > 1:
                good = False
            elif k >j:
                j = k
        if good:
            yield p

【问题讨论】:

  • 这样做的目的是什么?你会处理所有的可能性,还是只选择其中的一些?总是有两个“桶”还是有时有更多?
  • 我实际上是在计算项目(索引)之间的不兼容性,我想知道如何将它们分布在有限数量的桶中以最小化这个数量。为此,我将枚举所有不同的分布,然后计算不兼容的数量(使用首选项列表),但我不在乎 1、2 和 3 是在存储桶 A 中还是在存储桶 B 中。我只需要知道它们是否相同。在我的现实生活问题中,我有 5 个桶和 12 个项目,这就是为什么我真的需要限制笛卡尔积的枚举。

标签: python algorithm permutation combinatorics


【解决方案1】:

我将首先提出以下意见:

  1. 每个组合的第一个元素必须为 0。
  2. 第二个元素必须是 0 或 1。
  3. 第三个元素必须是 0、1 或 2,但如果第二个元素是 1,它只能是 2。

这些观察结果表明了以下算法:

def assignments(n, m, used=0):
    """Generate assignments of `n` items to `m` indistinguishable
    buckets, where `used` buckets have been used so far.

        >>> list(assignments(3, 1))
        [(0, 0, 0)]
        >>> list(assignments(3, 2))
        [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1)]
        >>> list(assignments(3, 3))
        [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (0, 1, 2)]

    """
    if n == 0:
        yield ()
        return
    aa = list(assignments(n - 1, m, used))
    for first in range(used):
        for a in aa:
            yield (first,) + a
    if used < m:
        for a in assignments(n - 1, m, used + 1):
            yield (used,) + a

这将在几秒钟内处理您的用例(12 个项目,5 个存储桶):

>>> from timeit import timeit
>>> timeit(lambda:list(assignments(12, 5)), number=1)
4.513746023178101
>>> sum(1 for _ in assignments(12, 5))
2079475

如果修改它以处理 (12, 5) 用例,这比您在答案末尾给出的函数(调用 product 然后丢弃无效分配的函数)要快得多:

>>> timeit(lambda:list(test(12, 5)), number=1)
540.693009853363

【讨论】:

    【解决方案2】:

    在检查重复项之前,您应该协调表示法(假设您不想设置一些花哨的 AI):遍历列表并为从 0 开始的不同元素分配集合附属编号,向上计数。也就是说,您为正在处理的每一行创建一个临时字典。

    一个典型的输出是

    (0,0,0) -> (0,0,0)
    (0,1,0) -> (0,1,0)
    

    但是

    (1,0,1) -> (0,1,0)
    

    然后可以轻松地删除重复项,因为问题已简化为 Python : How to remove duplicate lists in a list of list? 已解决问题的问题

    【讨论】:

      【解决方案3】:

      如果您只考虑笛卡尔积中所有索引的第一次出现从零开始排序并且连续的元素,那应该就足够了。 itertools.combinations_with_replacement() 将消除那些未排序的,因此您只需要检查索引是否被跳过。

      【讨论】:

      • 我已经设法按照这个想法解决了一些问题,似乎适用于我的小例子。我不知道它是否真的产生了所有可能的解决方案......
      【解决方案4】:

      在您的具体情况下,您可以简单地获取由笛卡尔积产生的项目列表的前半部分或后半部分。

      import itertools
      
      alphabet = '01' 
      words3Lettered = [''.join(letter) for letter in itertools.product(alphabet,repeat=3)] 
      

      对于 n 个字母的单词,使用repeat=n

      words3Lettered 看起来像这样:

      ['000', '001', '010', '011', '100', '101', '110', '111']
      

      接下来,

      usefulWords = words3Lettered[:len(words3Lettered)/2]
      

      看起来像这样:

      ['000', '001', '010', '011']
      

      您可能对另一半感兴趣,即words3Lettered[len(words3Lettered)/2:],尽管另一半应该“折叠”到前半部分。

      您很可能想使用数字形式的字母组合,所以...

      indexes = [tuple(int(j) for j in word) for word in usefulWords]
      

      这给了我们:

      [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1)]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-05-31
        • 1970-01-01
        • 2012-06-15
        • 2019-04-01
        • 2018-01-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多