Помогите написать бинарный поиск внутри текстового файла

Рейтинг: 0Ответов: 1Опубликовано: 10.03.2023

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

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

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

По этой же принине если в какой-то момент будет найдена строка с искомым началом, нужно продолжать использовать бинарный поиск что бы найти первую и последнюю такую строку.

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

Вот моя первая попытка:

def find_in_text_file(ID_file, find_str, limit):
    # Открываем файл и переводим искомую строку в нижний регистр
    with open(f'part_{ID_file}.txt', encoding='utf-8') as file:
        find_str = find_str.lower()

        # Инициализируем переменные
        first_pos = 0
        last_pos = file.seek(0, 2)  # устанавливаем последнюю позицию на конец файла
        num_lines = 0
        results = []

        # Бинарный поиск
        while first_pos < last_pos:
            mid_pos = (first_pos + last_pos) // 2
            file.seek(mid_pos)
            file.readline()  # переходим на начало следующей строки

            # Считываем две строки и ищем вторую строку, начинающуюся с искомой подстроки
            line1 = file.readline().lower()
            line2 = file.readline().lower()
            if line2.startswith(find_str):
                # Ищем первую и последнюю позиции, содержащие искомую подстроку
                start_pos = mid_pos + len(line1)
                end_pos = start_pos + len(line2)
                while True:
                    file.seek(start_pos - 1)
                    if file.read(1) == '\n':
                        break
                    start_pos -= 1
                while True:
                    file.seek(end_pos)
                    if file.read(1) == '\n' or end_pos == last_pos:
                        break
                    end_pos += 1

                # Оцениваем количество строк между первой и последней позицией
                num_lines = (end_pos - start_pos) // 86 + 1
                if num_lines <= limit:
                    # Если строк меньше или равно лимиту, добавляем их в список результатов
                    file.seek(start_pos)
                    results = [line.strip() for line in file.readlines(num_lines)]
                else:
                    # Если строк больше лимита, возвращаем ошибку
                    return ["Error. Many result", num_lines]
                
                # Ищем последнюю позицию, содержащую искомую подстроку
                last_found_pos = end_pos
                while last_found_pos < last_pos:
                    file.seek(last_found_pos)
                    line = file.readline().lower()
                    if line.startswith(find_str):
                        last_found_pos += len(line)
                    else:
                        break
                
                # Устанавливаем начальную позицию для следующего поиска
                first_pos = end_pos
                
            elif line2 > find_str:
                last_pos = mid_pos
            else:
                first_pos = mid_pos + len(line1)
        
        if results:
            return results
        else:
            return ["Not found"]

И вот вторая попытка:

