Как перебрать два генератора одного и того же открытого файла

1

У меня есть файл среднего размера (25 МБ, 1000000 строк), и я хочу читать каждую строку, кроме каждой третьей строки.

FIRST QUESTION: быстрее ли загружать весь файл в память, а затем читать строки (метод .read()) или загружать и читать одну строку в то время (метод .readline())?

Поскольку я не опытный кодер, я попробовал второй вариант с islice метода itertools из модуля itertools.

import intertools

with open(input_file) as inp:
    inp_atomtype = itertools.islice(inp, 0, 40, 3)
    inp_atomdata = itertools.islice(inp, 1, 40, 3)
    for atomtype, atomdata in itertools.zip_longest(inp_atomtype, inp_atomdata):
        print(atomtype + atomdata)

Несмотря на то, что цикл через один генератор (inp_atomtype или inp_atomdata) печатает правильные данные, циклическое inp_atomdata обоих из них одновременно (как в этом коде) выводит неверные данные.

ВТОРОЙ ВОПРОС: Как я могу достичь желаемых строк с помощью генераторов?

  • 0
    Звучит для меня как проблема xy, что вы пытаетесь решить?
  • 0
    В соответствии со значением atomtype я хочу правильно обрабатывать переменную atomdata .
Теги:
generator
itertools

5 ответов

0
Лучший ответ

yield идеально подходит для этого.

Эти функции дают пары из итерируемого и пропускают каждый третий элемент:

def two_thirds(seq):
    _iter = iter(seq)
    while True:
        yield (next(_iter), next(_iter))
        next(_iter)

Вы потеряете половину пар, что означает, что two_thirds(range(2)) немедленно прекратят итерацию.

https://repl.it/repls/DullNecessaryCron

Вы также можете использовать рецепт grouper из документа itertools и игнорировать третий элемент в каждом сгенерированном кортеже:

for atomtype, atomdata, _ in grouper(lines, 3):
    pass
  • 0
    Это простое решение, и я могу достичь двух переменных одновременно.
1

Вам не нужно обрезать итератор, достаточно простого счетчика строк:

with open(input_file) as f:
    current_line = 0
    for line in f:
        current_line += 1
        if current_line % 3:  # ignore every third line
            print(line)  # NOTE: print() will add an additional new line by default

Что касается превращения его в генератор, просто yield строку вместо печати.

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

0

ПЕРВЫЙ ВОПРОС: Я уверен, что.readline() быстрее, чем.read(). Плюс, самый быстрый способ, основанный на моем тестировании, - это сделать lopping как:

with open(file, 'r') as f:
    for line in f:
        ...

ВТОРОЙ ВОПРОС: Я не совсем уверен в этом. вы можете использовать урожай.

Вы можете найти фрагмент кода:

def myreadlines(f, newline):
    buf = ""
    while True:
        while newline in buf:
            pos = buf.index(newline)
            yield buf[:pos]
            buf = buf[pos + len(newline):]
        chunk = f.read(4096)

        if not chunk:
        # the end of file
            yield buf
            break
        buf += chunk

with open("input.txt") as f:
    for line in myreadlines(f, "{|}"):
        print (line)
0

Вы можете использовать выражение генератора:

with open(input_file, 'r') as f:
    generator = (line for e, line in enumerate(f, start=1) if e % 3)

enumerate добавляет номера строк в каждую строку, а предложение if игнорирует номера строк, делящиеся на 3 (нумерация по умолчанию начинается с 0, поэтому вам нужно указать start=1 чтобы получить желаемый шаблон).

Имейте в виду, что вы можете использовать генератор только в том случае, если файл все еще открыт.

0

q2: здесь мой генератор:

def yield_from_file(input_file):
    with open(input_file) as file:
        yield from file

def read_two_skip_one(gen):
    while True:
        try:
            val1 = next(gen)
            val2 = next(gen)
            yield val1, val2
            _ = next(gen)
        except StopIteration:
            break

if __name__ == '__main__':
    for atomtype, atomdata in read_two_skip_one(yield_from_file('sample.txt')):
        print(atomtype + atomdata)

sample.txt был сгенерирован с помощью оболочки bash (она просто подсчитывает 100)

for i in {001..100}; do echo $i; done > sample.txt

относительно q1: если вы читаете файл несколько раз, вам будет лучше иметь его в памяти. в противном случае вы прекрасно читаете его по строкам.

Что касается проблемы, которую вы испытываете с неправильными результатами:

itertools.islice(inp, 0, 40, 3) будут использовать inp как генератор. Оба вызовут next(inp), чтобы предоставить вам значение. Каждый раз, когда вы вызываете next() на итераторе, он меняет свое состояние, так что там, где возникают ваши проблемы.

  • 1
    Вы можете позвонить next(gen) не назначая его _
  • 0
    Я знаю, это привычка: D Некоторые линтеры жалуются, что не используют значение.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню