Допустим, у меня есть несколько итераций:
[[1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13, 14]]
Как я могу получить только каждый элемент, который первым появляется по своему индексу в любой из итераций? В этом случае:
[1, 2, 5, 6, 14]
Визуализировано:
[1, 2]
[_, _, 5, 6]
[_, _, _]
[_, _, _, _, 14]
если мы выводим n элементов, мы должны игнорировать первые n элементов во всех итераторах, поэтому следующий (n + 1) элемент из первых итераторов, которые содержат более n элементов
С помощью генератора можно делать практически всё:
def chain_suffixes(iters):
n=0
for it in iters:
for x in itertools.islice(it,n,None):
yield x
n+=1
с Chain_siffixes(*iters) — верно! - можно ли это сделать в более функциональном стиле?
Эту функцию можно вызывать не с помощью (*iters)
, а с помощью (iters)
, что позволяет использовать бесконечные итераторы и так далее. Функциональный стиль, конечно, всегда возможен, но здесь это было бы не очень естественно из-за цепочки зависимостей от предыдущего размера (особенно, если вы еще и хотите свести к минимуму копирование).
я имею в виду (*iters), поскольку в цепочке более подходящий интерфейс для моей функции :)
@qulinxao Как так? У вас есть итерации в списке. С помощью (*iters) вам придется распаковать этот список для звонков, а затем он будет сразу же упакован обратно. Лучше просто передать и использовать список как есть.
Вот один из способов сделать это:
out = []
for sub_list in m:
out.extend(sub_list[len(out):])
# out = [1, 2, 5, 6, 14]
Для каждого подсписка мы выбираем только те элементы, которые начинаются с индекса len(out)
и до конца.
нужны итерации :(
Вы также можете использовать reduce
:
from functools import reduce
reduce(lambda a,b: a + b[len(a):], m)
Out[33]: [1, 2, 5, 6, 14]
нужны итерации :(
@qulinxao извини, я это пропустил. Конечно, это не сработает
Вы можете перебирать списки в обратном порядке и заменять фрагмент на каждой итерации.
lists = [[1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13, 14]]
result = lists[-1]
for lst in lists[-1::-1]:
result[:len(lst)] = lst
print(result) # [1, 2, 5, 6, 14]
можно ли это сделать в более функциональном стиле?
Конечно, но я бы не стал. Подход Дэвиса Херринга уже прекрасен. Вот «более функциональный» способ, но более непонятный для моих глаз:
from itertools import zip_longest
SKIP = object()
def chain_suffixes(*iters):
return (next(a for a in s if a is not SKIP)
for s in zip_longest(*iters, fillvalue=SKIP))
print(list(chain_suffixes(
[1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13, 14])))
который печатает
[1, 2, 5, 6, 14]
Кстати, «более функциональный» может показаться смотрящему. На мой взгляд, различные формы генераторного понимания столь же «функциональны», как и другие способы его написания. В крайнем случае, можно переписать вышеизложенное, вообще не используя понимания и даже не вводя «def».
from itertools import zip_longest
SKIP = object()
not_SKIP = lambda x: x is not SKIP
first_not_SKIP = lambda s: next(filter(not_SKIP, s))
chain_suffixes = lambda *iters: map(
first_not_SKIP,
zip_longest(*iters, fillvalue=SKIP))
хорошо, я делаю что-то вроде этого, но использую не object(), а None и получаю некоторую неясность :). спасибо
Если вы используете None
, может возникнуть путаница, если None
является одним из значений в повторяющихся последовательностях. Использование нового одноэлементного объекта, специфичного для модуля, для обозначения «на самом деле не существует» является обычным способом решения этой проблемы. Никакая итерация не может содержать SKIP
, если только она не обманывает внутренние компоненты модуля.
from itertools import islice
def chain_suffixes(iters):
result = []
for it in iters:
result += islice(it, len(result), None)
return result
iterables = [[1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13, 14]]
print(chain_suffixes(iterables))
Другой вариант: использование zip для упаковки элементов в одноэлементные кортежи и пустой кортеж в качестве значения заполнения:
from itertools import *
def chain_suffixes(iters):
return map(next, map(chain.from_iterable, zip_longest(*map(zip, iters), fillvalue=())))
iterables = [[1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13, 14]]
print(*chain_suffixes(iterables))
Другой, без itertools:
def chain_suffixes(iters):
iters = list(map(iter, iters))
while iters:
for x in iters.pop(0):
yield x
for it in iters:
next(it, None)
iterables = [[1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13, 14]]
print(*chain_suffixes(iterables))
Вот забавный вариант: использовать islice
, чтобы получить хвосты, chain
, чтобы соединить их в цепочку, а также compress
и count
, чтобы подсчитать произведенные элементы, чтобы получить старты islice
. Он запускается без какой-либо интерпретации Python (после его настройки).
def chain_suffixes6(iters):
counter = count()
starts = map(sub, counter, count())
tails = map(islice, iters, starts, repeat(None))
return compress(chain.from_iterable(tails), counter)
Пояснение: Представьте, что у вас есть волшебный способ получить начальные индексы для решки. Тогда это было бы просто так:
def chain_suffixes(iters):
starts = <magic here>
tails = map(islice, iters, starts, repeat(None))
return chain.from_iterable(tails)
Начальный индекс для хвоста каждой итерации — это просто количество элементов, которые мы вывели на данный момент из всех предыдущих итераций. Чтобы их подсчитать, я использую итератор count
и compress
для его перемещения:
def chain_suffixes6(iters):
counter = count()
...
return compress(chain.from_iterable(tails), counter)
После первой итерации мы могли бы просто запросить у счетчика следующий элемент. Это подскажет нам, как долго это длилось. Но тогда после второй итерации счетчик не будет просто подсчитывать произведенные элементы первой итерации и хвоста второй. Ещё бы засчитали, что один запрос для текущего счёта! Значит, нам нужно вычесть 1. И в следующий раз, когда мы попросим, нам нужно вычесть 2 и т. д. Я делаю эту компенсацию, вычитая еще один count()
:
starts = map(sub, counter, count())
На самом деле это начинается с start = 0 - 0
для «хвоста» первой итерации. Обратите внимание, что это также уже потребляет 0
из counter
, так что в
compress(chain.from_iterable(tails), counter)
он предоставляет только положительные числа, которые являются истинными, так что compress
дает все элементы из цепочки хвостов.
Это решение также является последней версией в этой последовательности решений. Каждый из них развивается из предыдущего и становится все более необычным, поэтому лучше всего читать их по порядку. (Хотя я, кстати, сразу написал версию 3, затем 4 и 5, затем написал 1 и 2, чтобы, возможно, помочь пониманию, а затем 6, когда понял, что могу «сжимать/подсчитывать» цепочку хвостов вместо каждого хвоста.)
from itertools import *
from operator import sub
def chain_suffixes1(iters):
start = 0
for it in iters:
counter = count(1)
tail = islice(it, start, None)
yield from compress(tail, counter)
start += next(counter) - 1
def chain_suffixes2(iters):
counter = count()
for i, it in enumerate(iters):
start = next(counter) - i
tail = islice(it, start, None)
yield from compress(tail, counter)
def chain_suffixes3(iters):
counter = count()
starts = map(sub, counter, count())
for it in iters:
tail = islice(it, next(starts), None)
yield from compress(tail, counter)
def chain_suffixes4(iters):
counter = count()
starts = map(sub, counter, count())
return chain.from_iterable(
compress(tail, counter)
for it in iters
for tail in [islice(it, next(starts), None)]
)
def chain_suffixes5(iters):
counter = count()
starts = map(sub, counter, count())
tails = map(islice, iters, starts, repeat(None))
tails_counted = map(compress, tails, repeat(counter))
return chain.from_iterable(tails_counted)
def chain_suffixes6(iters):
counter = count()
starts = map(sub, counter, count())
tails = map(islice, iters, starts, repeat(None))
return compress(chain.from_iterable(tails), counter)
iterables = [[1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13, 14]]
for f in chain_suffixes1, chain_suffixes2, chain_suffixes3, chain_suffixes4, chain_suffixes5, chain_suffixes6:
print(*f(iterables), f.__name__)
Глупая однострочная версия, мне просто было любопытно, как это будет выглядеть:
def chain_suffixes7(iters):
return compress(chain.from_iterable(map(islice, iters, map(sub, counter := count(), count()), repeat(None))), counter)
И распределим это по нескольким физическим линиям:
def chain_suffixes8(iters):
return compress(
chain.from_iterable(
map(islice,
iters,
map(sub, counter := count(), count()),
repeat(None))),
counter
)
Вау - это умно! И тошно ;-) Я не знаю, пытаюсь ли я сделать это как можно более неясным, но это настоящая головная боль. Если это не просто попытка вышибить мозги, поскольку chain_suffixes6
вообще не используется по своей основной цели (выбор правильной подпоследовательности), то его можно заменить, например, на несколько менее непонятный compress
. Тогда становится ясно, что значения, полученные в результате перебора return map(operator.itemgetter(0), zip(chain.from_iterable(tails), counter))
, не имеют отношения к этой конкретной строке — counter
продвигается из-за своего влияния на другие строки.
@TimPeters Хе :-). Теперь я добавил больше объяснений. Определенно не пытался быть неясным. Всего лишь пытаюсь создать штуковину itertools, которая затем будет работать без интерпретации Python (да, рискуя остаться неясной). Для меня это хобби, я занимаюсь этим ради удовольствия, ради вызова и ради скорости. И я на самом деле считаю, что версия 6 довольно чистая, я ею очень доволен. Вероятно, довольно быстро. Это одна из причин, по которой я предпочитаю сжатие вместо карты+itemgetter+zip, не говоря уже о более коротком коде. Внутренняя проверка истинности int выполняется быстрее, чем сопоставление метода получения элемента с кортежем.
(продолжение) Еще одна причина в том, что я хочу продемонстрировать сжатие, поскольку думаю, что многие люди еще не знают о нем. Я использовал его с настоящими «селекторами» довольно много раз, в том числе для ограничения длины одной итерации другой. Буквально вчера я придумал еще одно аккуратное использование, и я мог бы поделиться им через новый вопрос.
Можете ли вы объяснить логику