【问题标题】:How do you create any number of nested loops?如何创建任意数量的嵌套循环?
【发布时间】:2021-02-10 21:09:45
【问题描述】:

我正在尝试编写一个程序,该程序在匹配特定字符串之前测试每个可能的字符串,并尝试给出数字。举个例子:

a = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm',
     13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y',
     25: 'z'}

word = 'bike'

found = False
counter = 0
for i in range(len(a)):
    for j in range(len(a)):
        for k in range(len(a)):
            for m in range(len(a)):
                string = f'{a[i]}{a[j]}{a[k]}{a[m]}'
                counter += 1
                print(string, counter)
                if string == word:
                    found = True
                    break
            if found:
                break
        if found:
            break
    if found:
        break

输出看起来像这样:

aaaa 1
aaab 2
aaac 3
aaad 4
aaae 5
aaaf 6
...
bijz 23244
bika 23245
bikb 23246
bikc 23247
bikd 23248
bike 23249

如果您知道单词总是四个字符,则可以使用此代码,但如果您不知道长度怎么办?当长度未知时,您将如何创建它?我试图考虑是否有一些递归函数可以实现这一点,但没有任何效果。我试图实现的程序将有这样的输出:

a 1
b 2
c 3
...
aa 27
ab 28
ac 29
...
ba #
bb #
bc #
...
aaa #
aab #
aac #

