从itertools随机化链

时间:2012-05-05 19:58:00

标签: python random

我正在复制python docs的示例。

def powerset(iterable):
    "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
    s = list(iterable)
    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

powerset的结果仍被懒惰评估时,我们如何随机化我们得到的值的顺序?

编辑:我想要它的原因是我想计算派生集的总和并在我找到两个具有相同总和的集合时立即停止。如果我没有记错的话,the problem is NP-complete

4 个答案:

答案 0 :(得分:2)

itertools.combinations()以输入的设定顺序为我们提供结果。鉴于此,我们可以对输入列表进行随机排序以生成随机的元素顺序(显然,结果的订单可能会少得多)。

def random_powerset(iterable):
     s = list(iterable)
     lengths = list(range(len(s)+1))
     shuffle(lengths)
     return chain.from_iterable(combinations(s, r) for r in lengths if not shuffle(s))

(这是一个丑陋的黑客 - 我们知道shuffle(s)将始终返回False,因此我们可以将其添加为条件,以确保每次调用combinations()时都会运行。)

我们预先生成长度列表,这样我们也可以随机播放。

它不是完全随机的(仍然会有一个顺序 - 例如,长度为n的所有元素将聚集在一起,并且这些元素将按顺序排列,具体取决于输入的随机顺序),但是会有相当大的随机性,如果这对你来说足够了。

示例输出:

>>> list(random_powerset(range(3)))
[(), (2,), (0,), (1,), (2, 1), (2, 0), (1, 0), (1, 2, 0)]
>>> list(random_powerset(range(3)))
[(), (0, 1), (0, 2), (1, 2), (0, 1, 2), (2,), (0,), (1,)]
>>> list(random_powerset(range(3)))
[(0, 1, 2), (2,), (1,), (0,), (0, 2), (0, 1), (2, 1), ()]
>>> list(random_powerset(range(3)))
[(1, 2, 0), (0,), (2,), (1,), (), (0, 1), (0, 2), (1, 2)]
>>> list(random_powerset(range(3)))
[(), (2, 1), (2, 0), (1, 0), (0,), (2,), (1,), (2, 1, 0)]
>>> list(random_powerset(range(3)))
[(1, 0), (1, 2), (0, 2), (0, 2, 1), (), (1,), (0,), (2,)]

我认为这是你能做到的最好而不会让它变得不懒惰。

答案 1 :(得分:2)

这是另一个想法:存储组合生成器并随机生成,直到您全部消耗。这也使设定大小的顺序随机化。

编辑:我假设您不关心单个集合中元素的顺序,因为您将对它们求和。如果这样做,您可以在收益前加random.shuffle(next_value)

import itertools
import random

def random_powerset(l):
    combs = [itertools.combinations(l,i) for i in range(len(l)+1)]
    while combs:
        comb_index = random.choice(range(len(combs)))
        try:
            next_value = next(combs[comb_index])
            yield next_value
        except StopIteration:
            combs.pop(comb_index)

输出:

In : list(random_powerset(range(3)))
Out: [(0, 1), (0, 2), (0, 1, 2), (1, 2), (), (0,), (1,), (2,)]

In : list(random_powerset(range(3)))
Out: [(0, 1, 2), (0,), (), (0, 1), (1,), (0, 2), (1, 2), (2,)]

In : list(random_powerset(range(3)))
Out: [(0, 1), (0, 1, 2), (0, 2), (), (0,), (1,), (1, 2), (2,)]

In : list(random_powerset(range(3)))
Out: [(), (0,), (0, 1), (0, 1, 2), (1,), (0, 2), (2,), (1, 2)]

In : list(random_powerset(range(3)))
Out: [(), (0, 1), (0,), (0, 1, 2), (1,), (0, 2), (2,), (1, 2)]

In : list(random_powerset(range(3)))
Out: [(0, 1), (0,), (0, 2), (1, 2), (), (1,), (2,), (0, 1, 2)]

