Алгоритм для линейного сопоставления с образцом? - PullRequest
4 голосов
/ 09 августа 2009

У меня есть линейный список нулей и единиц, и мне нужно сопоставить несколько простых шаблонов и найти первое вхождение. Например, мне может понадобиться найти 0001101101, 01010100100, ИЛИ 10100100010 в списке длиной 8 миллионов. Мне нужно только найти первое вхождение любого из них, а затем вернуть индекс, по которому оно происходит. Однако выполнение циклов и доступа к большому списку может быть дорогостоящим, и я бы не стал делать это слишком много раз.

Есть ли более быстрый метод, чем

foreach (patterns) {
    for (i=0; i < listLength; i++)
        for(t=0; t < patternlength; t++)
            if( list[i+t] != pattern[t] ) {
                 break;
            }
            if( t == patternlength - 1 ) {
                 return i;  // pattern found!
            }
        }
    }
}

Редактировать: Кстати, я реализовал эту программу в соответствии с приведенным выше псевдокодом, и производительность в порядке, но ничего впечатляющего. По моим оценкам, я обрабатываю около 6 миллионов бит в секунду на одном ядре моего процессора. Я использую это для обработки изображений, и мне придется пройти через несколько тысяч 8-мегапиксельных изображений, так что каждый кусочек помогает.

Редактировать: Если неясно, я работаю с битовым массивом, поэтому есть только две возможности: ОДИН и НУЛЬ. И это в C ++.

Редактировать: Спасибо за указатели на алгоритмы BM и KMP. Я отметил, что на странице Википедии для BM написано

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

Это выглядит интересно, но не приводило примеров таких алгоритмов. Поможет ли что-нибудь подобное?

Ответы [ 7 ]

11 голосов
/ 09 августа 2009

Ключ к поиску в Google - это сопоставление строк с несколькими шаблонами.

В 1975 году Ахо и Корасик опубликовали (линейное время) алгоритм, который использовался в оригинальной версии fgrep. Впоследствии алгоритм был усовершенствован многими исследователями. Например, Commentz-Walter (1979) объединяет Aho & Corasick с Boyer & Moore . Baeza-Yates (1989) комбинированный AC с вариантом Бойера-Мура-Хорспула . У и Мэнбер (1994) проделал аналогичную работу.

Альтернативой линии переменного тока алгоритмов сопоставления с несколькими шаблонами является алгоритм Рабина и Карпа .

Я предлагаю начать с чтения страниц Википедии Aho-Corasick и Rabin-Karp, а затем решить, будет ли это иметь смысл в вашем случае. Если так, возможно, уже есть реализация для вашего языка / среды выполнения.

4 голосов
/ 09 августа 2009
2 голосов
/ 09 августа 2009

Вы можете создать SuffixArray и искать время выполнения безумно: O (длина (шаблон)). НО .. вы должны построить этот массив. Это стоит только ... когда текст статичен, а шаблон динамичен.

1 голос
/ 09 августа 2009

Решение, которое может быть эффективным:

  1. сохранить шаблоны в структуре структуры данных
  2. начать поиск в списке
  3. проверить, находятся ли следующие pattern_length символы в три, остановка при успехе (операция O (1))
  4. шаг первый символ и повторите # 3

Если список не является изменяемым, вы можете сохранить смещение совпадающих шаблонов, чтобы избежать повторения вычислений в следующий раз.

0 голосов
/ 09 августа 2009

Если это битовый массив, я думаю, что внесение скользящей суммы было бы улучшением. Если шаблон имеет длину n, суммируйте первые n биты и посмотрите, соответствует ли он сумме шаблона. Храните первый бит суммы всегда. Затем для каждого следующего бита вычтите первый бит из суммы и добавьте следующий бит, и посмотрите, соответствует ли сумма сумме шаблона. Это позволило бы сохранить линейный цикл над шаблоном.

Кажется, что алгоритм BM не так хорош для этого, как кажется, потому что здесь у меня есть только два возможных значения, ноль и одно, поэтому первая таблица не очень помогает. Вторая таблица может помочь, но это означает, что BMH в основном ничего не стоит.

Редактировать: В моем лишенном сна состоянии я не мог понять BM, поэтому я просто реализовал эту скользящую сумму (это было действительно легко), и это сделало мой поиск в 3 раза быстрее. Спасибо всем, кто упомянул "катящиеся хеши". Теперь я могу искать 321 750 000 битов для двух 30-битных шаблонов за 5,45 секунды (и это однопоточное), по сравнению с 17,3 секундами ранее.

