C++: предложения по хэш-функции для последовательности строк, где порядок строк не имеет значения.

Допустим, у вас есть эти две последовательности строк

abc cba bc

bc abc cba

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

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

Однако я очень новичок в мире функций хэширования строк, и я понятия не имею, будет ли этот подход эффективным.

На этом веб-сайте http://www.partow.net/programming/hashfunctions/index.html< /а>

Я нашел много различных реализаций хеширования строк, однако я не уверен, какая из них лучше всего подходит для моих нужд.

Некоторые технические подробности о каждой строке в последовательности заключаются в том, что каждая из них не может содержать более 25 символов. Также каждая последовательность не будет иметь более 3 строк.

Вопросы

1. Будет ли работать такой подход с добавлением результатов функции хеширования строк к каждой строке последовательности?

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

заранее спасибо


person ksm001    schedule 01.04.2013    source источник
comment
Было бы полезно применить функцию хеширования к отсортированной копии последовательности строк?   -  person Roger Rowland    schedule 01.04.2013
comment
каков размер алфавита (т.е. какой набор символов будет использоваться)?   -  person didierc    schedule 01.04.2013
comment
Вы хотите, чтобы они были в одном ведре, но НЕ сталкивались? Трудная задача.   -  person WhozCraig    schedule 01.04.2013
comment
если вы сортируете последовательность, вам даже не нужно хешировать, просто сравните строки с одинаковым рангом.   -  person didierc    schedule 01.04.2013
comment
roger_rowland, я думал об этом, однако сортировка последовательности будет O (klogk), где k — количество строк в последовательности, и даже если я позже использую хеширование, у меня будет как минимум O (n) для хэша быть сгенерированным. Я хотел бы избежать дополнительных затрат O (klogk), если это возможно. Didierc, алфавит будет английский (включая заглавные буквы)   -  person ksm001    schedule 01.04.2013
comment
Сортировка последовательности из трех строк едва ли является излишней. Тот факт, что их не более трех, и только три, является основным преимуществом включения 3-элементной сортировки в вашу хеш-функцию. Развернутый набор if-else будет работать.   -  person WhozCraig    schedule 01.04.2013
comment
WhozCraig, вы правы, но я не уверен, что произойдет, если у меня будет много последовательностей с тремя строками по 25 символов, в которых отличается только последняя буква. Фаза сортировки заняла бы много времени, чтобы увидеть, какая строка должна быть первой в окончательной последовательности, а какая — второй. Будут некоторые общие дополнительные расходы, если у меня будет много последовательностей строк, которых я хотел бы избежать, если это возможно.   -  person ksm001    schedule 01.04.2013
comment
для дополнения я предлагаю использовать XOR.   -  person Karoly Horvath    schedule 01.04.2013


Ответы (3)


Просто демонстрация идеи (очень неэффективное копирование строк), сложность O (NlogN), где N - размер ключа (=== O (1), если ваши ключи имеют постоянную длину, известную во время компиляции), я не думаю, что вы может сделать лучшую сложность:

#include <boost/functional/hash.hpp>
#include <set>
#include <algorithm>

std::size_t make_hash(
  std::string const& a,
  std::string const& b,
  std::string const& c)
{
    std::string input[] = {a,b,c};
    std::sort(input, input + (sizeof(input)/sizeof(*input)));
    return boost::hash_range(input, input + (sizeof(input)/sizeof(*input)));
}

#include <iostream>
// g++ -I.../boost_1_47_0 string_set_hash.cpp
int main()
{
    std::cout << make_hash("abc", "bcd", "def") << std::endl; // 46247451276990640
    std::cout << make_hash("bcd", "def", "abc") << std::endl; // 46247451276990640
}

Фрагмент boost/functional/hash.hpp для справки:

template <class T>
inline void hash_combine(std::size_t& seed, T const& v)

{
    boost::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}

template <class It>
inline std::size_t hash_range(It first, It last)
{
    std::size_t seed = 0;

    for(; first != last; ++first)
    {
        hash_combine(seed, *first);
    }

    return seed;
}
person bobah    schedule 01.04.2013
comment
спасибо за ваше предложение, не будет ли реализация вашей собственной хеш-функции так, как я описал, избежать дополнительных затрат на сортировку? Поскольку нахождение хэша строки будет как минимум O (N), однако с учетом того факта, что я могу использовать не более трех раз хеш-функцию для каждой строки последовательности, это даст сложность O (Ki), где i является i-й строкой последовательности, общая производительность будет O(K1 + K2 + ...) = O(N). - person ksm001; 01.04.2013
comment
Почему это лучше, чем объединение хэшей отдельных строк с помощью симметричной операции, такой как сложение? - person Mike Seymour; 01.04.2013
comment
@MikeSeymour - если вы покажете доказательство того, что добавление сохраняет единообразное распределение ключей, я буду рад удалить свой ответ - person bobah; 01.04.2013
comment
@bobah: я не утверждаю, что ответ неправильный; Я просто хотел бы увидеть обоснование повышенной сложности. (У меня нет времени доказывать это, но я почти уверен, что исключающее или сохранит дистрибутив; я бы использовал это, а не добавление). - person Mike Seymour; 01.04.2013
comment
@MikeSeymour - я доверяю писателю библиотеки хэшей boost как эксперту в хороших хеш-функциях и предложил ответ, используя существующий API boost::hash. Я добавил примечание о сложности: если размер ключа небольшой и фиксированный, то сортировка является дополнительной NlogN по сравнению с N для XOR. - person bobah; 01.04.2013
comment
@ ksm001 - вы вполне можете выиграть в общем времени над большим набором данных за счет лучшей хеш-функции, даже если вы заплатите дополнительную стоимость сортировки, не отбрасывайте ее, пока не докажете, что это плохо в эксперименте. - person bobah; 01.04.2013

Какую бы хеш-функцию вы ни выбрали, вам нужен оператор для окончательной комбинации каждого отдельного хэша, который будет:

  • коммутативный
  • ассоциативный

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

Вы также можете изменить порядок операций: сначала сложить строки посимвольно (например, добавление «ab» и «cba» становится ('a' + 'c')('b' + 'b')('\0 ' + 'a') с распространением переноса для суммы или произведения, так что, возможно, xor здесь является интересным кандидатом), а затем применить хеш-функцию. Вы даже можете комбинировать эти две операции при их выполнении (следует псевдокод):

int hash(string a, string b, string c){
    int r = 0, k;
    int m = max(a.length(), max(b.length(), c.length()));
    for (int i = 0; i < m; i++) {
        k = ( i < a.length()? a[i] : 0) ^
              (i < b.length()? b[i] : 0) ^
              (i < c.length()? c[i] : 0);
        r = hash(r,k);
    }
    return r;
}

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

Совершенно другое (и лучшее?) решение состоит в том, чтобы просто отсортировать последовательность (3 записи означают квазипостоянное время), а затем создать упорядоченную карту с функцией сравнения, рассматривающей строки как «цифру» трехзначного числа. Но это выходит за рамки вопроса.

person didierc    schedule 01.04.2013
comment
В то время как 3 элемента, каждый элемент имеет неограниченный размер: в такой ситуации вы хотите прочитать каждый символ не более одного раза. - person Yakk - Adam Nevraumont; 01.04.2013

Я бы хешировал каждый элемент по отдельности.

Затем отсортируйте эти хэши. Сортировка 3 size_t выполняется быстро.

Затем свяжите эти хэши. Ваша библиотека может иметь функции цепочки хэшей или даже использовать hash( a+b+c ) с переносом переполнения.

Избегайте xor, потому что xor двух одинаковых хеш-значений равен нулю. И хэш одинаковых строк идентичен. Таким образом, наивный xor может привести к тому, что ( a,a,b ) и ( c,c,b ) будут иметь один и тот же хеш-выход, что отстой.

person Yakk - Adam Nevraumont    schedule 01.04.2013