def find_in_big_file(name_file, find_str, limit):
    find_str = find_str.lower()
    sr_len_string = 86  # средняя длина строки
    with open(f"{name_file}", "r", encoding="utf-8", errors='ignore') as f:
        f.seek(0, 2)
        size = f.tell()
        f.seek(0)
        low, high = 0, size
        while low < high:
            mid = (low + high) // 2
            f.seek(mid)
            f.readline()
            line = f.readline().lower()
            if line.startswith(find_str):
                start_pos = mid
                break
            elif line < find_str:
                low = mid + 1
            else:
                high = mid
        else:
            return []
        f.seek(0)
        low, high = 0, size
        end_pos=-1
        while low < high-500:
            mid = (low + high) // 2
            f.seek(mid)
            f.readline()
            line = f.readline().lower()
            #print(low,high,line)
            #print()
            if line.startswith(find_str):
                end_pos = mid
                low = mid + 1
            elif line < find_str:
                low = mid + 1
            else:
                high = mid
        f.seek(max(start_pos-5000,0))
        result = []
        count = 0
        if end_pos == -1:
            end_pos = high

        count_lines = round((end_pos-start_pos)//sr_len_string)
        if count_lines > limit:
            return(["Error. Many result", count_lines])
        data = f.read((end_pos-start_pos) + 10000).split('\n')[1:-1]
        #print(data)
        for d in data:
            if d.lower().startswith(find_str):
                result.append(d)
                count += 1
                if count > limit:
                    return ["Error. Many result", count]
    return result

Ответы

▲ 0

Дополнение. Всё возможно даже без индексации и со строками разной длины. С третьей попытки написал рабочий код. Ищет за 1/1000 секунды. Может принимать сразу массив строк для поиска что бы уменьшить количество открытий/закрытий.

def find_in_big_file(name_file,find_strings,limit): #ищет в упорядоченом файле строки, начинающиеся на Str, возвращает их в виде массива. limit-максимальное число вернувшихся строк.
global directory
#print(name_file,find_str,limit)
def iter_find(MinGran,MaxGran,CenterMin,CenterMax): #одна итерация изменения границ
    if CenterMin==None:
        point=(MinGran+MaxGran)//2
        file.seek(point)
        file.readline()
        S1=file.readline()[0:len(find_str)].lower()
        #print(S1)
        if S1<find_str:
            MinGran=point
            return([MinGran,MaxGran,CenterMin,CenterMax])
        elif S1>find_str:
            MaxGran=point
            return([MinGran,MaxGran,CenterMin,CenterMax])
        elif S1==find_str:
            CenterMin=point
            CenterMax=point
            return([MinGran,MaxGran,CenterMin,CenterMax])
    else: #Если уже найдена точка, в которой есть искомая строка, разбиваем один поиск на два - слева и справа
        point1=(MinGran+CenterMin)//2
        file.seek(point1)
        file.readline()
        S1=file.readline()[0:len(find_str)].lower()
        #print(S1)
        if S1<find_str:
            MinGran=point1
            return([MinGran,MaxGran,CenterMin,CenterMax])
        elif S1==find_str:
            CenterMin=point1
            return([MinGran,MaxGran,CenterMin,CenterMax])
        elif S1>find_str:
            print('Ошибка!!! Строки не упорядочены')
            return([MinGran,MaxGran,CenterMin,CenterMax])

        point2=(MaxGran+CenterMax)//2
        file.seek(point2)
        file.readline()
        S1=file.readline()[0:len(find_str)].lower()
        if S1>find_str:
            MaxGran=point2
            return([MinGran,MaxGran,CenterMin,CenterMax])
        elif S1==find_str:
            CenterMax=point2
            return([MinGran,MaxGran,CenterMin,CenterMax])
        elif S1<find_str:
            print('Ошибка!!! Строки не упорядочены')
            return([MinGran,MaxGran,CenterMin,CenterMax])
        
    return([MinGran,MaxGran,CenterMin,CenterMax])


file=open(name_file,'r',errors='ignore') #,'\n'.join(a[Min:])

if type(find_strings)==str: #это может быть массив запросов
    find_str=[find_strings.lower()]
else:
    find_str=[elem.lower() for elem in find_strings]

rez=[]
for find_str in find_strings:
    MinGran=0
    MaxGran=os.fstat(file.fileno()).st_size
    CenterMin=None #в этой переменной хранится точка, в которой уже найдена строка с искомым значением
    CenterMax=None

    while True:
        if (CenterMin!=None) and (CenterMin-MinGran>1500):
            break
        if MaxGran-MinGran<3000:
            break
        MinGran,MaxGran,CenterMin,CenterMax=iter_find(MinGran,MaxGran,CenterMin,CenterMax)
        #print(MinGran,MaxGran,CenterMin,CenterMax)
    file.seek(max(0,MinGran-100))
    data=file.read(MaxGran-MinGran+200)
    data2=file.readline()
    data=(data+data2).split('\n')
    #print('\n'.join(data))
    #print('\n')
    
    result=[]
    for d in data:
        if d[0:len(find_str)].lower()==find_str:
            result.append(d)
    rez.append(result)
return(rez)