Знаходження відмінностей між елементами списку


113

Подаючи список чисел, як можна знайти відмінності між кожним ( i) -ми елементами та його (i+1 ) -ми?

Чи краще використовувати lambdaвираз чи, можливо, розуміння списку?

Наприклад:

З огляду на список t=[1,3,6,...], мета полягає в тому, щоб знайти список , v=[2,3,...]тому що 3-1=2, 6-3=3і т.д.

Відповіді:


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
Якщо вам потрібні абсолютні різниці, [abs(j-i) for i,j in zip(t, t[1:])]
Аніль,

У випадку, якщо ви хочете зробити це більш ефективним: list(itertools.starmap(operator.sub, zip(t[1:], t)))(після імпорту itertoolsта operator).
блхзінг

3
Насправді просто list(map(operator.sub, t[1:], t[:-1]))зроблять.
блхзінг

Блискуче! Я дуже люблю цю відповідь!
Хаїм Фрідман

104

Інші відповіді є правильними, але якщо ви робите числові роботи, ви можете розглянути питання про те, що було багато. Використовуючи numpy, відповідь така:

v = numpy.diff(t)

Дуже корисний! Дякую! np.diff([2,4,9])буде[2,5]
TravelTrader

Це було б ефективніше, ніж zipверсія?
user760900

35

Якщо ви не хочете використовувати numpyні zip, ви можете скористатися наступним рішенням:

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

12

Ви можете використовувати itertools.teeта zipефективно будувати результат:

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

Або використовуючи itertools.isliceзамість цього:

from itertools import islice

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

Ви також можете уникати використання itertoolsмодуля:

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

Усі ці рішення працюють у постійному просторі, якщо вам не потрібно зберігати всі результати та підтримувати нескінченні ітерабелі.


Ось кілька мікро-орієнтирів рішень:

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

І інші запропоновані рішення:

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

Зауважте, що:

  • zip(L[1:], L)еквівалентна zip(L[1:], L[:-1])тому, що zipвже закінчується за найкоротшим вкладом, проте це дозволяє уникнути цілої копії L.
  • Доступ до окремих елементів за допомогою індексу дуже повільний, оскільки кожен доступ до індексу є викликом методу в python
  • numpy.diffце повільно , тому що він повинен спочатку перетворити listдо ndarray. Очевидно, якщо ви почнете з цього, ndarrayце буде набагато швидше:

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

у другому рішенні, islice(seq, 1, None)замість того, islice(seq, 1, len(seq))щоб він працював із нескінченними ітерабелями
Снайдер

5

Використання :=оператора моржів, доступного в Python 3.8+:

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

5

Я б запропонував використовувати

v = np.diff(t)

це просто і легко читати.

Але якщо ви хочете vмати таку ж довжину, як tтоді

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

або

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

FYI: це працюватиме лише для списків.

для нумерованих масивів

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

приємна відповідь, ви також можете використати ключове слово додавання, хоча для того, щоб забезпечити однакову довжину, див. відповідь нижче, що, на мою думку, трохи акуратніше
Адріан Томпкінс

4

Функціональний підхід:

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

Використання генератора:

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

Використання індексів:

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

Операторський метод приємний та елегантний
bcattle

3

Гаразд. Я думаю, що я знайшов правильне рішення:

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

2
ya це добре, але я думаю, це повинно бути v = [x [0] -x [1] для x in zip (t [1:], t [: - 1])] для відсортованого списку!
Аміт Карник

0

Розв’язання з періодичними межами

Іноді при числовій інтеграції вам потрібно буде відрізняти список з періодичними граничними умовами (тому перший елемент обчислює різницю до останнього. У цьому випадку корисною є функція numpy.roll:

v-np.roll(v,1)

Рішення з нульовим попередженням

Ще одне нудне рішення (тільки для повноти) - використовувати

numpy.ediff1d(v)

Це працює як numpy.diff, але лише у векторному (він вирівнює вхідний масив). Він пропонує можливість додавати чи додавати числа до отриманого вектора. Це корисно при обробці накопичених полів, що часто буває потоками метеорологічних змінних (наприклад, дощу, прихованої спеки тощо), оскільки ви хочете, щоб результат, який виходить, тієї ж довжини, що і вхідна змінна, при цьому перший запис не змінювався.

Тоді ви б писали

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

Звичайно, ви також можете це зробити за допомогою команди np.diff, але в цьому випадку вам потрібно додати нуль до серії за допомогою ключового слова prepend:

np.diff(v,prepend=0.0) 

Усі вищезазначені рішення повертають вектор, який має ту саму довжину, що і вхідний.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.