Быстрое внедрение Rolling Hash - PullRequest
16 голосов
/ 03 апреля 2009

Мне нужен Rolling Hash для поиска шаблонов в файле. (Я пытаюсь использовать алгоритм поиска строк Рабина-Карпа ).

Я понимаю, как работает хороший хэш и как должен работать хороший Rolling Hash , но я не могу понять, как эффективно реализовать деление ( или обратное умножение) при переходе хэша. Я также читал, что rsync использует скользящую версию adler32 , но это не выглядит как случайный хэш.

В идеале было бы здорово, если бы вы указали мне на оптимизированную реализацию C / C ++, но любые указатели в правильном направлении помогут.

Ответы [ 3 ]

15 голосов
/ 03 апреля 2009

Идея «первичной базы» Шипера должна работать достойно, хотя опубликованное им решение выглядит несколько схематично.

Я не думаю, что в этом методе есть необходимость в обратном умножении. Вот мое решение:

Скажем, строка, которую мы в настоящий момент хэшировали, это "abc", и мы хотим добавить "d" и удалить "a".

Так же, как и Cipher, мой основной алгоритм хеширования будет:

unsigned hash(const string& s)
{
    unsigned ret = 0;
    for (int i = 0; i < s.size(); i++)
    {
        ret *= PRIME_BASE; //shift over by one
        ret += s[i]; //add the current char
        ret %= PRIME_MOD; //don't overflow
    }
    return ret;
}

Теперь, чтобы реализовать скольжение:

hash1 = [0]*base^(n-1) + [1]*base^(n-2) + ... + [n-1]

Мы хотели бы добавить что-то в конце и удалить первое значение, поэтому

hash2 = [1]*base^(n-1) + [2]*base^(n-2) + ... + [n]

Сначала мы можем добавить последнюю букву:

hash2 = (hash1 * PRIME_BASE) + newchar;
=> [0]*base^n + [1]*base^(n-1) + ... + [n-1]*base + [n]

Затем просто вычтите первый символ:

hash2 -= firstchar * pow(base, n);
=> [1]*base^(n-1) + ... + [n]

Важное замечание: вы должны быть осторожны с переполнением. Вы можете просто позволить ему переполнять unsigned int, но я думаю, что он гораздо более подвержен столкновениям (но также и быстрее!)

Вот моя реализация:

#include <iostream>
#include <string>
using namespace std;

const unsigned PRIME_BASE = 257;
const unsigned PRIME_MOD = 1000000007;

unsigned hash(const string& s)
{
    long long ret = 0;
    for (int i = 0; i < s.size(); i++)
    {
        ret = ret*PRIME_BASE + s[i];
        ret %= PRIME_MOD; //don't overflow
    }
    return ret;
}

int rabin_karp(const string& needle, const string& haystack)
{
    //I'm using long longs to avoid overflow
    long long hash1 = hash(needle);
    long long hash2 = 0;

    //you could use exponentiation by squaring for extra speed
    long long power = 1;
    for (int i = 0; i < needle.size(); i++)
        power = (power * PRIME_BASE) % PRIME_MOD;

    for (int i = 0; i < haystack.size(); i++)
    {
        //add the last letter
        hash2 = hash2*PRIME_BASE + haystack[i];
        hash2 %= PRIME_MOD;

        //remove the first character, if needed
        if (i >= needle.size())
        {
            hash2 -= power * haystack[i-needle.size()] % PRIME_MOD;
            if (hash2 < 0) //negative can be made positive with mod
                hash2 += PRIME_MOD;
        }

        //match?
        if (i >= needle.size()-1 && hash1 == hash2)
            return i - (needle.size()-1);
    }

    return -1;
}

int main()
{
    cout << rabin_karp("waldo", "willy werther warhol wendy --> waldo <--") << endl;
}
4 голосов
/ 03 апреля 2009

Несколько указателей для быстрой реализации:

  1. Избегайте операций по модулю n (% в языках, подобных C), используйте маску n - 1, где n равно 2 ^ k, включая операции для поиска в хеш-таблице. Да, можно создать хороший хеш с непростыми модулями.
  2. Выберите множители и показатели с хорошими показателями качества, подробности см. в этом документе .
0 голосов
/ 03 апреля 2009

Я написал это некоторое время назад. Он написан на c #, но это очень близко к c, вам нужно будет добавить только пару параметров. Это должно работать, но я не тестировал эту версию, я удалил пару строк, которые игнорировали бы регистр или несимвольные символы. Я надеюсь, что это помогает

private const int primeBase = 101;
//primeBase^2*[0]+primeBase^1*[1]+primeBase^0*[2]
//==
//primeBase*(primeBase*[0]+[1])+[2]
public static int primeRollingHash(String input, int start, int end)
{
    int acc = 0;
    for (int i = start; i <= end; i++)
    {
        char c = input[i];
        acc *= primeBase;
        acc += c;
    }
    return acc;
}

public static int primeRollingHash(String input)
{
    return primeRollingHash(input, 0, input.Length - 1);
}

public static int rollHashRight(int currentHashValue, String input, 
                                int start, int newEnd)
{
    if (newEnd == input.Length)
        return currentHashValue;
    int length = newEnd - start - 1;
    int multiplier = primeBase;
    char newChar = input[newEnd];
    int firstValue = input[start];
    if(length>0)
        firstValue *= length * primeBase;
    return (currentHashValue - firstValue) * multiplier + newChar;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...