Как перебрать два генератора одного и того же открытого файла - PullRequest
0 голосов
/ 21 сентября 2018

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

ПЕРВЫЙ ВОПРОС : быстрее ли загружатьвесь файл в память и затем читать строки (метод .read()) или загружать и читать по одной строке за раз (метод .readline())?

Так как я не опытный кодер, я попробовал второйопция с методом islice из модуля 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) печатаются правильные данные, проходящие через оба одновременно (как в этом коде) печатает неверные данные.

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

Ответы [ 5 ]

0 голосов
/ 21 сентября 2018

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

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 голосов
/ 21 сентября 2018

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

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

for atomtype, atomdata, _ in grouper(lines, 3):
    pass
0 голосов
/ 21 сентября 2018

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() на итераторе, он меняет свое состояние, поэтому и возникают ваши проблемы.

0 голосов
/ 21 сентября 2018

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

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 голосов
/ 21 сентября 2018

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

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 строка вместо печати.

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

...