如何排列 30 个字符的二进制字符串并省略 "bad strings"

How do I permutate a binary string of 30 characters and omit "bad strings"

我输入字符串的长度(10或30或...)并输入"bad strings"“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)))

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

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

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

>>> 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)

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

>>> 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

这是另一个解决方案。它在我的旧机器上比 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