Дешевый способ поиска большого текстового файла для строки

Мне нужно найти довольно большой текстовый файл для конкретной строки. Это сборка журнала с около 5000 строк текста. Каков наилучший способ сделать это? Использование регулярных выражений не должно вызывать проблем, не так ли? Я прочитаю блоки строк и воспользуюсь простым поиском.

8 ответов

Решение

Если это "довольно большой" файл, то последовательно обращайтесь к строкам и не читайте весь файл в память:

with open('largeFile', 'r') as inF:
    for line in inF:
        if 'myString' in line:
            # do_something

Вы можете сделать простую находку:

f = open('file.txt', 'r')
lines = f.read()
answer = lines.find('string')

Простая находка будет немного быстрее, чем регулярное выражение, если вам это сойдет с рук.

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

def fnd(fname, s, start=0):
    with open(fname, 'rb') as f:
        fsize = os.path.getsize(fname)
        bsize = 4096
        buffer = None
        if start > 0:
            f.seek(start)
        overlap = len(s) - 1
        while True:
            if (f.tell() >= overlap and f.tell() < fsize):
                f.seek(f.tell() - overlap)
            buffer = f.read(bsize)
            if buffer:
                pos = buffer.find(s)
                if pos >= 0:
                    return f.tell() - (len(buffer) - pos)
            else:
                return -1

Идея этого заключается в следующем:

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

Я использовал что-то вроде этого, чтобы найти сигнатуры файлов внутри больших файлов ISO9660, что было довольно быстро и не занимало много памяти, вы также можете использовать больший буфер для ускорения работы.

Я попытался собрать многопроцессорный пример поиска текстового файла. Это моя первая попытка использования многопроцессорного модуля; и я питон n00b. Комментарии вполне приветствуются. Мне придется подождать, пока на работе, чтобы проверить действительно большие файлы. Это должно быть быстрее в многоядерных системах, чем одноядерный поиск. Bleagh! Как мне остановить процессы после того, как текст найден и надежно сообщить номер строки?

import multiprocessing, os, time
NUMBER_OF_PROCESSES = multiprocessing.cpu_count()

def FindText( host, file_name, text):
    file_size = os.stat(file_name ).st_size 
    m1 = open(file_name, "r")

    #work out file size to divide up to farm out line counting

    chunk = (file_size / NUMBER_OF_PROCESSES ) + 1
    lines = 0
    line_found_at = -1

    seekStart = chunk * (host)
    seekEnd = chunk * (host+1)
    if seekEnd > file_size:
        seekEnd = file_size

    if host > 0:
        m1.seek( seekStart )
        m1.readline()

    line = m1.readline()

    while len(line) > 0:
        lines += 1
        if text in line:
            #found the line
            line_found_at = lines
            break
        if m1.tell() > seekEnd or len(line) == 0:
            break
        line = m1.readline()
    m1.close()
    return host,lines,line_found_at

# Function run by worker processes
def worker(input, output):
    for host,file_name,text in iter(input.get, 'STOP'):
        output.put(FindText( host,file_name,text ))

def main(file_name,text):
    t_start = time.time()
    # Create queues
    task_queue = multiprocessing.Queue()
    done_queue = multiprocessing.Queue()
    #submit file to open and text to find
    print 'Starting', NUMBER_OF_PROCESSES, 'searching workers'
    for h in range( NUMBER_OF_PROCESSES ):
        t = (h,file_name,text)
        task_queue.put(t)

    #Start worker processes
    for _i in range(NUMBER_OF_PROCESSES):
        multiprocessing.Process(target=worker, args=(task_queue, done_queue)).start()

    # Get and print results

    results = {}
    for _i in range(NUMBER_OF_PROCESSES):
        host,lines,line_found = done_queue.get()
        results[host] = (lines,line_found)

    # Tell child processes to stop
    for _i in range(NUMBER_OF_PROCESSES):
        task_queue.put('STOP')