In : list(random_powerset(range(3)))
Out: [(), (0, 1, 2), (0,), (1,), (2,), (0, 1), (0, 2), (1, 2)]

答案 2 :(得分:1)

如果超越itertools.chain,可以稍微改进Lattyware的解决方案:

def chain_random(iterables):
    iterables = list(iterables)
    icount = len(iterables)
    if icount == 0: return 
    while icount > 1:
        shuffle(iterables)
        try:
            yield iterables[-1].next()
        except StopIteration:
            iterables.pop()
            icount -= 1
    for element in iterables[0]:
        yield element

def random_powerset(iterable):
    s = list(iterable)
    lengths = list(range(len(s)+1))
    shuffle(lengths)
    return chain_random(combinations(s, r) for r in lengths if not shuffle(s))

示例输出:

>>> list(random_powerset(range(3)))
[(), (2, 1, 0), (1, 0), (1, 2), (2,), (0, 2), (1,), (0,)]
>>> list(random_powerset(range(3)))
[(1, 0), (1, 2), (0, 2, 1), (2,), (), (0, 2), (0,), (1,)]
>>> list(random_powerset(range(3)))
[(0, 1), (), (0, 2), (0,), (1, 2), (2, 0, 1), (1,), (2,)]
>>> list(random_powerset(range(3)))
[(), (1, 2), (2,), (1, 0), (0,), (2, 0), (1,), (1, 0, 2)]
>>> list(random_powerset(range(3)))
[(0, 1), (), (2,), (0, 2), (1, 2), (1,), (1, 2, 0), (0,)]
>>> list(random_powerset(range(3)))
[(0, 2, 1), (0,), (), (2, 0), (1,), (2, 1), (2,), (0, 1)]

itertools是用C语言编写的,因此chain_random会慢于itertools.chain。但是你可以通过这种方式获得更多随机化。

答案 3 :(得分:1)

这是一个懒惰的随机解决方案:

import random

def powerset(seq):
    n = 2**len(seq)
    used = set([])
    while len(used) < n:
        choice = random.randint(0, n - 1)
        if not (choice in used):
            used.add(choice)
            binary = bin(choice)[2:].zfill(len(seq))
            yield [i[1] for i in zip(binary, seq) if i[0] == '1']
            #or following line if > python 2.7:
            #yield itertools.compress(seq, binary)

print list(powerset([1,2,3]))
print list(powerset([1,2,3]))
#output:
[[3], [1], [2, 3], [], [1, 2], [2], [1, 3], [1, 2, 3]]
[[2, 3], [1, 3], [1], [1, 2, 3], [1, 2], [2], [3], []]

如果您考虑二进制[1, 2, 3]的组合:

n  123 

0  000
1  001
2  010
3  011
4  100
5  101
6  110
7  111

每个组合都可以使用唯一的二进制标识符进行标记。并且始终有2**len(seq)个组合....所以:

  1. 随机选择02**len(seq) - 1之间的整数。
  2. 检查我们之前没有使用它(如果我们有,再次画画)。
  3. 将其转换为二进制。
  4. 使用seq拉链。
  5. 如果压缩的二进制数字为'0',我们会将其从输出中排除。
  6. 这是懒惰的,适用于大型seq

    小警告:

    可能存在问题,但对您来说可能无关紧要。在序列结束时,您可能会遇到重复重绘(可能会消耗一些时间)的麻烦。由于绘制已绘制数字的概率为number of successful draws / 2**len(seq),因此在给定的平局g上,找到未使用的新数字的预期绘制数量为:

    n / (n - g)
    #where n = 2**len(seq)
    

    哪个没问题,前提是:n很小,或大ng << n(这些情况中的任何一个或两个都很可能,因此也不应该是一个问题)。实际上,对于大n,您可以省略used并完全检查重复,因为直到第一次重复的预期迭代次数接近n**0.5