【问题讨论】:

    标签: python python-3.x loops recursion


    【解决方案1】:

    这应该有效;您在productrepeat 参数中输入长度,而enumerate 枚举单词(默认从0 开始;您可以根据需要添加start=1):

    from itertools import product
    
    
    search = "bike"
    
    for i, item in enumerate(product(a.values(), repeat=len(search))):
         word = "".join(item)
         print(word, i)
         if word == search:
              break
    

    它输出:

    ...
    bikb 23245
    bikc 23246
    bikd 23247
    bike 23248
    

    如果您只对这个数字感兴趣,您可以将这个词翻译成以 26 为底的数字,然后使用int 进行转换:

    alpha_to_digits = str.maketrans(
        "abcdefghijklmnopqrstuvwxyz", "0123456789abcdefghijklmnop")
    
    word = 'bike'
    
    d = word.translate(alpha_to_digits)
    print(d, int(d, base=26))
    # 18a4 23248
    

    将单词 bike 转换为以 26 为底的数字表示 (18a4),然后可以将其转换为整数 (23248)。

    打包成一个函数:

    def number(word):
        return int(word.translate(alpha_to_digits), base=26)
    

    【讨论】:

    • 谢谢。这足以让我开始我正在寻找的输出,但我这样做是为了学习经验。如果有办法用递归函数做到这一点,我会很高兴知道的。
    • documentation 或多或少告诉你它是如何实现的。但它不是递归的......
    • @GabeMorris 我在答案中包含了一个递归版本,尽管在 python 中我不推荐它。
    • @Stef 对 python 来说是不是太过分了?
    • @GabeMorris:函数调用是 python 中最慢的操作之一。此外,python 没有优化尾调用,这意味着调用堆栈在每次递归调用时都会变得越来越大,即使您编写像return recursive_call() 这样的代码也是如此。因此,python 中的递归函数比迭代函数慢,占用更多内存,并且无法处理大量输入。
    【解决方案2】:

    注意:此答案中提供的所有函数都返回 23248 作为“自行车”的答案,因为我喜欢从 0 开始计数。如果您更喜欢从 1 开始计数,并且想要得到答案 23249,那么只需添加一个+1 在函数的某个地方。

    第一个想法:编写自己的increment_word函数:

    您可以通过编写一个计算下一个单词的函数来遍历单词。比如bikd之后的下一个单词应该是bikecdzz之后的下一个单词应该是ceaa

    字符串在 python 中是不可变的,所以为了方便我们将使用字符列表而不是字符串。

    def increment_word(w):
      i = len(w) - 1
      while (w[i] == 'z'):
        w[i] = 'a'
        i = i - 1
      w[i] = chr(ord(w[i]) + 1)
    

    请注意,仅当在包含至少一个非“z”字母的单词上调用此函数时,才能保证此函数有效。 'zzz' 之后的下一个单词由用户决定。

    现在我们可以解决您的问题了:

    def find_word(w):
      candidate = ['a' for _ in w]
      w = list(w)
      count_attempts = 0
      while candidate != w:
        increment_word(candidate)
        count_attempts += 1
      return count_attempts
    

    第二个想法:使用itertools.product

    一般来说,当你想在 python 中迭代复杂的东西时,有人已经准确地编写了你需要的循环结构,它在 itertools 包中。如果不是,那么它可能在itertools recipesmore_itertools

    在这种情况下,您可以使用itertools.product

    from string import ascii_lowercase
    from itertools import product
    
    def find_word(w):
      for count_attempts, candidate in enumerate(product(*[ascii_lowercase]*len(w))):
        if all(x == y for x,y in zip(w, candidate)):
          return count_attempts
    

    请注意我们如何使用string.ascii_lowercase,而不是自己输入整个字母表。有人好心教python字母表。无需过分热心并自己重写字母表(有忘记字母并破坏所有内容的风险)。

    第三个思路:用递归代替迭代

    可以使用递归来模拟任何类型的复杂循环。请注意,python 是一种非常糟糕的递归语言 - 特别是,递归函数在 python 中往往非常慢;更不用说如果递归太深程序可能会崩溃,因为python没有优化尾调用。但是如果你需要使用 Python 以外的语言,你应该考虑这个选项。

    def find_word(word):
      return find_word_aux(word, 'a'*len(word), 0)
    
    def find_word_aux(word, candidate, count_attempts):
      if candidate == word:
        return count_attempts
      else:
        i,c = max((i,c) for i,c in enumerate(candidate) if c != 'z')
        return find_word_aux(word, candidate[:i] + chr(ord(c)+1) + 'a'*(len(word)-i-1), count_attempts + 1)
    

    注意这最终与increment_word 版本非常相似。不幸的是,在我的机器上使用默认的 python 参数,它只适用于aaabmg 之间的单词。对于bmh 之后的任何单词,它都会崩溃,但RecursionError: maximum recursion depth exceeded in comparison 除外。如果您将此代码翻译成优化尾调用的语言,例如 OCaml、Haskell 或 C,那么它将适用于任何单词。

    第四个想法:使用组合学立即解决您的问题

    您可以尝试使用乘法计算成批的单词,而不是逐个遍历单词。例如,很容易看出有:

    • 26 * 26 * 26 = 26^3 = 17576aaaaazzz 之间的单词;
    • 8 * 26 * 26 = 5408baaabhzz 之间的单词;
    • 10 * 26 = 260 介于biaabijz 之间的单词;
    • 5 bikabike 之间的单词。

    总计:aaaabike 之间的 23249 个字。

    这给了我们一个python程序:

    def find_word(w):
      count_attempts = 0
      for i,c in enumerate(w):
        n = ord(c) - ord('a')
        count_attempts += n * 26**(len(w) - i - 1)
      return count_attempts
    

    注意这里的for-loop 是在w 的字符上,而不是在所有可能的单词上;所以我们迭代了 4 次而不是 23249 次。此功能比其他版本快很多

    【讨论】:

    【解决方案3】:

    您可以使用itertools.product(用于嵌套for循环)+dict.values(直接循环字母)+enumerate(从1开始,获取计数器):

    from itertools import product
    
    word = 'bike'
    
    for counter, letters in enumerate(product(a.values(), repeat = len(word)), 1):
      string = ''.join(letters)
      print(string, counter)
      if string == word:
          break
    

    输出:

    ...
    ...
    ...
    bika 23245
    bikb 23246
    bikc 23247
    bikd 23248
    bike 23249
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-07
      相关资源
      最近更新 更多