尝试使用递归解决数字到助记符问题

Trying to solve a number to mnemonics problem using recursion

提问人:trying2learn 提问时间:11/2/2022 最后编辑:trying2learn 更新时间:11/5/2022 访问量:108

问:

给定一个长度为非零的字符串化电话号码,编写一个函数,以任何顺序返回此电话号码的所有助记符。

`

def phoneNumberMnemonics(phoneNumber, Mnemonics=[''], idx=0):
    number_lookup={'0':['0'], '1':['1'], '2':['a','b','c'], '3':['d','e','f'], '4':['g','h','i'], '5':['j','k','l'], '6':['m','n','o'], '7':['p','q','r','s'], '8':['t','u','v'], '9':['w','x','y','z']}

    if idx==len(phoneNumber):
        return Mnemonics
    else:
        new_Mnemonics=[]
        for letter in number_lookup[phoneNumber[idx]]:
            for mnemonic in Mnemonics:
                new_Mnemonics.append(mnemonic+letter)
        phoneNumberMnemonics(phoneNumber, new_Mnemonics, idx+1)
        

`

如果我使用输入“1905”,我的函数将输出 null。在 return 语句之前使用 print 语句,我可以看到列表助记符是

['1w0j', '1x0j', '1y0j', '1z0j', '1w0k', '1x0k', '1y0k', '1z0k', '1w0l', '1x0l', '1y0l', '1z0l']

这是正确答案。为什么返回 null?

我还不是很擅长实现递归(还?),感谢您的帮助。

python 算法 递归 返回 助记符

评论

0赞 Gene 11/2/2022
如果不使用语句,则不会返回任何内容。return
0赞 trying2learn 11/2/2022
感谢您@Gene的回复。我在第五行有一个返回语句。不确定我是否明白出了什么问题。
0赞 D.L 11/3/2022
这个问题在没有递归的情况下可以更好地解决。
0赞 trying2learn 11/4/2022
@D.L,你能解释一下为什么吗?您的意思是因为它由于调用堆栈而增加了 O(n) 内存,还是有其他原因?
0赞 D.L 11/4/2022
@trying2learn,是的,情况差不多就是这样,而且它通常也比裸 for 或 while 循环慢。本书中有一章专门讨论递归和递归过程:amazon.co.uk/dp/B0BHL2XKCR,您可能会发现这很有用。

答:

0赞 trying2learn 11/2/2022 #1

为递归的最深调用生成了正确的列表(助记符)。但是,它没有传递回以前的调用。

要解决此问题,不仅需要在“else”块中返回助记符,还需要将其设置为等于递归函数 phone Number Mnemonics 的输出。

def phoneNumberMnemonics(phoneNumber, Mnemonics=[''], idx=0):
number_lookup={'0':['0'], '1':['1'], '2':['a','b','c'], '3':['d','e','f'], '4':['g','h','i'], '5':['j','k','l'], '6':['m','n','o'], '7':['p','q','r','s'], '8':['t','u','v'], '9':['w','x','y','z']}

print(idx, len(phoneNumber))
if idx==len(phoneNumber):
    pass
else:
    new_Mnemonics=[]
    for letter in number_lookup[phoneNumber[idx]]:
        for mnemonic in Mnemonics:
            new_Mnemonics.append(mnemonic+letter)
    Mnemonics=phoneNumberMnemonics(phoneNumber, new_Mnemonics, idx+1)
return Mnemonics

我仍然觉得我对递归的理解缺乏复杂性。欢迎提供建议、反馈和澄清。

0赞 Gene 11/4/2022 #2

这个问题有不同的递归表达式,但当你开始时,最简单的思考是“纯函数”表达式。这意味着您永远不会改变递归确定的值。相反,计算新的字符串:列表等(Python不会给你关于字符串的选择;它们总是不可变的。通过这种方式,您可以只考虑值,而不是它们如何存储以及更改它们的原因,这非常容易出错。

思考这个问题的一个纯功能方式是这样的:

  • 如果电话号码为空字符串,则返回值只是一个包含空字符串的列表。

  • 否则,将数字分解为其第一个字符和其余字符。递归获取其余部分的所有助记符 R。然后找到与第一个字母对应的所有字母,并将每个字母都预置到 R 的每个成员之前,以创建一个新字符串(这称为笛卡尔叉积,它经常在递归中出现。返回所有这些字符串。

在此表达式中,纯函数的形式为

M(n: str) -> list[str]:

它接受一串数字并返回助记符列表。

把这个想法放到python中相当简单:

LETTERS_BY_DIGIT = {
  '0':['0'],
  '1':['1'],
  '2':['a','b','c'],
  '3':['d','e','f'],
  '4':['g','h','i'],
  '5':['j','k','l'],
  '6':['m','n','o'],
  '7':['p','q','r','s'],
  '8':['t','u','v'],
  '9':['w','x','y','z'],
}

def mneumonics(n: str):
  if len(n) == 0:
    return ['']
  rest = mneumonics(n[1:])
  first = LETTERS_BY_DIGIT[n[0]]
  rtn = []  # A fresh list to return.
  for f in first:  # Cartesian cross:
    for r in rest: #   first X rest
      rtn.append(f + r);  # Fresh string
  return rtn

print(mneumonics('1905'))

请注意,此代码根本不会改变递归返回值。它创建一个新字符串的新列表。rest

当你掌握了所有的 Python 习语后,你会看到一种更流畅的方法来编写同样的东西:

def mneumonics(n: str):
  return [''] if len(n) == 0 else [
    c + r for c in LETTERS_BY_DIGIT[n[0]] for r in mneumonics(n[1:])]

这是解决此问题的最有效代码吗?绝对不行。但无论如何,这不是一件非常实际的事情。在你牢牢掌握这种思维方式之前,最好选择一个简单、正确的、易于理解的解决方案,而不是担心效率。

正如其他人所说,如果这是一个生产需求,那么在这个问题上使用递归并不是一个好的选择。

评论

0赞 trying2learn 11/4/2022
非常感谢您的指导。对于递归何时是一个不错的选择,是否有一般准则?
0赞 Gene 11/4/2022
经验法则:每当 1) 这是解决问题的简单/最自然的方法,并且 2) 它不会无意中增加运行时或存储成本的渐进性增加。此解决方案违反了 2),因为它在每个递归级别复制每个列表和每个字符串。但对于小字符串来说,这并不重要。