Відповіді:
Мультиплікативний метод Кнута:
hash(i)=i*2654435761 mod 2^32
Загалом, ви повинні вибрати множник, який відповідає вашому розміру хешу ( 2^32
у прикладі) і не має з ним загальних факторів. Таким чином хеш-функція рівномірно покриває весь ваш хеш-простір.
Редагувати: Найбільшим недоліком цієї хеш-функції є те, що вона зберігає подільність, тому якщо ваші цілі числа діляться на 2 або на 4 (що не є рідкістю), їхні хеші теж будуть. Це проблема в хеш-таблицях - ви можете отримати лише 1/2 або 1/4 відра.
Я виявив, що наступний алгоритм забезпечує дуже хороший статистичний розподіл. Кожен вхідний біт впливає на кожен вихідний біт з приблизно 50% вірогідністю. Сутичок немає (кожен вхід призводить до різного виходу). Алгоритм швидкий, за винятком випадків, коли в процесорі немає вбудованого цілого числа множення. C код, припускаючи , що int
це 32 біт (для Java, замінити >>
з >>>
і видалити unsigned
):
unsigned int hash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
Магічне число було розраховано за допомогою спеціальної багатопотокової програми тестування, яка працювала протягом багатьох годин, яка обчислює ефект лавини (кількість вихідних бітів, які змінюються, якщо змінено один вхідний біт; в середньому має бути майже 16), незалежність зміни вихідного біта (вихідні біти не повинні залежати один від одного), а також ймовірність зміни кожного вихідного біта, якщо будь-який вхідний біт змінений. Обчислені значення кращі, ніж 32-розрядний фіналізатор, який використовується MurmurHash , і майже такий же хороший (не зовсім), як при використанні AES . Невелика перевага полягає в тому, що одна і та ж константа використовується двічі (це зробило її трохи швидше, коли я тестував останній раз, не впевнений, чи все ще так).
Ви можете змінити процес (отримати значення вхідного сигналу від хеша) , якщо замінити 0x45d9f3b
з 0x119de1f3
(в мультиплікативної інверсії ):
unsigned int unhash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x119de1f3;
x = ((x >> 16) ^ x) * 0x119de1f3;
x = (x >> 16) ^ x;
return x;
}
Для 64-розрядних чисел я пропоную скористатися наступним, навіть подумав, що це може бути не найшвидшим. Цей заснований на splitmix64 , який, здається, базується на статті блогу Better Bit Mixing (змішання 13).
uint64_t hash(uint64_t x) {
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
x = x ^ (x >> 31);
return x;
}
Для Java, використання long
, додати L
до константи, замініть >>
з >>>
і видалити unsigned
. У цьому випадку реверсування складніше:
uint64_t unhash(uint64_t x) {
x = (x ^ (x >> 31) ^ (x >> 62)) * UINT64_C(0x319642b2d24d8ec3);
x = (x ^ (x >> 27) ^ (x >> 54)) * UINT64_C(0x96de1b173f119089);
x = x ^ (x >> 30) ^ (x >> 60);
return x;
}
Оновлення: Ви також можете переглянути проект Hash Function Prospector , де вказані інші (можливо кращі) константи.
x = ((x >> 32) ^ x)
а потім використовую 32-розрядні множення вище. Я не впевнений, що краще. Ви також можете подивитися 64-розрядний фіналізатор для Murmur3
Залежить від способу розповсюдження ваших даних. Для простого лічильника найпростіша функція
f(i) = i
буде добре (я підозрюю оптимальне, але не можу цього довести).
Швидкі та хороші хеш-функції можуть складатися із швидких перестановок із меншими якостями, як-от
Для отримання функції хешування з чудовими якостями, як це показано з PCG для генерації випадкових чисел.
Це насправді також рецепт rrxmrrxmsx_0 та хеш-шум, що використовується, свідомо чи несвідомо.
Я особисто знайшов
uint64_t xorshift(const uint64_t& n,int i){
return n^(n>>i);
}
uint64_t hash(const uint64_t& n){
uint64_t p = 0x5555555555555555ull; // pattern of alternating 0 and 1
uint64_t c = 17316035218449499591ull;// random uneven integer constant;
return c*xorshift(p*xorshift(n,32),32);
}
щоб бути досить хорошим.
Хороша хеш-функція повинна
Давайте спочатку розглянемо функцію ідентичності. Він задовольняє 1., але не 2.:
Біт введення n визначає вихідний біт n із співвідношенням 100% (червоний) та жодних інших, тому вони сині, даючи ідеальну червону лінію поперек.
A xorshift (n, 32) не набагато кращий, що дає півтора рядка. Все-таки задовольняючи 1., оскільки воно є зворотним при другому застосуванні.
Множення з непідписаним цілим числом набагато краще, каскадніше сильніше і перегортаючи більше вихідних бітів з вірогідністю 0,5, що ви хочете, зеленим кольором. Він задовольняє 1. оскільки для кожного нерівномірного цілого числа існує мультиплікативна обернена.
Поєднання двох дає наступний результат, все-таки задовольняючи 1. оскільки склад двох біективних функцій дає ще одну біективну функцію.
Друге застосування множення і xorshift призведе до наступного:
Або ви можете використовувати множення поля Galois на зразок GHash , вони стали досить швидко на сучасних процесорах і мають вищі якості за один крок.
uint64_t const inline gfmul(const uint64_t& i,const uint64_t& j){
__m128i I{};I[0]^=i;
__m128i J{};J[0]^=j;
__m128i M{};M[0]^=0xb000000000000000ull;
__m128i X = _mm_clmulepi64_si128(I,J,0);
__m128i A = _mm_clmulepi64_si128(X,M,0);
__m128i B = _mm_clmulepi64_si128(A,M,0);
return A[0]^A[1]^B[1]^X[0]^X[1];
}
__m128i I = i; //set the lower 64 bits
, але це не можу, тому я використовую ^=
. 0^1 = 1
тому жодне не викликається. Щодо ініціалізації, з якою {}
мій компілятор ніколи не скаржився, це може бути не найкращим рішенням, але те, що я хочу, - це ініціалізувати все до 0, щоб я міг зробити ^=
або |=
. Я думаю, що я базував цей код на цьому блозі, який також дає інверсію, дуже корисну: D
На цій сторінці перераховано кілька простих хеш-функцій, які, як правило, пристойно загалом, але будь-який простий хеш має патологічні випадки, коли він не працює добре.
32-бітний мультиплікативний метод (дуже швидкий) див. @Rafal
#define hash32(x) ((x)*2654435761)
#define H_BITS 24 // Hashtable size
#define H_SHIFT (32-H_BITS)
unsigned hashtab[1<<H_BITS]
....
unsigned slot = hash32(x) >> H_SHIFT
32-біт і 64-біт (хороший розподіл) за адресою: MurmurHash
Тут є хороший огляд деяких алгоритмів хешу на Eternally Confuzzled . Я рекомендую одноразовий хеш Боба Дженкінса, який швидко досягає лавини і тому може бути використаний для ефективного пошуку хеш-таблиць.
Відповідь залежить від багатьох речей, таких як:
Я пропоную вам поглянути на сімейство Меркле-Дамгард хеш-функцій, таких як SHA-1 тощо
Я не думаю, що ми можемо сказати, що хеш-функція "хороша", не знаючи ваших даних заздалегідь! і не знаючи, що ти будеш робити з цим.
Є кращі структури даних, ніж хеш-таблиці для невідомих розмірів даних (я припускаю, що ви робите хешування для хеш-таблиці тут). Я особисто використовував би хеш-таблицю, коли знаю, що у мене є "кінцева" кількість елементів, які потребують збереження в обмеженому обсязі пам'яті. Я б спробував зробити швидкий статистичний аналіз моїх даних, побачити, як він розподіляється і т. Д., Перш ніж почати думати про свою хеш-функцію.
Щодо випадкових хеш-значень, деякі інженери сказали, що золоте співвідношення просте число (2654435761) є поганим вибором, і завдяки моїм результатам тестування я виявив, що це неправда; натомість 2654435761 досить добре поширює хеш-значення.
#define MCR_HashTableSize 2^10
unsigned int
Hash_UInt_GRPrimeNumber(unsigned int key)
{
key = key*2654435761 & (MCR_HashTableSize - 1)
return key;
}
Розмір хеш-таблиці повинен бути потужністю два.
Я написав тестову програму для оцінки багатьох хеш-функцій для цілих чисел, результати показують, що GRPrimeNumber є досить хорошим вибором.
Я намагався:
За результатами тестування я виявив, що Prime Ratio Prime Number завжди має менше порожніх відра або нуль порожнього відра і найкоротшу довжину ланцюга зіткнення.
Деякі хеш-функції для цілих чисел вважаються хорошими, але результати тестування показують, що коли total_data_entry / total_bucket_number = 3, найдовша довжина ланцюга перевищує 10 (максимальне число зіткнення> 10), і багато відра не відображаються (порожні відра ), що дуже погано, в порівнянні з результатом нульового порожнього відра і найдовшої довжини ланцюга 3 за допомогою Золотого коефіцієнта.
До речі, за результатами тестування я виявив, що одна версія хеш-функцій shifting-xor є досить хорошою (її поділяє mikera).
unsigned int Hash_UInt_M3(unsigned int key)
{
key ^= (key << 13);
key ^= (key >> 17);
key ^= (key << 5);
return key;
}
Я використовував splitmix64
(загострений в Томаса Мюллера відповіді ) з тих пір я знайшов цю тему. Однак я нещодавно натрапив на rrxmrrxmsx_0 Pelle Evensen , який дав надзвичайно кращий статистичний розподіл, ніж оригінальний фіналізатор MurmurHash3 та його наступники ( splitmix64
та інші суміші). Ось фрагмент коду в C:
#include <stdint.h>
static inline uint64_t ror64(uint64_t v, int r) {
return (v >> r) | (v << (64 - r));
}
uint64_t rrxmrrxmsx_0(uint64_t v) {
v ^= ror64(v, 25) ^ ror64(v, 50);
v *= 0xA24BAED4963EE407UL;
v ^= ror64(v, 24) ^ ror64(v, 49);
v *= 0x9FB21C651E98DF25UL;
return v ^ v >> 28;
}
Pelle також надає глибокий аналіз 64-бітного змішувача, що використовується на останньому етапі MurmurHash3
та останніх варіантів.