如何置换30个字符的二进制字符串并省略"坏字符串"

时间:2016-10-25 13:07:36

标签: python permutation

我输入字符串的长度(10或30或......)并输入"坏字符串" " 101"," 111",并计算不包含错误字符串的排列数。

我用itertools尝试了这个,它适用于10-15的字符串,但对于30,它超过一万亿的结果并且无法高效运行。我认为该方法是一次构建一个字符串的字符串,但我无法弄清楚算法来做到这一点。

import itertools
v1="111"
v2="101"
def perms(n):
    for i in range(2**n):
        s=bin(i)[2:]
        s="0"*(n-len(s)) + s
        if v1 not in s and v2 not in s:
            print(s)
            yield s
print len(list(perms(10)))

2 个答案:

答案 0 :(得分:1)

您可以逐位构建字符串,立即避免错误路径。这是一个迭代版本:

def build(n):
    strings = ['']
    bad = '111', '101'
    for _ in range(n):
        strings = [s + bit
                   for s in strings
                   for bit in '01'
                   if not (s + bit).endswith(bad)]
    return strings

在一瞬间确认169为10位,大约需要3.4秒才能找到3050位的2550409字符串。

>>> len(build(10))
169
>>> len(build(30))
2550409


还可以使用嵌套生成器来节省内存:

def build(n):
    strings = ['']
    bad = '111', '101'
    for _ in range(n):
        strings = (s + bit
                   for s in strings
                   for bit in '01'
                   if not (s + bit).endswith(bad))
    return strings

演示,也花了我大约3.4秒:

>>> len(list(build(30)))
2550409


或者,递归版本:

def build(n, prefix=''):
    if n == 0:
        yield prefix
        return
    for bit in '01':
        if not (prefix + bit).endswith(('111', '101')):
            yield from build(n - 1, prefix + bit)

在大约7秒内找到2550409的30位:

>>> len(list(build(30)))
2550409

对于Python 2,将yield from行替换为:

            for bits in build(n - 1, prefix + bit):
                yield bits

如果我传递输出列表而不是通过所有级别,我需要大约4.5秒:

def build(n, output, prefix=''):
    if n == 0:
        output.append(prefix)
        return
    for bit in '01':
        if not (prefix + bit).endswith(('111', '101')):
            build(n - 1, output, prefix + bit)

演示:

>>> output = []
>>> build(30, output)
>>> len(output)
2550409

答案 1 :(得分:1)

这是另一种解决方案。它在我的古老机器上比Stefan的代码更快地运行

我们使用str.format方法迭代数字,将它们转换为位串。当我们找到一个带有错误模式的位串时,我们跳到该位位置不包含该模式的第一个数字。因此,如果我们发现在比特串的2**0比特位置(LSB)结束的坏模式,我们前进1,如果坏模式在2**1比特位置结束,我们前进2,等等。

这是循环中打印调用的版本,因此我们可以看到发生了什么。

def skip_bad(width, bad=("101", "111")):
    n = 0
    while n < 1<<width:
        s = '{0:0{1}b}'.format(n, width)
        for b in bad:
            i = s.find(b)
            if i != -1:
                i = width - i - len(b)
                print('skipping', s, i)
                n += 1 << i
                break
        else:
            yield s
            n += 1

for i in skip_bad(5):
    print(i)

<强>输出

00000
00001
00010
00011
00100
skipping 00101 0
00110
skipping 00111 0
01000
01001
skipping 01010 1
01100
skipping 01101 0
skipping 01110 1
10000
10001
10010
10011
skipping 10100 2
11000
11001
skipping 11010 1
skipping 11100 2

这是我用来获取评论中给出的时间数据的版本。

def skip_bad(width, bad=("101", "111")):
    n = 0
    while n < 1<<width:
        s = '{0:0{1}b}'.format(n, width)
        for b in bad:
            i = s.find(b)
            if i != -1:
                n += 1 << (width - i - len(b))
                break
        else:
            yield s
            n += 1

width = 30
print(width)
print(sum(1 for _ in skip_bad(width)))

<强>输出

30
2550409