0 голосов
/ 09 августа 2009

Если это просто чередование 0 и 1, то закодируйте текст как прогоны. Серия n 0 равна -n, а последовательность n 1 равна n. Затем закодируйте ваши строки поиска. Затем создайте функцию поиска, которая использует закодированные строки.

Код выглядит так:

try:
    import psyco
    psyco.full()
except ImportError:
    pass

def encode(s):
    def calc_count(count, c):
        return count * (-1 if c == '0' else 1)
    result = []
    c = s[0]
    count = 1
    for i in range(1, len(s)):
        d = s[i]
        if d == c:
            count += 1
        else:
            result.append(calc_count(count, c))
            count = 1
            c = d
    result.append(calc_count(count, c))
    return result

def search(encoded_source, targets):
    def match(encoded_source, t, max_search_len, len_source):
        x = len(t)-1
        # Get the indexes of the longest segments and search them first
        most_restrictive = [bb[0] for bb in sorted(((i, abs(t[i])) for i in range(1,x)), key=lambda x: x[1], reverse=True)]
        # Align the signs of the source and target
        index = (0 if encoded_source[0] * t[0] > 0 else 1)
        unencoded_pos = sum(abs(c) for c in encoded_source[:index])
        start_t, end_t = abs(t[0]), abs(t[x])
        for i in range(index, len(encoded_source)-x, 2):
            if all(t[j] == encoded_source[j+i] for j in most_restrictive):
                encoded_start, encoded_end = abs(encoded_source[i]), abs(encoded_source[i+x])
                if start_t <= encoded_start and end_t <= encoded_end:
                    return unencoded_pos + (abs(encoded_source[i]) - start_t)
            unencoded_pos += abs(encoded_source[i]) + abs(encoded_source[i+1])
            if unencoded_pos > max_search_len:
                return len_source
        return len_source
    len_source = sum(abs(c) for c in encoded_source)
    i, found, target_index = len_source, None, -1
    for j, t in enumerate(targets):
        x = match(encoded_source, t, i, len_source)
        print "Match at: ", x
        if x < i:
            i, found, target_index = x, t, j
    return (i, found, target_index)


if __name__ == "__main__":
    import datetime
    def make_source_text(len):
        from random import randint
        item_len = 8
        item_count = 2**item_len
        table = ["".join("1" if (j & (1 << i)) else "0" for i in reversed(range(item_len))) for j in range(item_count)]
        return "".join(table[randint(0,item_count-1)] for _ in range(len//item_len))
    targets = ['0001101101'*2, '01010100100'*2, '10100100010'*2]
    encoded_targets = [encode(t) for t in targets]
    data_len = 10*1000*1000
    s = datetime.datetime.now()
    source_text = make_source_text(data_len)
    e = datetime.datetime.now()
    print "Make source text(length %d): " % data_len,  (e - s)
    s = datetime.datetime.now()
    encoded_source = encode(source_text)
    e = datetime.datetime.now()
    print "Encode source text: ", (e - s)

    s = datetime.datetime.now()
    (i, found, target_index) = search(encoded_source, encoded_targets)
    print (i, found, target_index)
    print "Target was: ", targets[target_index]
    print "Source matched here: ", source_text[i:i+len(targets[target_index])]
    e = datetime.datetime.now()
    print "Search time: ", (e - s)

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

psyco - это модуль на python для оптимизации кода во время выполнения. Используя его, вы получаете отличную производительность, и вы можете оценить ее как верхнюю границу производительности C / C ++. Вот недавнее выступление:

Make source text(length 10000000):  0:00:02.277000
Encode source text:  0:00:00.329000
Match at:  2517905
Match at:  494990
Match at:  450986
(450986, [1, -1, 1, -2, 1, -3, 1, -1, 1, -1, 1, -2, 1, -3, 1, -1], 2)
Target was:  1010010001010100100010
Source matched here:  1010010001010100100010
Search time:  0:00:04.325000

Требуется около 300 миллисекунд для кодирования 10 миллионов символов и около 4 секунд для поиска трех закодированных строк. Я не думаю, что время кодирования было бы высоким в C / C ++.

0 голосов
/ 09 августа 2009

Если ваши строки должны быть гибкими, я бы также порекомендовал модифицированный «алгоритм поиска строк Бойера – Мура» в соответствии с Митчем Уитом. Если ваши строки не должны быть гибкими, вы сможете свернуть соответствие шаблона еще больше. Модель Бойера-Мура невероятно эффективна для поиска большого количества данных по одной из нескольких строк для сравнения.

Jacob

...