【问题标题】:Efficient enumeration of ordered subsets in PythonPython中有序子集的高效枚举
【发布时间】:2015-07-07 22:55:30
【问题描述】:

我不确定我正在尝试编写的代码的适当数学术语。我想生成唯一整数的组合,其中每个组合的“有序子集”用于排除某些后来的组合。

希望一个例子能说明这一点:

from itertools import chain, combinations
​
mylist = range(4)
max_depth = 3

rev = chain.from_iterable(combinations(mylist, i) for i in xrange(max_depth, 0, -1))
for el in list(rev):
    print el

该代码生成的输出包含我想要的所有子集,但也包含一些我不想要的额外子集。我已经手动插入了 cmets 来指示我不想要哪些元素。

(0, 1, 2)
(0, 1, 3)
(0, 2, 3)
(1, 2, 3)
(0, 1)  # Exclude: (0, 1, _) occurs as part of (0, 1, 2) above
(0, 2)  # Exclude: (0, 2, _) occurs above
(0, 3)  # Keep
(1, 2)  # Exclude: (1, 2, _) occurs above
(1, 3)  # Keep: (_, 1, 3) occurs above, but (1, 3, _) does not
(2, 3)  # Keep
(0,)    # Exclude: (0, _, _) occurs above
(1,)    # Exclude: (1, _, _) occurs above
(2,)    # Exclude: (2, _) occurs above
(3,)    # Keep

因此,我的生成器或迭代器的期望输出将是:

(0, 1, 2)
(0, 1, 3)
(0, 2, 3)
(1, 2, 3)
(0, 3)
(1, 3)
(2, 3)
(3,)  

我知道我可以列出所有(想要的和不需要的)组合,然后过滤掉我不想要的组合,但我想知道是否有更高效的基于生成器或迭代器的方式。

【问题讨论】:

  • 您可以将所有子集散列到字典中。所以如果你生成 (0, 1, 2) 写一个方法来散列 {(0,): True, (1,): True, (0, 1): True, (0, 2): True, (0, 1, 2): 真 } 等等。然后你可以在这个哈希表中查找,看看你是否想要新的集合。

标签: python iterator generator combinations itertools


【解决方案1】:

您正在尝试排除作为先前返回组合的前缀的任何组合。这样做很简单。

  • 如果元组 t 的长度为 max_depth,则它不能是先前返回的元组的前缀,因为作为前缀的任何元组都必须更长。
  • 如果元组tmylist[-1] 结尾,则它不能是先前返回的元组的前缀,因为没有任何元素可以合法地添加到t 的末尾来扩展它.
  • 如果一个元组t的长度小于max_depth并且不以mylist[-1]结尾,那么t是之前返回的元组t + (mylist[-1],)的前缀,并且t不应该被返回.

因此,您应该生成的组合正是长度为max_depth 的组合和以mylist[-1] 结尾的较短组合。以下代码以与原始代码完全相同的顺序执行此操作,并正确处理maxdepth > len(mylist) 之类的情况:

def nonprefix_combinations(iterable, maxlen):
    iterable = list(iterable)
    if not (iterable and maxlen):
        return
    for comb in combinations(iterable, maxlen):
        yield comb
    for length in xrange(maxlen-2, -1, -1):
        for comb in combinations(iterable[:-1], length):
            yield comb + (iterable[-1],)

(我在这里假设在maxdepth == 0 的情况下,您仍然不想在输出中包含空元组,即使对于maxdepth == 0,它不是之前的前缀-返回元组。如果在这种情况下确实需要空元组,可以将if not (iterable and maxlen) 更改为if not iterable。)

【讨论】:

  • 这很好用。非常感谢你的帮助!正如我在对@hgwell 的评论中提到的,您的解决方案和@hgwell 的结果相同,但有趣的是,顺序不同。我使用line_profiler 进行了一些快速速度测试,您的解决方案在速度方面大致相同。也非常感谢词汇帮助(prefix)。
【解决方案2】:

我注意到您想要的输出中有一个有趣的模式,我有一个生成器可以产生这种模式。这适用于您的所有情况吗?

from itertools import combinations

def orderedSetCombination(iterable, r):
    # Get the last element of the iterable
    last = (iterable[-1], )
    # yield all the combinations of the iterable without the
    # last element
    for iter in combinations(iterable[:-1], r):
        yield iter
    # while r > 1 reduce r by 1 and yield all the combinations
    while r>1:
        r -= 1
        for iter in combinations(iterable[:-1], r):
            yield iter+last
    # yield the last item
    yield last

iter = [0,1,2,3]

for el in (list(orderedSetCombination(iter, 3))):
    print(el)

这是我对逻辑的解释:

# All combinations that does not include the last element of the iterable
# taking r = max_depth items at a time

(0,1,2) 

# from here on, its the combinations of all the elements except 
# the last element and the last element is added to it.
# so here taking r = r -1 items at a time and adding the last element
# combinations([0,1,2], r=2)

(0,1,3)
(0,2,3)
(1,2,3)

# the only possible value right now at index r = 2 is the last element (3)
# since all possible values of (0,1,_) (0,2,_) (1,2,_) are already listed
# So reduce r by 1 again and continue: combinations([0,1,2], r=1)

(0, 3)
(1, 3)
(2, 3)

# continue until r == 0 and then yield the last element

(3,)

【讨论】:

  • 这很好用。非常感谢你的帮助!您的解决方案和@user2357112 产生相同的结果,但有趣的是,顺序不同。我使用line_profiler 进行了一些快速速度测试,您的解决方案大致相同。仅供参考,您的函数需要可切片的对象,而不仅仅是 iterable 参数中的任何 iterable
猜你喜欢
  • 1970-01-01
  • 2015-12-05
  • 2015-08-08
  • 1970-01-01
  • 2011-09-23
  • 2010-10-05
  • 2016-07-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多