#        print "Stopping Process #%s" % i

    total_lines = 0
    for h in range(NUMBER_OF_PROCESSES):
        if results[h][1] > -1:
            print text, 'Found at', total_lines + results[h][1], 'in', time.time() - t_start, 'seconds'
            break
        total_lines += results[h][0]

if __name__ == "__main__":
    main( file_name = 'testFile.txt', text = 'IPI1520' )

Я удивлен, что никто не упомянул отображение файла в память: mmap

При этом вы можете получить доступ к файлу, как если бы он был уже загружен в память, и ОС позаботится о его отображении и выходе. Кроме того, если вы сделаете это из 2 независимых процессов, и они отобразят файл как "общий", они будут использовать основную память.

После сопоставления он будет вести себя как байтовый массив. Вы можете использовать регулярные выражения, найти или любой из других распространенных методов.

Помните, что этот подход немного специфичен для ОС. Он не будет автоматически переносимым.

Если нет способа определить, где будет находиться строка (первая половина, вторая половина и т. Д.), То на самом деле не существует оптимизированного способа поиска, кроме встроенной функции find. Вы можете уменьшить время ввода-вывода и потребление памяти, не считывая файл целиком за один снимок, а используя блоки по 4 КБ (обычно это размер блока жесткого диска). Это не ускорит поиск, если только строка не находится в первой части файла, но в любом случае уменьшит потребление памяти, что может быть хорошей идеей, если файл огромен.

Мне нравится решение Хавьера. Я не пробовал, но звучит круто!

Чтобы прочитать произвольный большой текст и узнать, существует ли строка, замените ее, вы можете использовать Flashtext, который работает быстрее, чем Regex с очень большими файлами.

Редактировать:

Со страницы разработчика:

>>> from flashtext import KeywordProcessor
>>> keyword_processor = KeywordProcessor()
>>> # keyword_processor.add_keyword(<unclean name>, <standardised name>)
>>> keyword_processor.add_keyword('Big Apple', 'New York')
>>> keyword_processor.add_keyword('Bay Area')
>>> keywords_found = keyword_processor.extract_keywords('I love Big Apple and Bay Area.')
>>> keywords_found
>>> # ['New York', 'Bay Area']

Или при извлечении смещения:

>>> from flashtext import KeywordProcessor
>>> keyword_processor = KeywordProcessor()
>>> keyword_processor.add_keyword('Big Apple', 'New York')
>>> keyword_processor.add_keyword('Bay Area')
>>> keywords_found = keyword_processor.extract_keywords('I love big Apple and Bay Area.', span_info=True)
>>> keywords_found
>>> # [('New York', 7, 16), ('Bay Area', 21, 29)]

Это полностью вдохновлено ответом Лавразии выше, но это уточняет структуру.

Также добавлены некоторые проверки:

  • Он вернется правильно 0 при поиске пустого файла для пустой строки. В ответе Лавразии, это крайний случай, который вернет -1,
  • Он также предварительно проверяет, больше ли целевая строка, чем размер буфера, и выдает ошибку, если это так.

На практике строка цели должна быть намного меньше буфера для эффективности, и существуют более эффективные методы поиска, если размер строки цели очень близок к размеру буфера.

def fnd(fname, goal, start=0, bsize=4096):
    if bsize < len(goal):
        raise ValueError("The buffer size must be larger than the string being searched for.")
    with open(fname, 'rb') as f:
        if start > 0:
            f.seek(start)
        overlap = len(goal) - 1
        while True:
            buffer = f.read(bsize)
            pos = buffer.find(goal)
            if pos >= 0:
                return f.tell() - len(buffer) + pos
            if not buffer:
                return -1
            f.seek(f.tell() - overlap)

5000 строк невелики (ну, зависит от того, насколько длинные строки...)

В любом случае: при условии, что строка будет словом и будет разделена пробелами...

lines=open(file_path,'r').readlines()
str_wanted="whatever_youre_looking_for"


    for i in range(len(lines)):
        l1=lines.split()
        for p in range(len(l1)):
            if l1[p]==str_wanted:
                #found
                # i is the file line, lines[i] is the full line, etc.
Другие вопросы по тегам