Tìm sự khác biệt giữa các phần tử của danh sách


113

Cho một danh sách các số, làm cách nào để tìm ra sự khác biệt giữa mọi iphần tử ( ) -th và ( i+1) -th của nó ?

Tốt hơn là sử dụng một lambdabiểu thức hoặc có thể hiểu một danh sách?

Ví dụ:

Cho một danh sách t=[1,3,6,...], mục tiêu là để tìm thấy một danh sách v=[2,3,...]bởi vì 3-1=2, 6-3=3vv

Câu trả lời:


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
Trong trường hợp bạn cần sự khác biệt tuyệt đối, [abs(j-i) for i,j in zip(t, t[1:])]
Anil

Trong trường hợp bạn muốn làm cho nó hiệu quả hơn: list(itertools.starmap(operator.sub, zip(t[1:], t)))(sau khi nhập itertoolsoperator).
blhsing

3
Trên thực tế chỉ đơn giản là list(map(operator.sub, t[1:], t[:-1]))sẽ làm.
blhsing

Xuất sắc! Tôi rất thích câu trả lời này!
Chayim Friedman

104

Các câu trả lời khác đều đúng nhưng nếu bạn đang làm công việc số, bạn có thể muốn xem xét câu trả lời sai. Sử dụng numpy, câu trả lời là:

v = numpy.diff(t)

Rất hữu ích! Cảm ơn! np.diff([2,4,9])sẽ là[2,5]
TravelTrader

Điều này có hiệu quả hơn zipphiên bản này không?
user760900

35

Nếu bạn không muốn sử dụng numpycũng như không zip, bạn có thể sử dụng giải pháp sau:

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

12

Bạn có thể sử dụng itertools.teezipđể xây dựng kết quả một cách hiệu quả:

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

Hoặc sử dụng itertools.islicethay thế:

from itertools import islice

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

Bạn cũng có thể tránh sử dụng itertoolsmô-đun:

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

Tất cả các giải pháp này hoạt động trong không gian không đổi nếu bạn không cần phải lưu trữ tất cả các kết quả và hỗ trợ lặp vô hạn.


Dưới đây là một số điểm chuẩn vi mô của các giải pháp:

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

Và các giải pháp đề xuất khác:

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

Lưu ý rằng:

  • zip(L[1:], L)tương đương với zip(L[1:], L[:-1])từ zipđã kết thúc trên đầu vào ngắn nhất, tuy nhiên, nó tránh toàn bộ bản sao của L.
  • Việc truy cập các phần tử đơn lẻ theo chỉ mục rất chậm vì mọi truy cập chỉ mục là một cuộc gọi phương thức trong python
  • numpy.diffchậm vì nó phải đầu tiên chuyển đổi listmột ndarray. Rõ ràng là nếu bạn bắt đầu với một ndarraynó sẽ nhanh hơn nhiều :

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

trong giải pháp thứ hai, islice(seq, 1, None)thay vì islice(seq, 1, len(seq))làm cho nó hoạt động với các vòng lặp vô hạn
Braham Snyder

5

Sử dụng :=toán tử hải mã có sẵn trong Python 3.8+:

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

5

Tôi sẽ đề nghị sử dụng

v = np.diff(t)

cái này đơn giản và dễ đọc.

Nhưng nếu bạn muốn vcó cùng độ dài tthì

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

hoặc là

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

FYI: điều này sẽ chỉ hoạt động cho các danh sách.

cho các mảng numpy

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

câu trả lời hay, bạn cũng có thể sử dụng từ khóa thêm trước để đảm bảo cùng độ dài, hãy xem câu trả lời bên dưới, tôi nghĩ là gọn gàng hơn một chút
Adrian Tompkins

4

Một cách tiếp cận chức năng:

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

Sử dụng máy phát điện:

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

Sử dụng các chỉ số:

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

Phương thức điều hành rất hay và thanh lịch
bcattle

3

Đồng ý. Tôi nghĩ rằng tôi đã tìm thấy giải pháp thích hợp:

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

2
ya nó tốt, nhưng tôi nghĩ nó phải là v = [x [0] -x [1] cho x trong zip (t [1:], t [: - 1])] cho danh sách được sắp xếp!
Amit Karnik,

0

Giải pháp với ranh giới tuần hoàn

Đôi khi với tích hợp số, bạn sẽ muốn khác biệt danh sách với các điều kiện biên tuần hoàn (vì vậy phần tử đầu tiên tính toán sự khác biệt cho phần tử cuối cùng. Trong trường hợp này, hàm numpy.roll rất hữu ích:

v-np.roll(v,1)

Các giải pháp không có bổ sung trước

Một giải pháp phức tạp khác (chỉ để hoàn thiện) là sử dụng

numpy.ediff1d(v)

Điều này hoạt động như numpy.diff, nhưng chỉ trên một vectơ (nó làm phẳng mảng đầu vào). Nó cung cấp khả năng thêm trước hoặc nối các số vào vectơ kết quả. Điều này rất hữu ích khi xử lý các trường tích lũy thường là thông lượng trường hợp trong các biến khí tượng (ví dụ như mưa, nhiệt tiềm ẩn, v.v.), vì bạn muốn một danh sách kết quả có cùng độ dài với biến đầu vào, với mục nhập đầu tiên không bị ảnh hưởng.

Sau đó, bạn sẽ viết

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

Tất nhiên, bạn cũng có thể thực hiện việc này bằng lệnh np.diff, trong trường hợp này, mặc dù bạn cần thêm số 0 vào chuỗi với từ khóa thêm trước:

np.diff(v,prepend=0.0) 

Tất cả các giải pháp trên trả về một vectơ có cùng độ dài với đầu vào.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.