У меня есть список значений и функция, которая принимает аргументы n>1. Значения в списке находятся в порядке (очевидно), и мне приходится перебирать список, чтобы проверить все возможные последовательности значений.
Приведенный ниже код работает хорошо, но мне он не кажется питоническим (мне особенно не нравится конструкция «for i in range»). Есть ли более элегантный и, возможно, эффективный способ добиться того же?
# function that takes 3 arguments
def foo(a, b, c):
print(a, b, c)
# for this particular list (1,2,3), (2,3,4), (3,4,5)
# are triplets that should be analyzed by foo function
L = [1, 2, 3, 4, 5]
for i in range(len(L) - 2):
foo(*L[i : i+3])
Результат:
1 2 3
2 3 4
3 4 5
Вы можете использовать рецепт sliding_window, описанный здесь, но у меня нет проблем с тем, как вы это делаете (хотя вам следует ознакомиться с Руководством по стилю PEP 8)
Почему вы хотите, чтобы это было эффективно? В каком смысле ваш реальный вариант использования больше?
@nocomment Мой фактический вариант использования — список вершин геометрической формы (полилинии или многоугольника). Может быть большое количество вершин и большое количество фигур. Меня беспокоит, что мой код может быть неоптимальным из-за разделения списка (большинство значений в действительно длинном списке будут "разрезаны" 3 раза - в этом примере только число 3 "разрезано" 3 раза).
Обратите внимание, что PEP 8 требует L[i : i+3], а не l[i:i + 3].
@nocomment Ты уверен? Что касается пространств, PyCharm с вами не согласен. Также проверьте это: stackoverflow.com/questions/45916806/…. И почему L должно быть в верхнем регистре?
Да, я уверен. Он называет ham[lower + offset:upper + offset] неправильным, а ham[lower+offset : upper+offset] правильным, а о l говорится: «В некоторых шрифтах эти символы неотличимы от цифр один [...] Если возникает соблазн использовать букву «l», используйте вместо нее букву «L». Что говорит PyCharm?
Там написано: PEP 8: пробел E203 перед ':' Но я не собираюсь настаивать на том, что это программное обеспечение без ошибок. Насчет L конечно ты прав.
Кажется, это известная ошибка, см. результаты Google для «PEP 8: E203».






Вы можете использовать функцию windowed или sliding_window из more_itertools, которая может обрабатывать такого рода итерацию скользящего окна по последовательности.
from more_itertools import windowed
# You can use sliding_window as an alternative from more_itertools import sliding_window
def foo(a, b, c):
print(a, b, c)
l = [1, 2, 3, 4, 5]
for values in windowed(l, 3):
foo(*values)
Если вы не хотите использовать внешнюю библиотеку, вы все равно можете сделать свой код более питоническим, используя выражения-генераторы и функцию zip:
def foo(a, b, c):
print(a, b, c)
l = [1, 2, 3, 4, 5]
for values in zip(l, l[1:], l[2:]):
foo(*values)
Ваша последняя реализация с использованием zip самая быстрая.
Как вы думаете, где у вас есть выражения-генераторы?
@nocomment Я имею в виду ввод zip
Я не вижу никаких выражений-генераторов для ввода в zip, которые являются срезами, а zip сам возвращает итератор.
Я протестировал (timeit) решения Пармана, и результаты аналогичны моему коду. Я считаю решение zip наиболее элегантным (хотя согласен, что оно не имеет ничего общего с генераторами)
@nocomment @Booboo @Szym Поправьте меня, если я ошибаюсь, в Python определено выражение генератора: «Оно похоже на понимание списка, но вместо создания списка оно создает объект-генератор, который можно перебирать для создания значения в генераторе". (l, l[1:], l[2:]) создает 3 итератора, соответствующие приведенному выше определению.
Это не выражения-генераторы и не итераторы. И они совершенно не соответствуют этому определению.
Я отказываюсь от своего заявления о том, что решение Parman zip по скорости аналогично оригинальному (моему). Это как в @nocomment, ответ в 2 раза быстрее. Моя ошибка заключалась в использовании оператора print(timeit.timeit('original', globals=globals(), Number=1)) с глобальным списком L вместо использования лямбда-выражений. Хотя я до сих пор не понимаю, почему использование строкового оператора во времени радикально меняет результаты.
@Szym Не уверен, как именно вы измерили. Если вы покажете свой сценарий, возможно, я увижу причину.
@nocomment Я временно добавил это в свой вопрос. Я почти уверен, что я чего-то не понимаю в функции timeit.
@Szym Он сообщает время примерно 0,002 секунды. Насколько быстр ваш компьютер, что вы не шокированы тем, что можете выполнить 999998 итераций цикла Python 100000 раз за такое короткое время? :-) Вы не вызываете/выполняете функции. Следует использовать timeit('original()', ...) или timeit(original, ...).
для Pythonic и, возможно, более эффективно:
def foo(a, b, c):
print(a, b, c)
l = [1, 2, 3, 4, 5]
# Iterate over indices directly
for i in range(len(l) - 2):
foo(l[i], l[i + 1], l[i + 2])
Рекомендация: Возможно, предоставьте несколько эталонных тестов, чтобы проверить обоснованность утверждения «возможно, более эффективно». Само по себе, без подтверждающих доказательств, это бесполезное утверждение.
Некоторые более эффективные решения от меня для 10^5 элементов:
5.1 ± 0.1 ms me_2
5.2 ± 0.0 ms me_1
7.2 ± 0.1 ms me_3
12.8 ± 0.2 ms Parman_2
13.0 ± 0.4 ms Nayem
27.0 ± 0.4 ms original
Python: 3.12.2 (main, May 17 2024, 19:27:45) [GCC 14.1.1 20240507]
Код:
def me_1(l):
it = iter(l)
a = next(it, None)
b = next(it, None)
for c in it:
foo(a, b, c)
a = b
b = c
def me_2(l):
it = iter(l)
a = next(it, None)
b = next(it, None)
f = foo
for c in it:
f(a, b, c)
a = b
b = c
def me_3(l):
b = iter(l)
next(b, None)
c = iter(l)
next(c, None)
next(c, None)
deque(map(foo, l, b, c), 0)
def original(l):
for i in range(len(l) - 2):
foo(*l[i:i + 3])
def Parman_1(l):
for values in windowed(l, 3):
foo(*values)
def Parman_2(l):
for values in zip(l, l[1:], l[2:]):
foo(*values)
def Nayem(l):
for i in range(len(l) - 2):
foo(l[i], l[i + 1], l[i + 2])
funcs = [
original,
# Parman_1,
Parman_2,
Nayem,
me_1,
me_2,
me_3,
]
from itertools import *
from collections import deque
from timeit import timeit
from statistics import mean, stdev
import sys
import random
# Correctness
def foo(a, b, c):
result.append((a, b, c))
for n in range(20):
l = [object() for _ in range(n)]
expect = None
for f in funcs:
result = []
f(l)
if expect is None:
expect = result
else:
assert result == expect
# Speed
def foo(a, b, c):
pass
l = list(range(10**5))
times = {f: [] for f in funcs}
def stats(f):
ts = [t * 1e3 for t in sorted(times[f])[:5]]
return f'{mean(ts):5.1f} ± {stdev(ts):3.1f} ms '
for _ in range(25):
random.shuffle(funcs)
for f in funcs:
t = timeit(lambda: f(l), number=1)
times[f].append(t)
for f in sorted(funcs, key=stats):
print(stats(f), f.__name__)
print('\nPython:', sys.version)