【问题标题】:Generate all strings of length X that end with '0' and have no adjacent '1's生成所有以“0”结尾且没有相邻“1”的长度为 X 的字符串
【发布时间】:2015-11-17 05:13:32
【问题描述】:

我需要生成满足以下两条规则的一定长度X的所有可能字符串:

  1. 必须以“0”结尾
  2. 不能有两个或更多相邻的“1”

例如,当 X = 4 时,所有“合法”字符串都是 [0000, 0010, 0100, 1000, 1010]。

我已经写了一段递归代码,它只是将新找到的字符串附加到一个列表中。

def generate(pre0, pre1, cur_len, max_len, container = []):
    if (cur_len == max_len-1):
        container.append("".join([pre0, pre1, "0"]))
        return

    if (pre1 == '1'):
        cur_char = '0'
        generate(pre0+pre1, cur_char, cur_len+1, max_len, container)
    else:   
        cur_char = '0'
        generate(pre0+pre1, cur_char, cur_len+1, max_len, container)
        cur_char = '1'
        generate(pre0+pre1, cur_char, cur_len+1, max_len, container)

if __name__ == "__main__": 
    container = []
    _generate("", "", 0, 4, container)
    print container

但是,由于内存复杂性,当 X 达到 100+ 时,此方法将不起作用。我不熟悉 Python 中的生成器方法,所以这里的任何人都可以帮我弄清楚如何将它重新写入生成器吗?非常感谢!

附:我正在处理 Project Euler 问题,这不是家庭作业。

更新 1:

感谢前3个答案,我使用的是Python 2.7,可以切换到python 3.4。我要求生成器的原因是我什至无法将最终结果列表保存在我的记忆中。一个快速的数学证明将表明长度 X 存在 Fibonacci(X) 可能的字符串,这意味着我必须真正使用生成器并动态过滤结果。

【问题讨论】:

  • 我更改了您的标题以进一步表明您想要什么。好的标题会带来好的答案。

标签: python


【解决方案1】:

假设您使用的是 python 版本 >= 3.4,其中yield from 可用,您可以yield 而不是累积+返回。您无需传递容器。

def generate(pre0, pre1, cur_len, max_len):
    if (cur_len == max_len-1):
        yield "".join((pre0, pre1, "0"))
        return

    if (pre1 == '1'):
        yield from generate(pre0+pre1, '0', cur_len+1, max_len)
    else:   
        yield from generate(pre0+pre1, '0', cur_len+1, max_len)
        yield from generate(pre0+pre1, '1', cur_len+1, max_len)

if __name__ == "__main__": 
    for result in generate("", "", 0, 4):
        print result

如果您使用的 Python 版本中 yield from 不可用,请将这些行替换为:

for x in generate(...):
    yield x

【讨论】:

  • 绝妙的答案!这是迄今为止最快的解决方案,比公认答案中的可怕单线快约 30 倍(请参阅我的基准答案)。我将不得不多想一下yieldreturn 在这个游戏中是如何一起发挥作用的……你碰巧知道如何在 C++ 或汇编中实现相同的功能吗?
  • @CoDEmanX:如果你想直接翻译它,你可以使用协程,有可用的库。但是,更实用的是,您可以将函数指针传递给generate,每当生成新结果时就会调用该函数指针。
【解决方案2】:

如果您是在 Python 3 中执行此操作,您可以按如下方式进行调整:

  • 删除container参数。
  • 将所有出现的container.append(<i>some_value</i>) 更改为yield <i>some_value</i>
  • 在所有递归调用前添加 yield from

要在 Python 2 中执行此操作,您可以执行相同操作,只是 Python 2 不支持 yield from。而不是yield from <i>iterable</i>,你需要使用:

对于 iterable 中的项目:
    产量项目

然后,您还需要更改调用站点,删除 container 参数并存储调用的返回值。您还需要遍历结果,打印值,因为print 只会给您&lt;generator object at 0x...&gt;

【讨论】:

  • 生成器也可以转换为序列(列表,元组),您不一定需要遍历它们。请注意,如果生成器包含无限循环。通过使用迭代,可以在一定数量的元素(或时间)之后中止。
  • @CoDEmanX:如果之前因为内存使用而失败,把它变成一个列表会重新引入内存耗尽。
【解决方案3】:

您可以将filteritertools.product 一起使用

def generate(max_len):
    return list(filter(lambda i: '11' not in i, (''.join(i) + '0' for i in itertools.product('01', repeat=max_len-1))))

这会一直使用生成器,直到返回最终创建一个listfilter 将作用于itertools.product 创建的每个字符串。

>>> generate(5)
['00000', '00010', '00100', '01000', '01010', '10000', '10010', '10100']

编辑要将此函数用作生成器表达式,只需删除list 并将filter 切换为itertools.ifilter

def generate(max_len):
    return itertools.ifilter(lambda i: '11' not in i, (''.join(i) + '0' for i in itertools.product('01', repeat=max_len-1)))

for s in generate(10):
    # do something with s

【讨论】:

  • 有没有其他方法可以获取生成器并 for 循环它而不是获取列表?我已经更新了问题以解释为什么我需要这种方式。再次感谢。
  • @JimmyK 当然,只需放弃 list 调用并将 Python 的原生 filter 切换为返回迭代器的 itertools.ifilter(仅 Python 2.x 需要,在 Python 3.x 中 @987654337 @ 已经返回一个迭代器)
