Основной причиной этого является то, что вы читаете весь файл (или, точнее, его копию) для каждой строки в файле. Итак, если есть 10000 строк, вы читаете 10000 строк 10000 раз, что означает 10000000 строк!
Если у вас достаточно памяти, чтобы сохранить строки, прочитанные до сих пор, есть очень простое решение: Храните линии, видимые до сих пор в наборе. (Или, скорее, для каждой строки сохраните кортеж из трех ключей, которые считаются дублирующими.) Для каждой строки, если она уже находится в наборе, пропустите ее; в противном случае обработайте его и добавьте в набор.
Например:
seen = set()
for line in infile:
testname, vid, tstamp = line.split(",", 3)[:3]
if (testname, vid, tstamp) in seen:
continue
seen.add((testname, vid, tstamp))
outfile.write(line)
itertools
recipes в документации есть функция unique_everseen
, что позволяет обернуть это еще более красиво:
def keyfunc(line):
return tuple(line.split(",", 3)[:3])
for line in unique_everseen(infile, key=keyfunc):
outfile.write(line)
Если набор занимает слишком много памяти , вы всегда можете подделать набор поверх dict, и вы можете подделать dict поверх базы данных, используя модуль dbm
, который будет очень хорошо работать. o сохраняя достаточно в памяти, чтобы сделать все быстро, но недостаточно, чтобы вызвать проблему. Единственная проблема заключается в том, что ключи dbm должны быть строками, а не кортежами из трех строк ... но вы всегда можете просто объединить их (или вернуть join
) вместо разделения, а затем у вас есть строка.
Я предполагаю, что, когда вы говорите, что файл «сортируется», вы имеете в виду с точки зрения временной метки, а не с точки зрения ключевых столбцов. То есть, нет гарантии, что две строки, которые являются дубликатами, будут рядом друг с другом. Если бы это было, это еще проще. Он может не выглядеть проще, если вы используете рецепты itertools; вы просто заменить everseen
с justseen
:
def keyfunc(line):
return tuple(line.split(",", 3)[:3])
for line in unique_justseen(infile, key=keyfunc):
outfile.write(line)
Но под одеялом, это только отслеживанием последней строки, а не набор всех линий. Это не только быстрее, но и экономит массу памяти.
Теперь, когда (я думаю) я понимаю ваши требования лучше, что вы на самом деле хотите, чтобы избавиться от не все, кроме первой строки с тем же testname
, vid
и tstamp
, но и все строки с такие же testname
и vid
кроме одного с наивысшим tstamp
. И поскольку файл сортируется в порядке возрастания tstamp
, это означает, что вы можете полностью игнорировать tstamp
; вам просто нужен последний матч для каждого.
Это значит, что трюк everseen
не будет работать - мы не можем пропустить первый, потому что мы еще не знаем, что есть более поздний.
Если мы просто повторили файл назад, это решит проблему. Это также удвоит использование вашей памяти (потому что в дополнение к набору вы также сохраняете список, чтобы вы могли складывать все эти строки в обратном порядке). Но если это приемлемо, это легко:
def keyfunc(line):
return tuple(line.split(",", 2)[:2])
for line in reversed(list(unique_everseen(reversed(list(infile)), key=keyfunc))):
outfile.write(line)
Поворачивая эти ленивые итераторы в списки, чтобы мы могли реверс их занимает слишком много памяти, это, вероятно, самый быстрый, чтобы сделать несколько проходов: обратный файл на диске, затем процеживают обращенного файл, затем снова отмените его. Это означает две дополнительные записи файлов, но это может быть намного лучше, чем, скажем, замена виртуальной памяти вашей ОС на и с диска сотни раз (или ваша программа просто сбой с MemoryError
).
Если вы готовы сделать работу, это не было бы , что трудно написать обратный файл итератор, который считывает буферы с конца и расколы на переводы строк и дает таким же образом, объект file
/io.Whatever
делает. Но я бы не стал беспокоиться, если вам не понадобится.
Если вы когда-нибудь делать необходимости неоднократно прочитанными отдельные номера строк из файла, linecache
модуля обычно ускорить много. Конечно, не так быстро, как не перечитывать, но намного лучше, чем чтение и анализ тысяч строк.
Вы также теряете время, повторяя некоторую работу во внутреннем цикле. Например, вы вызываете line2.split(",")
три раза, вместо того, чтобы просто разбивать его один раз и фиксировать значение в переменной, что будет в три раза быстрее. 3-кратное постоянное усиление нигде не столь важно, как квадратичное линейное усиление, но когда оно приходит бесплатно, сделав ваш код более простым и понятным, он также может его принять.
В вашем вопросе постоянно говорится о «входном файле», но ваш код явно обрабатывает файлы ввода _two_. Что в каждом файле? – abarnert
Это копии. Я собираю токены из первого файла, опуская второй файл. – Illusionist
Для этого вам не нужна фактическая копия файла; вы можете «открыть» тот же файл дважды в режиме чтения и получить два разных файла. – abarnert