Znajdowanie różnic między elementami listy

113

Mając listę liczb, w jaki sposób można znaleźć różnice między każdym ( i) -tym elementem a jego ( i+1) -tym?

Czy lepiej jest użyć lambdawyrażenia, czy może zrozumienia listy?

Na przykład:

Biorąc pod uwagę listę t=[1,3,6,...], celem jest, aby znaleźć listę v=[2,3,...]dlatego 3-1=2, 6-3=3itp

psihodelia
źródło

Odpowiedzi:

154
>>> t
[1, 3, 6]
>>> [j-i for i, j in zip(t[:-1], t[1:])]  # or use itertools.izip in py2k
[2, 3]
SilentGhost
źródło
14
Jeśli potrzebujesz absolutnych różnic, [abs(j-i) for i,j in zip(t, t[1:])]
Anil,
Jeśli chcesz zwiększyć wydajność: list(itertools.starmap(operator.sub, zip(t[1:], t)))(po zaimportowaniu itertoolsi operator).
blhsing
3
Właściwie po prostu wystarczy list(map(operator.sub, t[1:], t[:-1])).
blhsing
Znakomity! Bardzo mi się podoba ta odpowiedź!
Chayim Friedman
104

Pozostałe odpowiedzi są poprawne, ale jeśli wykonujesz pracę numeryczną, możesz rozważyć numpy. Używając numpy, odpowiedź brzmi:

v = numpy.diff(t)
Christian Alis
źródło
Bardzo pomocne! Dzięki! np.diff([2,4,9])będzie[2,5]
TravelTrader
Czy byłoby to bardziej wydajne niż zipwersja?
user760900
35

Jeśli nie chcesz używać numpy nor zip, możesz skorzystać z następującego rozwiązania:

>>> t = [1, 3, 6]
>>> v = [t[i+1]-t[i] for i in range(len(t)-1)]
>>> v
[2, 3]
Omer Dagan
źródło
12

Możesz użyć itertools.teei zipefektywnie zbudować wynik:

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

Lub używając itertools.islicezamiast tego:

from itertools import islice

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

Możesz również uniknąć korzystania z itertoolsmodułu:

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

Wszystkie te rozwiązania działają w stałej przestrzeni, jeśli nie musisz przechowywać wszystkich wyników i obsługiwać nieskończonych iteracji.


Oto kilka mikro-benchmarków rozwiązań:

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

Oraz inne proponowane rozwiązania:

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

Zauważ, że:

  • zip(L[1:], L)jest równoważne z zip(L[1:], L[:-1])ponieważ zipjuż kończy się na najkrótszym wejściu, jednak unika całej kopii L.
  • Dostęp do pojedynczych elementów według indeksu jest bardzo powolny, ponieważ każdy dostęp do indeksu jest wywołaniem metody w Pythonie
  • numpy.diffjest powolny, ponieważ musi najpierw przekonwertować listplik ndarray. Oczywiście, jeśli zaczniesz od ndarray, będzie to znacznie szybsze:

    In [22]: arr = np.array(L)
    
    In [23]: %timeit np.diff(arr)
    100 loops, best of 3: 3.02 ms per loop
Bakuriu
źródło
w drugim rozwiązaniu islice(seq, 1, None)zamiast islice(seq, 1, len(seq))działać z nieskończonymi iteracjami
Braham Snyder
5

Korzystanie z :=operatora morsa dostępnego w Pythonie 3.8+:

>>> t = [1, 3, 6]
>>> prev = t[0]; [-prev + (prev := x) for x in t[1:]]
[2, 3]
Eugene Yarmash
źródło
5

Sugerowałbym użycie

v = np.diff(t)

jest to proste i łatwe do odczytania.

Ale jeśli chcesz vmieć taką samą długość jak twtedy

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

lub

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

FYI: to zadziała tylko w przypadku list.

dla tablic numpy

v = np.diff(np.append(t[0], t))
Chaitanya Kesanapalli
źródło
fajna odpowiedź, ale możesz również użyć słowa kluczowego poprzedzającego, aby zapewnić tę samą długość, patrz odpowiedź poniżej, która moim zdaniem jest trochę schludniejsza
Adrian Tompkins
4

Podejście funkcjonalne:

>>> 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]

Korzystanie z generatora:

>>> 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]

Korzystanie z indeksów:

>>> [a[i+1]-a[i] for i in xrange(len(a)-1)]
[2, 2, 2, 4, 2, 4, 4]
Ola Fosheim Grøstad
źródło
Metoda operatora jest miła i elegancka
bcattle
3

Dobrze. Myślę, że znalazłem odpowiednie rozwiązanie:

v = [x[1]-x[0] for x in zip(t[1:],t[:-1])]
psihodelia
źródło
2
tak, to dobrze, ale myślę, że powinno być v = [x [0] -x [1] dla x w zip (t [1:], t [: - 1])] dla posortowanej listy!
Amit Karnik
0

Rozwiązanie z okresowymi granicami

Czasami w przypadku całkowania numerycznego będziesz chciał rozróżnić listę z okresowymi warunkami brzegowymi (więc pierwszy element oblicza różnicę w stosunku do ostatniego. W tym przypadku pomocna jest funkcja numpy.roll:

v-np.roll(v,1)

Rozwiązania poprzedzone zerami

Innym niedbałym rozwiązaniem (tylko dla kompletności) jest użycie

numpy.ediff1d(v)

Działa jak numpy.diff, ale tylko na wektorze (spłaszcza tablicę wejściową). Oferuje możliwość dołączania liczb przed lub dołączania liczb do wynikowego wektora. Jest to przydatne podczas obsługi skumulowanych pól, które często są zmianami w zmiennych meteorologicznych (np. Deszcz, ciepło utajone itp.), Ponieważ chcesz otrzymać wynikową listę o tej samej długości co zmienna wejściowa, z pierwszym wpisem nietkniętym.

Wtedy byś pisał

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

Oczywiście możesz to również zrobić za pomocą polecenia np.diff, w tym przypadku musisz poprzedzić zero przed serią słowem kluczowym poprzedzającym:

np.diff(v,prepend=0.0) 

Wszystkie powyższe rozwiązania zwracają wektor o tej samej długości co dane wejściowe.

Adrian Tompkins
źródło
-1

Moja droga

>>>v = [1,2,3,4,5]
>>>[v[i] - v[i-1] for i, value in enumerate(v[1:], 1)]
[1, 1, 1, 1]
Arindam Roychowdhury
źródło
1
Używanie enumeratejest marnotrawstwem, ponieważ nie używasz value. Zobacz stackoverflow.com/a/16714453/832230
Acumenus