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

У меня есть большой текстовый файл (от 5 до 10 миллионов строк). Каждая строка содержит от 10 до 5 000 буквенно-цифровых элементов, отделенных друг от друга пробелом или запятой. Каждая строка заканчивается разрывом строки. Количество строк известно до выполнения, и текстовый файл не изменяется во время выполнения.

Каждый раз при запуске кода в него передается 50-200 списков поиска, каждый из которых содержит 2-10 элементов (слов). Для каждого из этих списков поиска я хотел бы найти x количество строк в текстовом файле, который содержит хотя бы один элемент из этого списка . Количество строк x варьируется от 5 до 10 строк и устанавливается во время выполнения. Соответствие регистронезависимо и должно быть точным на границе слова (например, «foo» соответствует «, foo», но не «foobar»).

В каждом списке поиска есть одна из трех стратегий порядка поиска:

  • Нормально. Начните с первой строки, ищите построчно в последовательном порядке, пока не найдет x количество строк или не дойдет до последней строки.

  • Случайно. Выбирайте строки из текстового файла случайным образом. Продолжайте, пока он не найдет x количество строк или не завершит поиск каждой строки.

  • Диапазон ковша. Сначала найдите диапазон строк с наивысшим приоритетом, затем следующий диапазон строк с наивысшим приоритетом, затем следующий и т. Д. Например, приоритет диапазона поиска может заключаться в том, чтобы сначала просмотреть строки от 1000000 до 1499999, затем строки от 0 до 999999, затем строки От 1 500 000 до 2 199 999 и т. Д. Может быть от 3 до 20 сегментов, каждая из которых охватывает диапазон от 100 000 до 5 000 000 строк. Количество сегментов и диапазоны номеров строк указываются во время выполнения. Последовательный поиск в каждом диапазоне. Ищите, пока не найдет x количество строк или не дойдет до конца последнего сегмента.

Вот что я написал для «обычного поиска» [этот тестовый код проверяет все строки до конца файла без остановки после x строк; в окончательной версии после того, как он найдет x совпадений для списка поиска, он остановится и перейдет к следующему списку поиска]:

import re
textFile = "bigTextFile.txt"
searchItems = ["foo","bar","anotherfoo","anotherbar","etc"]
searchStrategy = "normal" #could be "normal", "random", "bucket"; this code is just for "normal"
results = []
with open(textFile) as inFile:
    for line in inFile:
        regex = re.compile('(' + '|'.join('\\b'+item+'\\b' for item in searchItems) +')', re.IGNORECASE)
        match = regex.search(line)  
        if match is not None:
            analysisResult = analyze_the_match(line)
            results.append(analysisResult)

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

[Обновите в ответ на комментарии, чтобы лучше объяснить причину использования разных стратегий поиска]. Элементы сильно перекошены. Играя с данными, я обнаружил, что примерно половина поисков завершится до 10 000 строк, 40% может занять несколько миллионов строк, чтобы найти достаточное количество совпадений, а 10% не найдут достаточного количества совпадений во всем текстовом файле. Элементы в каждой строке не имеют отношения к строке, в которой они находятся, но диапазоны строк похожи (т. Е. Строки 100 000–500 000 связаны, 1 500 000–1750 000 связаны и т. Д.). Код можно запускать много раз для заданного списка поиска, и для каждого запуска приоритет может заключаться в том, чтобы сосредоточиться на другом диапазоне строк. Если весь текстовый файл содержит только x строк с элементами в определенном списке поиска, то результатом всегда будут эти строки x. Но для многих списков поиска есть строки 2x, 10x или 100 000x, которые совпадают, и в разное время я бы хотел выберите разные строки. В определенные моменты приоритетом может быть конкретный диапазон, в других случаях лучше всего использовать случайную выборку, а иногда достаточно просто найти первые строки x с самого начала, следовательно, "нормальный", "случайный" и стратегии поиска "ведра".

Я был бы очень признателен за любые идеи для «случайного» и «ведро», так как я не уверен, как лучше всего подойти к ним эффективно. Я поигрался с linecache, _ 3_, readlines (за @Martin Thoma в этот ответ, readlines на удивление быстро), а также изменение приведенного выше кода, но все мои попытки «случайного» и «ведро» неуклюжи, неэффективны и Я знаю, что не знаю достаточно, чтобы знать, что может быть лучше :).

Какие-либо предложения? Спасибо.


