Поиск отличий между элементами списка


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).
blhsing

3
На самом деле просто list(map(operator.sub, t[1:], t[:-1]))подойдет.
blhsing 06

Гениально! Мне очень нравится этот ответ!
Хаим Фридман

104

Другие ответы верны, но если вы выполняете числовую работу, вы можете подумать о numpy. Используя numpy, ответ:

v = numpy.diff(t)

Очень полезно! Спасибо! np.diff([2,4,9])будет[2,5]
TravelTrader 07

Будет ли это эффективнее 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. Очевидно, что если вы начнете с a, 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]])

К вашему сведению: это будет работать только для списков.

для массивов numpy

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

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

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
да, это хорошо, но я думаю, что это должно было быть v = [x [0] -x [1] для x в zip (t [1:], t [: - 1])] для отсортированного списка!
Амит Карник

0

Решение с периодическими границами

Иногда при численном интегрировании вам может понадобиться различать список с периодическими граничными условиями (так, чтобы первый элемент вычислял разницу до последнего. В этом случае полезна функция numpy.roll:

v-np.roll(v,1)

Решения с нулем в начале

Другое решение numpy (просто для полноты) - использовать

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.