Chỉ để cho thấy cách bạn có thể kết hợp các itertools
công thức nấu ăn , tôi sẽ mở rộng pairwise
công thức một cách trực tiếp nhất có thể trở lại window
công thức bằng cách sử dụng consume
công thức:
def consume(iterator, n):
"Advance the iterator n-steps ahead. If n is none, consume entirely."
# Use functions that consume iterators at C speed.
if n is None:
# feed the entire iterator into a zero-length deque
collections.deque(iterator, maxlen=0)
else:
# advance to the empty slice starting at position n
next(islice(iterator, n, n), None)
def window(iterable, n=2):
"s -> (s0, ...,s(n-1)), (s1, ...,sn), (s2, ..., s(n+1)), ..."
iters = tee(iterable, n)
# Could use enumerate(islice(iters, 1, None), 1) to avoid consume(it, 0), but that's
# slower for larger window sizes, while saving only small fixed "noop" cost
for i, it in enumerate(iters):
consume(it, i)
return zip(*iters)
Công window
thức tương tự như đối với pairwise
, nó chỉ thay thế phần tử duy nhất "tiêu thụ" trên tee
trình lặp thứ hai với mức tăng dần tiêu thụ trên các n - 1
trình vòng lặp. Việc sử dụng consume
thay vì gói mỗi iterator vào islice
nhanh hơn một chút (đối với các iterables đủ lớn) vì bạn chỉ trả islice
chi phí gói trong suốt consume
giai đoạn, không phải trong quá trình trích xuất từng giá trị cửa sổ (vì vậy, nó bị giới hạn bởi n
, không phải số lượng vật phẩm trong iterable
).
Hiệu suất-khôn ngoan, so với một số giải pháp khác, điều này là khá tốt (và tốt hơn bất kỳ giải pháp nào khác mà tôi đã thử nghiệm khi nó mở rộng). Đã thử nghiệm trên Python 3.5.0, Linux x86-64, sử dụng ipython
%timeit
phép thuật.
kindall là deque
giải pháp , tinh chỉnh cho hiệu suất / đúng đắn bằng cách sử dụng islice
thay vì một biểu thức máy phát điện gia đình cán và kiểm tra độ dài kết quả vì vậy nó không mang lại kết quả khi iterable là ngắn hơn so với các cửa sổ, cũng như vượt qua maxlen
những deque
địa vị thay vì theo từ khóa (tạo sự khác biệt đáng ngạc nhiên cho các đầu vào nhỏ hơn):
>>> %timeit -r5 deque(windowkindall(range(10), 3), 0)
100000 loops, best of 5: 1.87 μs per loop
>>> %timeit -r5 deque(windowkindall(range(1000), 3), 0)
10000 loops, best of 5: 72.6 μs per loop
>>> %timeit -r5 deque(windowkindall(range(1000), 30), 0)
1000 loops, best of 5: 71.6 μs per loop
Giống như giải pháp sắp xếp phù hợp trước đây, nhưng với mỗi yield win
thay đổi để yield tuple(win)
lưu trữ kết quả từ trình tạo hoạt động mà không có tất cả các kết quả được lưu trữ thực sự là một kết quả gần đây nhất (tất cả các giải pháp hợp lý khác đều an toàn trong kịch bản này) và thêm tuple=tuple
vào định nghĩa hàm để chuyển việc sử dụng tuple
từ B
trong LEGB
sang L
:
>>> %timeit -r5 deque(windowkindalltupled(range(10), 3), 0)
100000 loops, best of 5: 3.05 μs per loop
>>> %timeit -r5 deque(windowkindalltupled(range(1000), 3), 0)
10000 loops, best of 5: 207 μs per loop
>>> %timeit -r5 deque(windowkindalltupled(range(1000), 30), 0)
1000 loops, best of 5: 348 μs per loop
consume
giải pháp dựa trên hiển thị ở trên:
>>> %timeit -r5 deque(windowconsume(range(10), 3), 0)
100000 loops, best of 5: 3.92 μs per loop
>>> %timeit -r5 deque(windowconsume(range(1000), 3), 0)
10000 loops, best of 5: 42.8 μs per loop
>>> %timeit -r5 deque(windowconsume(range(1000), 30), 0)
1000 loops, best of 5: 232 μs per loop
Tương tự như consume
, nhưng else
trường hợp nội tuyến consume
để tránh chức năng gọi và n is None
kiểm tra để giảm thời gian chạy, đặc biệt đối với các đầu vào nhỏ trong đó chi phí thiết lập là một phần có ý nghĩa của công việc:
>>> %timeit -r5 deque(windowinlineconsume(range(10), 3), 0)
100000 loops, best of 5: 3.57 μs per loop
>>> %timeit -r5 deque(windowinlineconsume(range(1000), 3), 0)
10000 loops, best of 5: 40.9 μs per loop
>>> %timeit -r5 deque(windowinlineconsume(range(1000), 30), 0)
1000 loops, best of 5: 211 μs per loop
(Lưu ý bên lề: Một biến thể pairwise
sử dụng tee
với đối số mặc định là 2 lần để tạo các tee
đối tượng lồng nhau , do đó, bất kỳ trình vòng lặp đã cho nào chỉ được nâng cao một lần, không tiêu thụ độc lập số lần tăng, tương tự như câu trả lời của MrDrFenner tương tự như không được nội tuyến consume
và chậm hơn so với nội dung consume
trong tất cả các bài kiểm tra, vì vậy tôi đã bỏ qua những kết quả đó để cho ngắn gọn).
Như bạn có thể thấy, nếu bạn không quan tâm đến khả năng người gọi cần lưu trữ kết quả, phiên bản tối ưu hóa giải pháp của tôi sẽ thắng hầu hết thời gian, ngoại trừ trong "trường hợp kích thước cửa sổ nhỏ có thể lặp lại lớn" (trong đó consume
chiến thắng được nội tuyến ); nó xuống cấp nhanh chóng khi kích thước lặp tăng lên, trong khi không suy giảm chút nào khi kích thước cửa sổ tăng (mọi giải pháp khác đều giảm chậm hơn khi tăng kích thước lặp, nhưng cũng giảm khi tăng kích thước cửa sổ). Nó thậm chí có thể được điều chỉnh cho trường hợp "cần bộ dữ liệu" bằng cách gói map(tuple, ...)
, nó chạy chậm hơn một chút so với đặt bộ dữ liệu vào chức năng, nhưng nó tầm thường (mất 1-5% lâu hơn) và cho phép bạn giữ được sự linh hoạt khi chạy nhanh hơn khi bạn có thể chịu đựng nhiều lần trả lại cùng một giá trị.
Nếu bạn cần sự an toàn chống lại lợi nhuận được lưu trữ, thì consume
chiến thắng được đặt trên tất cả trừ các kích thước đầu vào nhỏ nhất (với không consume
được đặt nội tuyến sẽ chậm hơn một chút nhưng tỷ lệ tương tự). Các deque
và tupling thắng giải pháp dựa trên chỉ dành cho các đầu vào nhỏ nhất, vì chi phí thiết lập nhỏ hơn, và đạt được là nhỏ; nó xuống cấp trầm trọng khi lặp đi lặp lại lâu hơn.
Đối với hồ sơ, phiên bản chuyển thể của giải pháp kindall rằng yield
s tuple
s tôi sử dụng là:
def windowkindalltupled(iterable, n=2, tuple=tuple):
it = iter(iterable)
win = deque(islice(it, n), n)
if len(win) < n:
return
append = win.append
yield tuple(win)
for e in it:
append(e)
yield tuple(win)
Bỏ bộ nhớ đệm tuple
trong dòng định nghĩa hàm và sử dụng tuple
từng bộ yield
để có phiên bản nhanh hơn nhưng kém an toàn hơn.
sum()
hoặcmax()
), điều đáng lưu ý là có các thuật toán hiệu quả để tính giá trị mới cho mỗi cửa sổ trong thời gian không đổi (không phân biệt kích thước cửa sổ). Tôi đã thu thập một số thuật toán này cùng nhau trong một thư viện Python: roll .