person LunaiThi    schedule 20.04.2016    source источник
comment
Какое отношение имеют данные к строке / положению? Если он полностью случайный (или смещен вперед), почему не всегда начинать с начала файла и читать - в обычных повторяемых строках Python будет буферизовать системные вызовы ввода-вывода, а итеративный считыватель файлов будет только потребителю столько, сколько нужно - до тех пор, пока не будет выполнено условие?   -  person user2864740    schedule 20.04.2016
comment
Я не уверен, что вы ищете, извините. Похоже, у вас уже есть подходящее рабочее решение, отвечающее вашим потребностям. Вам нужна помощь в реализации случайного поиска и поиска по диапазону сегментов (в этом случае кажется, что вы уже знаете о впечатляющем арсенале инструментов для того же и пытаетесь выбрать лучший инструмент) или по оптимизации существующего кода (в этом случае , помните: преждевременная оптимизация - это корень всех зол)?   -  person Akshat Mahajan    schedule 20.04.2016
comment
@ user2864740 Спасибо. Я понимаю, что вы хотите всегда искать от начала до конца, но в этом случае я думаю, что мне нужно использовать стратегии упорядочения различий. Элементы в каждой строке не имеют отношения к строке, в которой они находятся, но диапазоны строк похожи (т.е. строки 100 000–500 000 связаны, 1500 000–1750 000 связаны и т. Д.). Итак, быстрые причины, по которым нужны разные стратегии: а) каждый список имеет разные характеристики и (может) необходимо установить приоритеты для другого диапазона, чем другие списки, б) один и тот же список может запускаться много раз, и ему необходимо установить приоритеты для другого диапазона и (если возможно) вернуть сравнивать результаты каждый раз   -  person LunaiThi    schedule 20.04.2016
comment
@AkshatMahajan Извините, не понятно. Вопрос: как реализовать случайный поиск и поиск по ведру? Обычный поиск работает, но не уверен, что это хорошее решение. Случайный и групповой поиск, я даже не знаю, как подойти к нему эффективно. Набор инструментов, которые я нашел, может быть впечатляющим, но я не смеюсь и не знаю, как создать эффективное решение.   -  person LunaiThi    schedule 20.04.2016
comment
Разве вы не должны индексировать файл в начале, а затем использовать этот индекс слов для эффективного поиска по каждому поисковому запросу?   -  person John Zwinck    schedule 20.04.2016
comment
Есть ли причина, по которой этот плоский файл не загружается в какую-то столбцовую структуру данных, где случайное чтение или поиск были бы намного более эффективными, чем простой цикл по текстовому документу?   -  person OneCricketeer    schedule 20.04.2016
comment
Вы спрашиваете, как реализовать алгоритм, или у вас есть реализация, но она неэффективна, и вам нужна помощь в ее улучшении?   -  person Burhan Khalid    schedule 20.04.2016
comment
На самом деле речь не идет о регулярном выражении, так почему тег? Однако именно так я и оказался здесь, и у меня есть предложение: я полагаю, что с таким объемом данных производительность может быть проблемой, поэтому измените RE, чтобы проверка границ слов была вне группы. Т.е. re.compile('\\b(' + '|'.join(item for item in searchItems) +')\b', re.IGNORECASE). Это улучшит производительность RE (и избавится от некоторой конкатенации строк в цикле;)   -  person SamWhan    schedule 20.04.2016
comment
@John: индексная и столбчатая структура данных? Извините, это означало бы поместить весь текстовый файл в sqite3, создать индекс из строк ‹-› элементов, а затем использовать его для поиска? После постройки он определенно будет более эффективным. Но текстовый файл каждый раз разный, поэтому его нужно создавать при каждом запуске. Разве это не займет больше времени, чем сэкономит? Есть ли какой-нибудь очень эффективный способ сделать это?   -  person LunaiThi    schedule 21.04.2016
comment
@ cricket_007: столбчатые данные, вы имеете в виду базу данных? См. Примечание, которое я сделал выше относительно комментария Джона. Я думаю, что на его индексацию уйдет больше времени, чем на то, чтобы просто просмотреть, но дайте мне знать, что вы думаете, было бы хорошо. Спасибо :)   -  person LunaiThi    schedule 21.04.2016
comment
Учитывая, что вам нужно выполнить поиск в каждом корпусе 50-200 раз, вероятно, будет намного быстрее просканировать корпус один раз, проиндексировать его, а затем выполнить поиск в индексе. Да, вы можете использовать SQLite или сами написать для него код. Любой способ должен быть намного быстрее, чем повторный линейный поиск.   -  person John Zwinck    schedule 21.04.2016
comment
@ClasG Извините, что перетащил вас сюда, но большое спасибо за предложение регулярного выражения. Это большое улучшение!   -  person LunaiThi    schedule 21.04.2016
comment
@JohnZwinck Я пробовал несколько стратегий индексации в sqlite, а также в моем собственном коде. Пока вроде медленнее. Оказывается, около 80% поисковых запросов могут завершаться быстро, 15% занимают несколько миллионов строк и только 5% выполняют полный поиск. В большинстве случаев выгоды от индексации (по крайней мере, как я ее пробовал) было недостаточно, чтобы компенсировать ее стоимость. Но это авантюра, потому что в этих немногих случаях поиск без индексации является долгим, и индексирование было бы лучше. Я не вижу никакого способа узнать заранее, какую стратегию выбрать для того или иного набора поисковых запросов (в конце концов, в этом весь смысл поиска).   -  person LunaiThi    schedule 29.04.2016


Ответы (1)


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

Для случайного выбора вы просто рассчитываете вероятность того, что кандидат находится в списке, и используете случайное число, чтобы определить, попадает ли кандидат в список или нет.

Для выбора сегмента кандидат заменяет существующий член списка, если его ранг сегмента лучше, чем ранг существующего элемента.

Для случайного выбора:

import random
candidates = []
n = 0
with open(textFile) as inFile:
    for line in inFile:
        if valid_candidate(line): # use regex here
            n += 1
            if n <= x:
                candidates.append(line)
            else:
                i = random.randrange(n)
                if i < x:
                    candidates[i] = line
results = []
for line in candidates:
    analysisResult = analyze_the_match(line)
    results.append(analysisResult)

Для выбора ковша:

import bisect
candidates = []
n = 0
with open(textFile) as inFile:
    for line in inFile:
        n += 1
        if valid_candidate(line): # use regex here
            rank = get_rank(n) # use n to determine the bucket and assign a rank, lower numbers are higher rank
            bisect.insort(candidates, (rank, n, line))
results = []
for rank, n, line in candidates[:x]:
    analysisResult = analyze_the_match(line)
    results.append(analysisResult)
person Mark Ransom    schedule 21.04.2016
comment
Пока все хорошо. Я тестирую его по индексам и другим вещам, для запуска всех тестов требуется время. Я вернусь, чтобы отметить ответ. Спасибо. - person LunaiThi; 29.04.2016