Encontrando diferenças entre os elementos de uma lista


113

Dada uma lista de números, como encontrar diferenças entre cada ( i) -ésimo elemento e seu ( i+1) -ésimo elemento?

É melhor usar uma lambdaexpressão ou talvez uma compreensão de lista?

Por exemplo:

Dada uma lista t=[1,3,6,...], o objetivo é encontrar uma lista v=[2,3,...]porque 3-1=2, 6-3=3, etc.

Respostas:


154
>>> t
[1, 3, 6]
>>> [j-i for i, j in zip(t[:-1], t[1:])]  # or use itertools.izip in py2k
[2, 3]

14
Caso você precise de diferenças absolutas, [abs(j-i) for i,j in zip(t, t[1:])]
Anil

Caso queira torná-lo mais eficiente: list(itertools.starmap(operator.sub, zip(t[1:], t)))(após a importação itertoolse operator).
blhsing

3
Na verdade, simplesmente list(map(operator.sub, t[1:], t[:-1]))servirá.
blhsing de

Brilhante! Eu amo muito essa resposta!
Chayim Friedman

104

As outras respostas estão corretas, mas se você estiver fazendo um trabalho numérico, considere o numpy. Usando numpy, a resposta é:

v = numpy.diff(t)

Muito útil! Obrigado! np.diff([2,4,9])seria[2,5]
TravelTrader

Isso seria mais eficiente do que a zipversão?
user760900

35

Se você não deseja usar numpynem zip, pode usar a seguinte solução:

>>> t = [1, 3, 6]
>>> v = [t[i+1]-t[i] for i in range(len(t)-1)]
>>> v
[2, 3]

12

Você pode usar itertools.teee zippara construir o resultado de forma eficiente:

from itertools import tee
# python2 only:
#from itertools import izip as zip

def differences(seq):
    iterable, copied = tee(seq)
    next(copied)
    for x, y in zip(iterable, copied):
        yield y - x

Ou usando itertools.islice:

from itertools import islice

def differences(seq):
    nexts = islice(seq, 1, None)
    for x, y in zip(seq, nexts):
        yield y - x

Você também pode evitar o uso do itertoolsmódulo:

def differences(seq):
    iterable = iter(seq)
    prev = next(iterable)
    for element in iterable:
        yield element - prev
        prev = element

Todas essas soluções funcionam em espaço constante se você não precisa armazenar todos os resultados e suportar iteráveis ​​infinitos.


Aqui estão alguns micro-benchmarks das soluções:

In [12]: L = range(10**6)

In [13]: from collections import deque
In [15]: %timeit deque(differences_tee(L), maxlen=0)
10 loops, best of 3: 122 ms per loop

In [16]: %timeit deque(differences_islice(L), maxlen=0)
10 loops, best of 3: 127 ms per loop

In [17]: %timeit deque(differences_no_it(L), maxlen=0)
10 loops, best of 3: 89.9 ms per loop

E as outras soluções propostas:

In [18]: %timeit [x[1] - x[0] for x in zip(L[1:], L)]
10 loops, best of 3: 163 ms per loop

In [19]: %timeit [L[i+1]-L[i] for i in range(len(L)-1)]
1 loops, best of 3: 395 ms per loop

In [20]: import numpy as np

In [21]: %timeit np.diff(L)
1 loops, best of 3: 479 ms per loop

In [35]: %%timeit
    ...: res = []
    ...: for i in range(len(L) - 1):
    ...:     res.append(L[i+1] - L[i])
    ...: 
1 loops, best of 3: 234 ms per loop

Observe que:

  • zip(L[1:], L)é equivalente a zip(L[1:], L[:-1])uma vez que zipjá termina na entrada mais curta, porém evita uma cópia inteira de L.
  • O acesso a elementos únicos por índice é muito lento porque todo acesso de índice é uma chamada de método em python
  • numpy.diffé lento porque primeiro precisa converter o listem a ndarray. Obviamente, se você começar com um ndarray, será muito mais rápido:

    In [22]: arr = np.array(L)
    
    In [23]: %timeit np.diff(arr)
    100 loops, best of 3: 3.02 ms per loop

na segunda solução, em islice(seq, 1, None)vez de islice(seq, 1, len(seq))fazê-lo funcionar com iteráveis ​​infinitos
Braham Snyder

5

Usando o :=operador walrus disponível no Python 3.8+:

>>> t = [1, 3, 6]
>>> prev = t[0]; [-prev + (prev := x) for x in t[1:]]
[2, 3]

5

Eu sugeriria usar

v = np.diff(t)

isso é simples e fácil de ler.

Mas se você quiser vter o mesmo comprimento que t, em seguida,

v = np.diff([t[0]] + t) # for python 3.x

ou

v = np.diff(t + [t[-1]])

FYI: isso só funcionará para listas.

para matrizes numpy

v = np.diff(np.append(t[0], t))

boa resposta, mas você também pode usar a palavra-chave prefixar para garantir o mesmo comprimento, veja a resposta abaixo, que eu acho um pouco mais organizada
Adrian Tompkins

4

Uma abordagem funcional:

>>> import operator
>>> a = [1,3,5,7,11,13,17,21]
>>> map(operator.sub, a[1:], a[:-1])
[2, 2, 2, 4, 2, 4, 4]

Usando gerador:

>>> import operator, itertools
>>> g1,g2 = itertools.tee((x*x for x in xrange(5)),2)
>>> list(itertools.imap(operator.sub, itertools.islice(g1,1,None), g2))
[1, 3, 5, 7]

Usando índices:

>>> [a[i+1]-a[i] for i in xrange(len(a)-1)]
[2, 2, 2, 4, 2, 4, 4]

O método do operador é bom e elegante
bcattle

3

Está bem. Acho que encontrei a solução adequada:

v = [x[1]-x[0] for x in zip(t[1:],t[:-1])]

2
sim, é bom, mas acho que deveria ser v = [x [0] -x [1] para x no zip (t [1:], t [: - 1])] para a lista classificada!
Amit Karnik

0

Solução com limites periódicos

Às vezes, com a integração numérica, você desejará diferenciar uma lista com condições de contorno periódicas (de modo que o primeiro elemento calcule a diferença até o último. Nesse caso, a função numpy.roll é útil:

v-np.roll(v,1)

Soluções com zero prefixado

Outra solução entorpecente (apenas para completar) é usar

numpy.ediff1d(v)

Isso funciona como numpy.diff, mas apenas em um vetor (nivela a matriz de entrada). Ele oferece a capacidade de preceder ou acrescentar números ao vetor resultante. Isso é útil ao lidar com campos acumulados que geralmente são os fluxos de caso em variáveis ​​meteorológicas (por exemplo, chuva, calor latente, etc.), pois você deseja uma lista resultante do mesmo comprimento da variável de entrada, com a primeira entrada intacta.

Então você escreveria

np.ediff1d(v,to_begin=v[0])

Claro, você também pode fazer isso com o comando np.diff, neste caso, embora você precise acrescentar zero à série com a palavra-chave prefixar:

np.diff(v,prepend=0.0) 

Todas as soluções acima retornam um vetor com o mesmo comprimento da entrada.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.