13

非常快速和简单的家庭作业问题。我运行正常,但我认为有更好的
方法来做到这一点。一种更 Pythonic 的方式。
这是我将列表的每个元素递归递减 1 的代码。

l = range(30)  
def recurseDecrMap(l, x = []):  
    if len(l) == 0:  
        return []  
    else:  
        x.append(l[0] -1)  
    recurseDecrMap(l[1:], x)  
    return x  

所以感谢您的任何意见。我正在努力学习做更好的递归。难以
掌握它的诀窍。

4

7 回答 7

25

可能不那么pythonic,但是有:

def recurseDecrMap(l):
    return [l[0]-1] + recurseDecrMap(l[1:]) if l else []
于 2013-04-27T23:33:49.063 回答
14

您只能使用一个参数,在我看来它更简单:

def recurseDecrMap(l):  
    if not l:  
        return []  
    else:
        return [l[0]-1] + recurseDecrMap(l[1:])

但正如@jamylak 指出的那样,该算法的复杂性是 O(N^2),因为l[1:]创建了一个新列表,其中引用了列表中的其余项目。

如果您需要效率,我建议您使用列表推导(海德罗的回答),但如果您仅出于学习目的而想要它,我认为这不是优先事项。

于 2013-04-27T23:27:15.433 回答
9

就其价值而言,这是学习递归的一种糟糕方法,因为您正在使用它来做一些本质上不是递归的事情。如果你的老师真的要你写一个程序来递减列表中的元素,比如[1, 2, 3, 4] 递归,那么他/她会感到羞耻。

正如 Haidro 所指出的,解决这个问题的最 Pythonic 的方法是使用列表推导来迭代列表

[i - 1 for i in l]

作为一个循环,这是

def decr(l):
    a = []
    for i in l:
        a.append(i - 1)
    return a

如果您想在任意深度级别解决相同的问题,递归很有用。例如,假设您有类似的东西,[1, [2, 3], [[4], 5]]并且您想将每个数字减 1,同时保持列表结构。在这种情况下,递归解决方案将使用迭代解决方案作为基本情况,并为递归情况调用自身。

def decr_recursive(l):
    a = []
    for i in l:
        if isinstance(i, list):
            a.append(decr_recursive(i))
        else:
            a.append(i - 1)
    return a

如果您想支持的不仅仅是列表或整数,这可以很容易地修改。

>>> decr([1, [2, 3], [[4], 5]])
[0, [1, 2], [[3], 4]]

是一种不使用递归很难解决的问题,但使用递归很容易解决。您要问的是那种无需递归即可轻松解决的问题(看在上帝的份上,这只是对列表的简单迭代),但用它解决有点困难。

在 Python 中应该避免递归的一些原因

  • 更难阅读。比较[i - 1 for i in l],甚至是显式循环,类似于

    def decr(l):
        if not l:
            return []
        return [l[0] - 1] + decr(l[:1])
    
  • 在 Python 中调用函数可能很昂贵。我在计算机上得到的时间与 Ashwini Chaudhary 大致相同。但[i - 1 for i in range(10**4)]在我的电脑上需要 559 µs。这比最快的递归方法快三个数量级。

  • 除非您将递归限制设置得更高,否则递归函数在 1000 次调用之后将无法工作。您可能已经注意到sys.setrecursionlimit(10**5)Ashwini Chaudhary 的回答中的电话。这是必要的,因为没有它,每次调用都会导致RuntimeError: maximum recursion depth exceeded一个巨大的回溯。但即便如此,更大的列表仍然会导致递归限制。根据文档,每个操作系统都有一个上限,将其设置得太高可能会导致崩溃。

  • 递归函数更难调试。它们不仅会通过来自同一函数的数百次调用污染您的堆栈跟踪,而且它们在概念上更难以遵循,因为同一函数的同一部分以不同的方式使用,具体取决于您在堆栈中的哪个级别. 人类的自然思维方式是迭代的。我们一次做一件事情。我们自己大脑的“堆栈”只有几层深,所以我们很难用递归的方式解决问题,比如“让我开始解决一个问题,但在我完成之前,让我解决另一个问题,然后等我做完了,我会把原来的问题做完。而在较小的问题中,我可能会做同样的事情,这样我就可以在完成之前获得几个层次的深度。这就是为什么你走进厨房拿笔,然后你看到一块糖果并开始吃它,当你吃完时,你忘记了笔。你“递归”了一个关卡,从钢笔问题到糖果条问题,你的心理堆栈太深了(只有两个关卡,但这已经足够了)。如果您取而代之的是抓住了糖果棒,但在您打开它并开始吃之前,还找到了笔(我能想到的最好的迭代模拟),您会同时做两件事而不会忘记。你在程序中解决问题的方式应该与你在头脑中解决问题的方式完全相同,因为这是你理解代码在做什么的唯一方式。Python 是一门很棒的语言,因为它的高级接口让你可以做到这一点(至少比低级语言更频繁)。你“递归”了一个关卡,从钢笔问题到糖果条问题,你的心理堆栈太深了(只有两个关卡,但这已经足够了)。如果您取而代之的是抓住了糖果棒,但在您打开它并开始吃之前,还找到了笔(我能想到的最好的迭代模拟),您会同时做两件事而不会忘记。你在程序中解决问题的方式应该与你在头脑中解决问题的方式完全相同,因为这是你理解代码在做什么的唯一方式。Python 是一门很棒的语言,因为它的高级接口让你可以做到这一点(至少比低级语言更频繁)。你“递归”了一个关卡,从钢笔问题到糖果条问题,你的心理堆栈太深了(只有两个关卡,但这已经足够了)。如果您取而代之的是抓住了糖果棒,但在您打开它并开始吃之前,还找到了笔(我能想到的最好的迭代模拟),您会同时做两件事而不会忘记。你在程序中解决问题的方式应该与你在头脑中解决问题的方式完全相同,因为这是你理解代码在做什么的唯一方式。Python 是一门很棒的语言,因为它的高级接口让你可以做到这一点(至少比低级语言更频繁)。如果您取而代之的是抓住了糖果棒,但在您打开它并开始吃之前,还找到了笔(我能想到的最好的迭代模拟),您会同时做两件事而不会忘记。你在程序中解决问题的方式应该与你在头脑中解决问题的方式完全相同,因为这是你理解代码在做什么的唯一方式。Python 是一门很棒的语言,因为它的高级接口让你可以做到这一点(至少比低级语言更频繁)。如果您取而代之的是抓住了糖果棒,但在您打开它并开始吃之前,还找到了笔(我能想到的最好的迭代模拟),您会同时做两件事而不会忘记。你在程序中解决问题的方式应该与你在头脑中解决问题的方式完全相同,因为这是你理解代码在做什么的唯一方式。Python 是一门很棒的语言,因为它的高级接口让你可以做到这一点(至少比低级语言更频繁)。因为这是您了解代码在做什么的唯一方法。Python 是一门很棒的语言,因为它的高级接口让你可以做到这一点(至少比低级语言更频繁)。因为这是您了解代码在做什么的唯一方法。Python 是一门很棒的语言,因为它的高级接口让你可以做到这一点(至少比低级语言更频繁)。利用这个事实!

