У меня есть большие несколько hudred тысяч текстового файла строк. Я должен извлечь 30 000 определенных строк, которые являются всеми в текстовом файле в случайных местах. Это - программа, я должен извлечь одну строку за один раз:
big_file = open('C:\\gbigfile.txt', 'r')
small_file3 = open('C:\\small_file3.txt', 'w')
for line in big_file:
if 'S0414' in line:
small_file3.write(line)
gbigfile.close()
small_file3.close()
Как я могу ускорить это для 30 000 строк, которые я должен искать>?
Ага! Значит, ваша реальная проблема в том, как проверить множество условий в строке и, если одно из них выполнено, вывести эту строку. Проще всего будет использовать регулярное выражение, я думаю:
import re
keywords = ['S0414', 'GT213', 'AT3423', 'PR342'] # etc - you probably get those from some source
pattern = re.compile('|'.join(keywords))
for line in inf:
if pattern.search(ln):
outf.write(line)
Согласно документации Python по файловым объектам, итерация, которую вы делаете, не должна быть особенно медленной, и поиск подстрок также должен быть в порядке с точки зрения скорости.
Я не вижу причин, по которым ваш код должен быть медленным, так что если вам нужно, чтобы он работал быстрее, возможно, вам придется переписать его на C и использовать mmap()
для быстрого доступа к исходному файлу.
Можно попробовать читать большими блоками, избегая накладных расходов на разбивку строк, за исключением отдельных интересующих вас строк. Например, если предположить, что ни одна из ваших строк не длиннее мегабайта:
BLOCKSIZE = 1024 * 1024
def byblock_fullines(f):
tail = ''
while True:
block = f.read(BLOCKSIZE)
if not block: break
linend = block.rindex('\n')
newtail = block[linend + 1:]
block = tail + block[:linend + 1]
tail = newtail
yield block
if tail: yield tail + '\n'
эта программа принимает аргумент открытого файла и выдает блоки размером около 1 МБ, гарантированно заканчивающиеся новой строкой. Чтобы определить (по итератору) все вхождения строки-иголки в строку стога сена:
def haystack_in_needle(haystack, needle):
start = 0
while True:
where = haystack.find(needle, start)
if where == -1: return
yield where
start = where + 1
Чтобы определить все соответствующие строки внутри такого блока:
def wantlines_inblock(s, block):
last_yielded = None
for where in haystack_in_needle(block, s):
prevend = block.rfind('\n', where) # could be -1, that's OK
if prevend == last_yielded: continue # no double-yields
linend = block.find('\n', where)
if linend == -1: linend = len(block)
yield block[prevend + 1: linend]
last_yielded = prevend
Как все это сочетается:
def main():
with open('bigfile.txt') as f:
with open('smallfile.txt', 'w') as g:
for block in byblock_fulllines(f):
for line in wantlines_inblock('S0414', block)
f.write(line)
В 2.7 вы можете сложить оба оператора с
в один, просто чтобы немного уменьшить вложенность.
Примечание: этот код не тестировался, поэтому в нем могут быть (надеюсь, небольшие;-) ошибки, такие как off-by-one. Производительность требует настройки размера блока и должна быть выверена путем измерения на вашей конкретной машине и данных. Ваш пробег может варьироваться. Недействительно, если запрещено законом.
Лучший вариант для ускорения - это если конкретная строка S0414
всегда появляется в одной и той же позиции, так что вместо того, чтобы делать несколько неудачных сравнений в строке (вы сказали, что они начинаются с разных имен), она может просто сделать одно и готово.
например, если в вашем файле есть строки типа
GLY S0414 GCT
ASP S0435 AGG
LEU S0432 CCT
сделайте if line[4:9] == 'S0414': small.write(line)
.
1. Попробуйте читать файл целиком
Одно из ускорений, которое вы можете сделать, это читать весь файл в памяти, если это возможно, в противном случае читайте кусками. Вы сказали "несколько сотен тысяч строк", допустим, 1 миллион строк, каждая строка 100 символов, т.е. около 100 МБ, если у вас есть столько свободной памяти (я предполагаю, что есть), просто сделайте вот так
big_file = open('C:\\gbigfile.txt', 'r')
big_file_lines = big_file.read_lines()
big_file.close()
small_file3 = open('C:\\small_file3.txt', 'w')
for line in big_file_lines:
if 'S0414' in line:
small_file3.write(line)
small_file3.close()
Задайте время для оригинальной версии и посмотрите, есть ли разница, я думаю, будет.
Но если ваш файл действительно большой в ГБ, то вы можете читать его кусками, например, кусками по 100 МБ, разделить его на строки и искать, но не забывайте соединять строки через каждые 100 МБ (я могу рассказать подробнее, если это так)
file.readlines возвращает список, содержащий все строки данных в файле. Если дан необязательный параметр sizehint, он считывает из файла столько байт, сколько нужно для завершения строки, и возвращает строки из этого списка. Это часто используется для эффективного чтения большого файла по строкам, но без необходимости загружать весь файл в память. Возвращаются только полные строки.
Также смотрите следующую ссылку о разнице в скорости между чтением по строкам и всего файла. http://handyfloss.wordpress.com/2008/02/15/python-speed-vs-memory-tradeoff-reading-files/
2. Попробуйте записать файл целиком
Вы также можете сохранять строки и записывать их сразу в конце, хотя я не уверен, что это сильно поможет
big_file = open('C:\\gbigfile.txt', 'r')
big_file_lines = big_file.read_lines()
small_file_lines = []
for line in big_file_lines:
if 'S0414' in line:
small_file_lines.append(line)
small_file3 = open('C:\\small_file3.txt', 'w')
small_file3.write("".join(small_file_lines))
small_file3.close()
3. Попробуйте использовать фильтр
Вы также можете попробовать использовать фильтр вместо цикла, посмотрим, есть ли разница
small_file_lines= filter(lambda line:line.find('S0414') >= 0, big_file_lines)
Если строка начинается с S0414, вы можете использовать метод .startswith
:
if line.startswith('S0414'): small_file3.write(line)
Вы также можете удалить левый пробел, если он есть:
line.lstrip().startswith('S0414')
If 'S0414'
всегда появляется после определенной точки, например, это всегда не менее 10 символов, а не последние 5 символов, вы можете сделать:
'S0414' in line[10:-5]
В противном случае вам придется искать по каждой строке, как и вы .
Тестирование многих условий на строку обычно происходит медленно при использовании наивного алгоритма. Существуют различные превосходные алгоритмы (например, с использованием Tries), которые могут быть намного лучше. Я предлагаю вам дать шанс алгоритму сопоставления строк Ахо-Корасика. См. здесь для реализации python. Это должно быть значительно быстрее, чем наивный подход использования вложенного цикла и тестирования каждой строки по отдельности.
Какие критерии определяют 30000 строк, которые вы хотите извлечь? Чем больше информации вы дадите, тем больше у вас шансов получить полезный ответ.
Если вы хотите, чтобы все строки содержали определенную строку или, в более общем смысле, содержали любой из заданного набора строк или вхождение регулярного выражения, используйте grep
. Вероятно, это будет значительно быстрее для больших наборов данных.
Это напоминает мне проблему, описанную Тимом Бреем , который пытался извлечь данные из файлов журнала веб-сервера с помощью многоядерных машин. Результаты описаны в Проект Wide Finder и Wide Finder 2 . Итак, если последовательная оптимизация идет недостаточно быстро для вас, возможно, вам стоит начать с этого. Есть примеры такого рода проблем во многих языках, включая python . Ключевая цитата из последней ссылки:
Резюме
В этой статье мы взяли относительно быструю реализацию Python и оптимизировали ее, используя количество приемов:
- Предварительно скомпилированные шаблоны RE
- Быстрая фильтрация строк-кандидатов
- Чтение по фрагментам
- Несколько процессов
- Отображение памяти в сочетании с поддержкой операций RE в отображенных буферах
Это уменьшило время, необходимое для синтаксического анализа 200 мегабайт данных журнала, с 6,7 секунды до 0,8 секунд на тестовой машине. Или, другими словами, финальная версия более чем в 8 раз быстрее чем исходная версия Python, и (потенциально) в 600 раз быстрее, чем исходная версия Тима Версия Erlang.
Сказав это, 30 000 строк - это не то много, поэтому вы можете хотя бы начать с исследования производительности чтения / записи вашего диска.Помогает ли, если вы записываете вывод не на диск, с которого вы читаете ввод, или читаете весь файл за один раз перед обработкой?
Этот метод предполагает специальные значения появляются в той же позиции в строке в gbigfile
def mydict(iterable):
d = {}
for k, v in iterable:
if k in d:
d[k].append(v)
else:
d[k] = [v]
return d
with open("C:\\to_find.txt", "r") as t:
tofind = mydict([(x[0], x) for x in t.readlines()])
with open("C:\\gbigfile.txt", "r") as bigfile:
with open("C:\\outfile.txt", "w") as outfile:
for line in bigfile:
seq = line[4:9]
if seq in tofind[seq[0]]:
outfile.write(line)
. В зависимости от того, какое распределение начальной буквы в этих целевых объектах, вы можете значительно сократить количество сравнений. Если вы не знаете, где появятся значения, вы говорите о ДЛИННОЙ операции, потому что вам придется сравнивать сотни тысяч - скажем, 300 000 - 30 000 раз. Это 9 миллионов сравнений, которые займут долгое время.