我编写了解决codewars.com问题的代码。我的代码通过了Codewars站点上的所有测试。这是一个简单的问题,可以天真地解决,但是我很想改善结构。

问题的提示如下:


编写函数,persistence,它接受一个正参数num
,并返回其乘法持久性,这是必须将num中的数字相乘直到达到单个
数字的次数。 />
例如:

persistence(39) => 3  # Because 3*9 = 27, 2*7 = 14, 1*4=4
                      # and 4 has only one digit.



所以我的解决方案如下:

def get_digits(num, digits=[]):
    while num > 0:
        digits.append(num % 10)
        return get_digits(num / 10, digits)
    if num == 0:
        return digits


def multiply_all(digits, multiplier=1):
    while len(digits) > 0:
        multiplier = multiplier * digits.pop(0)
        return multiply_all(digits, multiplier)
    if len(digits) == 0:
        return multiplier


def persistence(num, count=0):
    while num >= 10:
        num = multiply_all(get_digits(num))
        count += 1
        return persistence(num, count)
    if num < 10:
        return count


我当时以为我可以将这三个递归函数合并为一个包含所有三个递归函数的函数,但是我真的很想从经验丰富的程序员那里得到建议,因此,我非常感谢您抽出宝贵的时间提出建议。 />

评论

我对Python不太熟悉。是可选参数吗?任何调用您的代码的代码仍可以使用持久性(39)吗?

@BrianJ count = 0表示它是可选的,默认值为0

#1 楼

Python 3.x错误

您是否知道如果在Python 3上执行,您的程序会输出1的数字39

这是由于/除法运算符在此行上:

return get_digits(num / 10, digits)


更改了Python 3.x中/的含义(PEP 238)。切换到//将是一个快速修复。


改进解决方案


我想我可以合并这三个小递归
函数合并成一个包含所有三个函数的函数。 map(int, str(num))。为了对一个数字进行乘数运算,我们可以使用reduce()并应用operator.mul(乘法运算符)函数:完全可以,因为您要进行递归操作并具有num < 10基本案例作为递归“退出条件”。 ”,而不是来回转换类型:

from functools import reduce
from operator import mul


def persistence(number, count=0):
    # recursion base case - exit once the number is less than 10
    if number < 10:
        return count

    # get new number by multiplying digits of a number
    new_number = reduce(mul, map(int, str(number)))

    return persistence(new_number, count + 1)


这通常也比第一个版本快。

评论


\ $ \ begingroup \ $
这是一种有趣的方式!我想我一直在避免使用字符串的数字属性。我是否对它忌讳有错误的印象?但是,您的解决方案绝对非常简洁。我喜欢。是的...我是在Python 2.7中编写的,当时并没有考虑太多。我需要养成更好的习惯来编写交叉兼容的代码。而且我不知道我如何错过while循环。没错感谢您的帮助!
\ $ \ endgroup \ $
–凯特琳·昆特罗·韦弗(Caitlin Quintero Weaver)
17 Mar 3 '17 at 4:48

\ $ \ begingroup \ $
@CaitlinQuinteroWeaver是的,这部分是有争议的-有一种“数学”的方法来获取数字的位数-使用此解决方案可以更新答案。谢谢。
\ $ \ endgroup \ $
– alecxe
17年3月3日在5:00

\ $ \ begingroup \ $
我认为在这种情况下while循环实际上比递归解决方案更好。同样,将字符串转换为整数也是一个耗时的过程。绝对使用数学方法来检索数字。
\ $ \ endgroup \ $
– pzp
17年3月3日在5:18

\ $ \ begingroup \ $
@pzp repl.it不同意:它们大致相同。
\ $ \ endgroup \ $
–临时狼
17 Mar 3 '17 at 5:37



\ $ \ begingroup \ $
@TemporalWolf在我的计算机上,在iPython中用10e6到10e9范围内的数字进行测试,数学解决方案至少快了10倍。
\ $ \ endgroup \ $
– pzp
17 Mar 3 '17 at 5:43

#2 楼

您所拥有的通常都很好。我只有几点注意事项。

get_digits中的默认参数可能不执行您期望的操作。该列表将在对get_digits函数的所有调用中共享。例如,尝试先调用get_digits(12),然后再调用get_digits(7)。输出? [2, 1, 7]。快速解决方案是将默认值设置为None,然后有条件地将digits初始化为空列表if digits is None。出于效率原因,您可能希望避免每次get_digits都创建新列表。叫。我建议您改为使用get_digits关键字使yield成为生成器函数。 (我还给了它一个可选的任意基本参数,只是为了好玩。)最后,使所有函数迭代。它的性能可能比递归解决方案更好,并且对大多数人而言在逻辑上更清晰。

总而言之...

from functools import reduce
from operator import mul


def get_digits(num, base=10):
    while num > 0:
        yield num % base
        num /= base


def product(it):
    return reduce(mul, it, 1)


def persistence(num):
    count = 0
    while num > 9:
        num = product(get_digits(num))
        count += 1
    return count


#3 楼


我建议您不要使用Python进行递归。 Python具有最大的递归深度,默认深度约为1000。
这是为了防止在使用递归时出现系统问题,而使用sys.setrecursionlimit的修复程序是变通办法,实际上并不能解决问题。
相反,要消除这些麻烦,请不要使用递归。

不要将可变数据类型作为默认参数传递,就像您两次调用get_digits一样,它将输出两个数字。例如:

>>> get_digits(12345)
[5, 4, 3, 2, 1]
>>> get_digits(12345)
[5, 4, 3, 2, 1, 5, 4, 3, 2, 1]



如果需要弹出数据,请尝试在list.pop()上使用list.pop(0)。这是因为前者仅需删除一个项目,而后者则必须删除列表中的第一个项目,然后将所有项目下移以代替它。
此问题谈论与deque.popleft相关的这些性能问题。但是,在这种情况下,您无需双端队列或在这种情况下反转数组。

以下等效于list.pop

def pop(list, index=None):
    end = len(list) - 1
    if index is None:
        index = end
    result = list[index]
    for i in range(index, end):
        list[i] = list[i + 1]
    del list[end]
    return result



但是可以进行一些非重大更改:


如果您继续使用递归,请不要使用while代替if
如果删除了递归,则无需在函数中使用if
而不是同时使用a % ba // b,而可以使用divmod
Python中的值可以隐式转换给布尔值。一个空数组是False,其中至少包含一项的数组是True
这也发生在整数中,0False,其中所有其他数字都是True。不遭受上述问题可能导致:

def get_digits(num):
    digits = []
    while num:
        num, digit = divmod(num, 10)
        digits.append(digit)
    return digits


def multiply_all(digits):
    multiplier = 1
    while digits:
        multiplier *= digits.pop()
    return multiplier


def persistence(num):
    count = 0
    while num >= 10:
        num = multiply_all(get_digits(num))
        count += 1
    return count


#4 楼

在不更改任何其他函数的情况下,将count传递给递归函数并不是进行递归的最干净方法:

def persistence(number):
    if number < 10:
        return 0

    new_number = multiply_all(get_digits(number))
    return 1 + persistence(new_number)


或更短:

def persistence(number):
    return 0 if number < 10 else 1 + persistence(multiply_all(get_digits(number)))


评论


\ $ \ begingroup \ $
将计数传递给递归函数确实使其成为尾递归,这(可能)允许编译器将其转换为迭代代码。我不知道Python实现是否真正执行了尾部调用消除功能,但是在进行此更改之前当然值得检查。
\ $ \ endgroup \ $
– Toby Speight
19年7月4日在7:41