在列表中找到相邻数字之间的差异(Python)

8

给定一个数字列表,我要编写一段代码来查找连续元素之间的差异。例如,A = [1, 10, 100, 50, 40],函数的输出应该是[0, 9, 90, 50, 10]。以下是我尝试使用递归实现的代码:

def deviation(A):
    if len(A) < 2:
        return
    else:
        return [abs(A[0]-A[1])] + [deviation(A[1: ])]

然而,我得到的输出结果(使用上述A作为输入的示例)是[9,[90,[50,[10,None]]]]。 我应该如何正确格式化括号?(我尝试过猜测和检查,但这是我最接近的情况)如何编写代码,使其减去前一个元素而不会在第一个元素处出现索引错误? 我仍希望输出列表的第一个元素为零,但我不知道如何使用递归来实现这一点,对我而言似乎这是最好的方法。

2
numpy.abs(numpy.diff([1, 10, 100, 50, 40])) - Fred Foo
6个回答

13

您可以做:

[y-x for x, y in zip(A[:-1], A[1:])] 


>>> A = [1, 10, 100, 50, 40]
>>> [y-x for x, y in zip(A[:-1], A[1:])]
[9, 90, -50, -10]

请注意,如果右侧较小,则差异将为负数,您可以轻松解决此问题(如果您认为这是错误的),我会留下解决方案给您。

解释:

您可以通过打印列表推导式的每个部分来获得最好的解释。

  • A[:-1] 返回不包含最后一个元素的列表:[1, 10, 100, 50]
  • A[1:] 返回不包含第一个元素的列表:[10, 100, 50, 40]
  • zip(A[:-1], A[1:]) 返回[(1, 10), (10, 100), (100, 50), (50, 40)]
  • 最后一步就是返回每个元组中的差异。

2
你忘记了 abs,但还是+1。 - Fred Foo
非常低效,因为它创建了三个与初始列表大小相同(+/- 1)的中间列表。 - freakish
出于某种原因,第一个元素应该是0。 - Sukrit Kalra
@SukritKalra 可以很容易地添加,但那真的很奇怪。 - Maroun

7
最简单(最懒)的解决方案是使用numpy函数diff:
>>> A = [1, 10, 100, 50, 40]
>>> np.diff(A)
array([  9,  90, -50, -10])

如果您想要的是差异的绝对值(正如您在问题中所暗示的),那么请取数组的绝对值。

2
[abs(j-A[i+1]) for i,j in enumerate(A[:-1])]

@dawg,我一开始使用了itertools pairwise,但在删除投票之前我进行了更改,所以我仍然不明白删除投票来自何处。但是在大型列表上,pairwise的表现相当,因此无论如何都没有意义。 - Padraic Cunningham
又来一个踩,这里有一些非常悲哀的人。 - Padraic Cunningham

1
实际上,递归是一种过度设计:
def deviation(A):
    yield 0
    for i in range(len(A) - 1):
        yield abs(A[i+1] - A[i])

例子:

>>> A = [3, 5, 2]
>>> list(deviation(A))
[0, 2, 3]

编辑:然而,另一种更加简单有效的解决方案是这样的:

def deviation(A):
    prev = A[0]
    for el in A:
        yield abs(el - prev)
        prev = el

迭代范围以索引可迭代对象很少是合适的。最好使用 tee 如果采用这种方法。 - wim
@wim 代码是用 Python3+ 编写的。在 Python < 3 中使用 xrange。除此之外,在使用 range 方面没有任何不适当的地方:它简单而高效。实际上,tee 是不合适、低效、复杂且过度的。 - freakish
低效率不仅在于迭代范围,而且在于每个元素调用两次 __getitem__。列表本身是可迭代的,因此根本没有必要迭代范围并使用 list.__getitem__,最好一开始就使用 list.__iter__。这适用于Python2和Python3。 - wim
首先,__getitem__ 是在 C 级别上实现的,由于我们处理的是动态数组,因此它的时间复杂度为 O(1),非常高效。遍历列表的时间复杂度也完全相同。其次,请看一下 tee 的源代码:它正在做更复杂的事情,如追加、弹出等等。总的来说,它的效率要低得多。 - freakish
@wim 我错了,效率在我的测试中几乎相同(使用您的“wim”函数稍微快一些)。如果效率至关重要,我可能会使用“tee”,否则我会坚持使用“range”,因为解决方案更容易理解。无论如何,我不确定为什么你会对它投反对票?难道它没有回答问题吗?请随意发布您的答案。 - freakish
显示剩余3条评论

1
你可以使用列表推导式:

>>> A = [1, 10, 100, 50, 40]
>>> l=[A[0]]+A
>>> [abs(l[i-1]-l[i]) for i in range(1,len(l))]
[0, 9, 90, 50, 10]

1

对于更长的递归解决方案,更符合您最初的方法:

def deviation(A) :
    if len(A) < 2 :
        return []
    else :
        return [abs(A[0]-A[1])] + deviation(A[1:])

你的问题在于递归调用中的括号。由于你将[deviation(a[1:])]放在自己的[]括号中,每次递归调用都会创建一个新列表,导致你有许多嵌套列表。
为了解决None问题,只需将基本情况更改为空列表[]即可。现在,你的函数将在递归生成的列表末尾添加“nothing”,而不是返回空的None

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接