Я пытаюсь адаптировать Бойера-Мура с (++) Реализация википедии чтобы получить все совпадения шаблона в строке. На самом деле реализация Wikipedia возвращает первое совпадение. Основной код выглядит так:
char* boyer_moore (uint8_t *string, uint32_t stringlen, uint8_t *pat, uint32_t patlen) {
int i;
int delta1[ALPHABET_LEN];
int *delta2 = malloc(patlen * sizeof(int));
make_delta1(delta1, pat, patlen);
make_delta2(delta2, pat, patlen);
i = patlen-1;
while (i < stringlen) {
int j = patlen-1;
while (j >= 0 && (string[i] == pat[j])) {
--i;
--j;
}
if (j < 0) {
free(delta2);
return (string + i+1);
}
i += max(delta1[string[i]], delta2[j]);
}
free(delta2);
return NULL;
}
Я попытался изменить блок после if (j < 0)
добавить индекс к массиву / вектору и позволить внешнему циклу продолжаться, но он не работает. При тестировании модифицированного кода я все еще получаю только одно совпадение. Возможно, эта реализация не была предназначена для возврата всех совпадений, и для этого нужно несколько быстрых изменений? Я не очень хорошо понимаю сам алгоритм, поэтому я не уверен, как заставить это работать. Если кто-нибудь может указать мне правильное направление, я был бы благодарен.
Примечание. Функции make_delta1 и make_delta2 определены ранее в источнике (см. Страницу Википедии), а вызов функции max () фактически является макросом, также определенным ранее в источнике.
Алгоритм Бойера-Мура использует тот факт, что при поиске, скажем, «HELLO WORLD» в более длинной строке, буква, которую вы находите в данной позиции, ограничивает то, что можно найти вокруг этой позиции, если совпадение вообще может быть найдено, сортировка в игре «Морской бой»: если вы обнаружите открытое море в четырех клетках от границы, вам не нужно тестировать четыре оставшиеся клетки, если там скрывается 5-клеточный носитель; не может быть
Если вы нашли, например, букву «D» в одиннадцатой позиции, это может быть последняя буква HELLO WORLD; но если вы нашли ‘Q’, ‘Q’ нигде в HELLO WORLD, это означает, что искомая строка не может быть где-либо в первых одиннадцати символах, и вы можете вообще избежать ее поиска. Буква «L», с другой стороны, может означать, что HELLO WORLD находится там, начиная с позиции 11-3 (третья буква HELLO WORLD — L), 11-4 или 11-10.
При поиске вы отслеживаете эти возможности, используя два дельта-массива.
Поэтому, когда вы найдете шаблон, вы должны сделать,
if (j < 0)
{
// Found a pattern from position i+1 to i+1+patlen
// Add vector or whatever is needed; check we don't overflow it.
if (index_size+1 >= index_counter)
{
index[index_counter] = 0;
return index_size;
}
index[index_counter++] = i+1;
// Reinitialize j to restart search
j = patlen-1;
// Reinitialize i to start at i+1+patlen
i += patlen +1; // (not completely sure of that +1)
// Do not free delta2
// free(delta2);
// Continue loop without altering i again
continue;
}
i += max(delta1[string[i]], delta2[j]);
}
free(delta2);
index[index_counter] = 0;
return index_counter;
Это должно вернуть список индексов с нулевым символом в конце, если вы передаете что-то вроде size_t *indexes
к функции.
Затем функция вернет 0 (не найдено), index_size (слишком много совпадений) или количество совпадений между 1 и index_size-1.
Это позволяет, например, добавлять дополнительные совпадения без необходимости повторять весь поиск уже найденных (index_size-1) подстрок; вы увеличиваете num_indexes
по new_num, realloc
indexes
массив, а затем передать в функцию новый массив по смещению old_index_size-1
, new_num как новый размер и строка стога сена, начиная со смещения соответствия по индексу old_index_size-1
плюс один (не, как я писал в предыдущей редакции, плюс длина игольной струны; см. комментарий).
Этот подход будет сообщать также совпадения совпадений, например, поиск изречений в банан найдет б *изречений* на и бан *изречений*.
ОБНОВИТЬ
Я проверил выше, и это, кажется, работает. Я изменил код Википедии, добавив эти два включения, чтобы gcc не ворчал
#include <stdio.h>
#include <string.h>
Затем я изменил if (j < 0)
просто вывести то, что он нашел
if (j < 0) {
printf("Found %s at offset %d: %s\n", pat, i+1, string+i+1);
//free(delta2);
// return (string + i+1);
i += patlen + 1;
j = patlen - 1;
continue;
}
и наконец я проверил с этим
int main(void)
{
char *s = "This is a string in which I am going to look for a string I will string along";
char *p = "string";
boyer_moore(s, strlen(s), p, strlen(p));
return 0;
}
и получил, как и ожидалось:
Found string at offset 10: string in which I am going to look for a string I will string along
Found string at offset 51: string I will string along
Found string at offset 65: string along
Если строка содержит две перекрывающиеся последовательности, ОБА найдены:
char *s = "This is an andean andeandean andean trouble";
char *p = "andean";
Found andean at offset 11: andean andeandean andean trouble
Found andean at offset 18: andeandean andean trouble
Found andean at offset 22: andean andean trouble
Found andean at offset 29: andean trouble
Чтобы избежать совпадений совпадений, самый быстрый способ — не хранить совпадения. Это можно сделать в функции, но это будет означать повторную инициализацию первого дельта-вектора и обновление указателя строки; нам также нужно будет хранить вторую i
индекс как i2
чтобы сохранить сохраненные индексы от немонотонности. Это того не стоит. Лучше:
if (j < 0) {
// We have found a patlen match at i+1
// Is it an overlap?
if (index && (indexes[index] + patlen < i+1))
{
// Yes, it is. So we don't store it.// We could store the last of several overlaps
// It's not exactly trivial, though:
// searching 'anana' in 'Bananananana'
// finds FOUR matches, and the fourth is NOT overlapped
// with the first. So in case of overlap, if we want to keep
// the LAST of the bunch, we must save info somewhere else,
// say last_conflicting_overlap, and check twice.
// Then again, the third match (which is the last to overlap
// with the first) would overlap with the fourth.
// So the "return as many non overlapping matches as possible"// is actually accomplished by doing NOTHING in this branch of the IF.
}
else
{
// Not an overlap, so store it.
indexes[++index] = i+1;
if (index == max_indexes) // Too many matches already found?
break; // Stop searching and return found so far
}
// Adapt i and j to keep searching
i += patlen + 1;
j = patlen - 1;
continue;
}
Других решений пока нет …