于 2013-04-28T02:02:01.090 回答
7

这是最糟糕的方法 - 使用Fixed Point Combinator

Y = lambda g: (lambda f: g(lambda arg: f(f)(arg))) (lambda f: g(lambda arg: f(f)(arg)))
recurseDecrMap = Y(lambda f: lambda l: [l[0]-1] + f(l[1:]) if l else [])
于 2013-04-27T23:49:40.707 回答
2

这是一个简单的pythonic方式:

>>> mylist = range(30)
>>> def func(l):
...     return [i-1 for i in l]
>>> func(mylist)
[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]

解释:

我使用列表推导来创建每个元素的新列表,其中每个元素mylist的值都比原来的值小一。

您的代码没有任何问题,除非您要多次使用它:

>>> recurseDecrMap(l)
[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]

>>> recurseDecrMap(l)
[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]

为避免这种情况,请查看此答案

于 2013-04-27T23:22:29.393 回答
2

执行此操作的各种方法的时间比较:

In [1]: import sys

In [2]: sys.setrecursionlimit(10**5)

In [3]: from so import *

In [4]: %timeit recur(range(10**4)).show()
10 loops, best of 3: 18.2 ms per loop

In [5]: %timeit recurse1(range(10**4))
1 loops, best of 3: 559 ms per loop

In [6]: %timeit recurse2(range(10**4))
1 loops, best of 3: 1e+03 ms per loop

In [7]: %timeit recurse3(range(10**4))
1 loops, best of 3: 1.02 s per loop

In [8]: %timeit recurse4(range(10**4))
1 loops, best of 3: 596 ms per loop

代码:

class recur:
    # No extra memory is required in this method

    def __init__(self,lis):
        self.lis=lis
        self.len=len(self.lis)
        self.rec(0)

    def show(self):
        return self.lis

    def rec(self,n):
        if n!=self.len:
            self.lis[n]-=1
            self.rec(n+1)

def recurse1(l,lis=None):
    lis=lis if lis is not None else []
    if l:
        lis.append(l[0]-1)
        return recurse1(l[1:],lis)
    else:
        return lis

def recurse2(l):
    return [l[0]-1] + recurse2(l[1:]) if l else []

def recurse3(l):  
    if len(l) == 0:  
        return []  
    else:
        return [l[0] -1] + recurse3(l[1:])

def recurse4(l, x = []):  
    if len(l) == 0:  
        return []  
    else:  
        x.append(l[0] -1)  
    recurse4(l[1:], x)  
    return x  
于 2013-04-27T23:25:54.843 回答
1

这是一个递归解决方案,可以在不达到递归深度限制的情况下处理大型列表。通过使用分而治之,递归深度在最坏的情况下是 O(log(N)),而使用朴素递归则为 O(N)。但是,对于这个问题,任何类型的递归都是一个糟糕的技术选择,因为它可以通过一个简单的 for 循环轻松解决。

def dec_list(xs, a, b):
    if b == a + 1:
        xs[a] -= 1
    if a + 1 >= b: return
    mid = (a + b) // 2
    dec_list(xs, a, mid)
    dec_list(xs, mid, b)

def dec(xs):
    dec_list(xs, 0, len(xs))

xs = range(1001)
dec(xs)
print xs
于 2013-04-28T06:10:51.663 回答