Как можно объединить итерации, сохраняя только первый элемент с каждым индексом?

Допустим, у меня есть несколько итераций:

[[1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13, 14]]

Как я могу получить только каждый элемент, который первым появляется по своему индексу в любой из итераций? В этом случае:

[1, 2, 5, 6, 14]

Визуализировано:

[1, 2]
[_, _, 5, 6]
[_, _, _]
[_, _, _, _, 14]

Можете ли вы объяснить логику

Onyambu 03.07.2024 05:37

если мы выводим n элементов, мы должны игнорировать первые n элементов во всех итераторах, поэтому следующий (n + 1) элемент из первых итераторов, которые содержат более n элементов

qulinxao 03.07.2024 05:40
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
2
169
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

С помощью генератора можно делать практически всё:

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) — верно! - можно ли это сделать в более функциональном стиле?

qulinxao 03.07.2024 06:01

Эту функцию можно вызывать не с помощью (*iters), а с помощью (iters), что позволяет использовать бесконечные итераторы и так далее. Функциональный стиль, конечно, всегда возможен, но здесь это было бы не очень естественно из-за цепочки зависимостей от предыдущего размера (особенно, если вы еще и хотите свести к минимуму копирование).

Davis Herring 03.07.2024 06:51

я имею в виду (*iters), поскольку в цепочке более подходящий интерфейс для моей функции :)

qulinxao 03.07.2024 07:43

@qulinxao Как так? У вас есть итерации в списке. С помощью (*iters) вам придется распаковать этот список для звонков, а затем он будет сразу же упакован обратно. Лучше просто передать и использовать список как есть.

no comment 03.07.2024 15:00

Вот один из способов сделать это:

out = []
for sub_list in m:
    out.extend(sub_list[len(out):])
# out = [1, 2, 5, 6, 14]

Для каждого подсписка мы выбираем только те элементы, которые начинаются с индекса len(out) и до конца.

нужны итерации :(

qulinxao 03.07.2024 06:03

Вы также можете использовать reduce:

from functools import reduce

reduce(lambda a,b: a + b[len(a):], m)

Out[33]: [1, 2, 5, 6, 14]

нужны итерации :(

qulinxao 03.07.2024 06:02

@qulinxao извини, я это пропустил. Конечно, это не сработает

Onyambu 03.07.2024 06:06

Вы можете перебирать списки в обратном порядке и заменять фрагмент на каждой итерации.

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 и получаю некоторую неясность :). спасибо

qulinxao 03.07.2024 07:46

Если вы используете None, может возникнуть путаница, если None является одним из значений в повторяющихся последовательностях. Использование нового одноэлементного объекта, специфичного для модуля, для обозначения «на самом деле не существует» является обычным способом решения этой проблемы. Никакая итерация не может содержать SKIP, если только она не обманывает внутренние компоненты модуля.

Tim Peters 03.07.2024 07:51
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 продвигается из-за своего влияния на другие строки.

Tim Peters 04.07.2024 20:53

@TimPeters Хе :-). Теперь я добавил больше объяснений. Определенно не пытался быть неясным. Всего лишь пытаюсь создать штуковину itertools, которая затем будет работать без интерпретации Python (да, рискуя остаться неясной). Для меня это хобби, я занимаюсь этим ради удовольствия, ради вызова и ради скорости. И я на самом деле считаю, что версия 6 довольно чистая, я ею очень доволен. Вероятно, довольно быстро. Это одна из причин, по которой я предпочитаю сжатие вместо карты+itemgetter+zip, не говоря уже о более коротком коде. Внутренняя проверка истинности int выполняется быстрее, чем сопоставление метода получения элемента с кортежем.

no comment 05.07.2024 10:45

(продолжение) Еще одна причина в том, что я хочу продемонстрировать сжатие, поскольку думаю, что многие люди еще не знают о нем. Я использовал его с настоящими «селекторами» довольно много раз, в том числе для ограничения длины одной итерации другой. Буквально вчера я придумал еще одно аккуратное использование, и я мог бы поделиться им через новый вопрос.

no comment 05.07.2024 10:45

Другие вопросы по теме

Похожие вопросы

Списки массивов HTML-элементов, упорядоченные по веб-сайтам в селене/красивом супе
Как вызвать asyncio.create_task() внутри asyncio.create_task()?
Как написать выражение REGEXP_EXTRACT в конфигурации Google Cloud Logging json?
Каков самый быстрый способ расчета ежедневного баланса со сложными процентами в Pandas или Spark?
Эффективный повторный анализ серии строк (в фрейме данных) в структуру, преобразование полей структуры и последующее ее вложение
При использовании matplotlib, как мне установить равные длины осей x и y на экране, не изменяя пределы ни одной оси?
«Модуль {Module_Name} не установлен» при запуске скрипта Python с использованием PyQt5
Как последовательно упорядочить геометрию LineString в возвращаемых ребрах OSMNX GeoDataFrame
Эффективное хранение большого количества больших целых чисел из Python в файл
Реализован ли s.rfind() в Python с использованием итераций в обратном направлении?