【解决方案4】:

基于蹩脚字符串的测试“11”是否包含在格式化字符串中,如果不包含则产生(对于每个偶数,直到 2^maxlen):

def gen(maxlen):
    pattern = "{{:0{}b}}".format(maxlen)
    for i in range(0, 2**maxlen, 2):
        s = pattern.format(i) # not ideal, because we always format to test for "11"
        if "11" not in s:
            yield s

高级数学方法 (M xor M * 2 = M * 3):

def gen(maxlen):
    pattern = "{{:0{}b}}".format(maxlen)
    for i in range(0, 2**maxlen, 2):
        if i ^ i*2 == i*3:
            yield pattern.format(i)

这是 6 种不同实现(Python 3!)的基准:

from time import clock
from itertools import product

def math_range(maxlen):
    pattern = "{{:0{}b}}".format(maxlen)
    for i in range(0, 2**maxlen, 2):
        if i ^ i*2 == i*3:
            yield pattern.format(i)


def math_while(maxlen):
    pattern = "{{:0{}b}}".format(maxlen)
    maxnum = 2**maxlen - 1
    i = 0
    while True:
        if i ^ i*2 == i*3:
            yield pattern.format(i)
        if i >= maxnum:
            break
        i += 2


def itertools_generator(max_len):
    return filter(lambda i: '11' not in i, (''.join(i) + '0' for i in product('01', repeat=max_len-1)))


def itertools_list(maxlen):
    return list(filter(lambda i: '11' not in i, (''.join(i) + '0' for i in product('01', repeat=maxlen-1))))


def string_based(maxlen):
    pattern = "{{:0{}b}}".format(maxlen)
    for i in range(0, 2**maxlen, 2):
        s = pattern.format(i)
        if "11" not in s:
            yield s


def generate(pre0, pre1, cur_len, max_len):
    if (cur_len == max_len-1):
        yield "".join((pre0, pre1, "0"))
        return

    if (pre1 == '1'):
        yield from generate(pre0+pre1, "0", cur_len+1, max_len)
    else:
        yield from generate(pre0+pre1, "0", cur_len+1, max_len)
        yield from generate(pre0+pre1, "1", cur_len+1, max_len)

def string_based_smart(val):
    yield from generate("", "", 0, val)


def benchmark(val, *funcs):
    for i, func in enumerate(funcs, 1):
        start = clock()
        for g in func(val):
            g
        print("{}. {:6.2f} - {}".format(i, clock()-start, func.__name__))

benchmark(24, string_based_smart, math_range, math_while, itertools_generator, itertools_list, string_based)

字符串长度 = 24(以秒为单位)的一些数字:

1.   0.24 - string_based_smart
2.   1.73 - math_range
3.   2.59 - math_while
4.   6.95 - itertools_generator
5.   6.78 - itertools_list
6.   6.45 - string_based

shx2 的算法显然是赢家,其次是数学。如果您比较两种数学方法的结果(注意:范围也是生成器),Pythonic 代码会产生很大的不同。

值得注意:itertools_* 函数的执行速度几乎一样慢,但itertools_list 需要更多内存来存储列表(在我的测试中约为 6 MB 峰值)。所有其他基于生成器的解决方案都具有最小的内存占用,因为它们只需要存储当前状态而不是整个结果。

显示的函数都不会破坏堆栈,因为它们不使用实际的递归。 Python does not optimize tail recursion,因此您需要循环和生成器。

//编辑: math_range 的幼稚 C++ 实现(MSVS 2013):

#include "stdafx.h"
#include <iostream>
#include <bitset>
#include <ctime>
#include <fstream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    const unsigned __int32 maxlen = 24;
    const unsigned __int32 maxnum = 2 << (maxlen - 1);

    clock_t begin = clock();

    ofstream out;
    out.open("log.txt");
    if (!out.is_open()){
        cout << "Can't write to target";
        return 1;
    }

    for (unsigned __int32 i = 0; i < maxnum; i+=2){
        if ((i ^ i * 2) == i * 3){
            out << std::bitset<maxlen>(i) << "\n"; // dont use std::endl!
        }
    }

    out.close();

    clock_t end = clock();
    double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
    cout << elapsed_secs << endl;

    return 0;
}

maxlen = 24 (/Ox) 需要 0.08 秒 (!)。

在 C++ 中实现 shx2 的算法并非易事,因为递归的方法会导致堆栈溢出(哈哈),而且没有 yield。见:

但如果你想要原始速度,那就没有办法了。

【讨论】:

  • 递归方法很可能不会导致堆栈溢出,除非您想要非常长的输出——递归深度受所需字符串的长度限制。
  • 我的 C++ 知识可能太少了,但是在我尝试实现 shx2 的方法时,即使是小的 maxlen 值也会发生堆栈溢出。我想很可能是我造成的缺陷。
  • 我成功地用 C 实现了它,就像这样:codepad.org/wPS8d2GF
  • 不错的一个!但不打印预期结果 - 数字应始终以 0 结尾。
  • 糟糕!好吧,这是一件小事。修复它只需要一个新行:codepad.org/mdic3FqW
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-04
  • 1970-01-01
  • 2010-12-23
相关资源
最近更新 更多