Хороша функція хешу для струн


160

Я намагаюся придумати гарну хеш-функцію для рядків. І я думав, що може бути гарною ідеєю підбити підсумки значень Unicode для перших п'яти символів рядка (якщо припустити, що їх п'ять, інакше зупиніться там, де він закінчується). Це було б гарною ідеєю чи поганою?

Я роблю це на Java, але не думаю, що це значно зміниться.


4
Хороші хеш-функції сильно залежать від входу в хеш і вимог алгоритму. Такий хеш не буде дуже хорошим, якщо, наприклад, усі ваші рядки починаються з тих же п’яти символів. Це також, як правило, призведе до нормального розподілу.
WhirlWind

1
Можливий дублікат 98153
Michael Mrozek

14
Чому ви не можете використовувати Stringвласну hashCode()?
Барт Кірс

@WhirlWind, правда, я не впевнений, якими будуть рядки, окрім того, що це, ймовірно, англійський текст.
Лейф Андерсен

@Barl, головним чином тому, що мій професор сказав нам реалізувати наш власний хеш-функтор ... і тому, чому я не хотів використовувати Java, це тому, що це було загальним, і я б уявив, що більш конкретний хеш-функтор буде краще.
Лейф Андерсен

Відповіді:


161

Зазвичай хеш не робитиме суми, в іншому випадку stopі potsбуде мати той же хеш.

і ви не обмежуєте це першими n символами, бо в іншому випадку будинок і будинки матимуть однаковий хеш.

Як правило, хеші приймають значення та помножують їх на просте число (робить його більш ймовірним генерувати унікальні хеші). Ви можете зробити щось на кшталт:

int hash = 7;
for (int i = 0; i < strlen; i++) {
    hash = hash*31 + charAt(i);
}

@jonathanasdf Як можна сказати, що він завжди дає вам унікальний хеш-ключ. Чи є математичний доказ? Я думаю, що ми повинні взяти мод хешу з іншим більшим простим числом, інакше виникає проблема переповнення.
devsda

17
@devsda Він не говорив, що завжди унікальний, він, швидше за все, унікальний. Що стосується того, чому швидкий пошук у Google розкриває цю статтю: computinglife.wordpress.com/2008/11/20/…, пояснюючи, чому 31 використовувався для хешування рядків Java. Математичного доказу немає, але це пояснює загальну концепцію того, чому праймери краще працюють.
Pharap

2
Велике спасибі за роз’яснення ідеї зробити краще хешування. Просто для подвійної перевірки - повернення значення hashCode () буде використане Java для відображення індексу таблиці перед збереженням об'єкта. Отже, якщо hashCode () повертає m, він робить щось на зразок (m mod k), щоб отримати індекс таблиці розміру k. Це так?
whitehat

1
"хеш = хеш * 31 + charAt (i);" створює той же хеш для плями, верхівки, стоп, оптів і горщиків.
Джек Штрауб

1
@maq Я вважаю, ти прав. Не знаю, про що я думав.
Джек Страуб

139

Якщо це справа безпеки, ви можете використовувати криптовалюту Java:

import java.security.MessageDigest;

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(stringToEncrypt.getBytes());
String encryptedString = new String(messageDigest.digest());

93
Приємно. У мене є програма машинного навчання, роблячи статистичний НЛП на великому корпусі. Після кількох початкових пропусків морфологічної нормалізації вихідних слів у тексті я викидаю рядкові значення та використовую замість хеш-кодів. В усьому моєму корпусі є близько 600 000 унікальних слів, і за допомогою функції хеш-коду Java за замовчуванням я отримував близько 3,5% зіткнень. Але якщо я SHA-256 значення рядка і потім генерувати хеш-код з перекопаної рядки, коефіцієнт зіткнення становить менше 0,0001%. Дякую!
benjismith

3
Дякуємо, що надали інформацію про зіткнення та кількість слів. Дуже корисний.
Філіпп

19
@benjismith Один мільйон - це занадто багато ... це "менше 0,0001%" косий спосіб сказати "рівно 0"? Я дуже сумніваюся, що ви побачили зіткнення SHA-256, оскільки цього ніколи не спостерігали, ніде і ніколи; навіть не для 160-бітного SHA-1. Якщо у вас є два рядки, які створюють один і той же SHA-256, спільнота безпеки хотіла б їх бачити; ти будеш всесвітньо відомим ... дуже незрозумілим чином. Дивіться Порівняння функцій SHA
Тім Сильвестр

7
@TimSylvester, ти неправильно зрозумів. Я не знайшов зіткнень SHA-256. Я обчислив SHA-256, а потім подав отримані послідовності байтів у типову функцію Java "hashCode", тому що мені потрібен був 32-бітний хеш. Саме там я і знайшов зіткнення. Нічого не примітного :)
benjismith

1
Чи не існує різниці між "хешуваннями" та "шифруванням"? Я розумію, що MessageDigest - це одностороння хеш-функція, правда? Крім того, коли я використав цю функцію, я отримав хешовану рядок як багато непотрібних символів UTF, коли відкрив файл у LibreOffice. Чи можливо отримати хешований рядок у вигляді випадкового буквено-цифрових символів замість небажаних символів UTF?
Nav

38

Ймовірно, ви повинні використовувати String.hashCode () .

Якщо ви дійсно хочете самостійно реалізувати hashCode:

Не спокушайтесь виключити значні частини об'єкта з обчислення хеш-коду для підвищення продуктивності - Джошуа Блох, Ефективна Java

Використання лише перших п’яти символів - погана ідея . Подумайте про ієрархічні назви, такі як URL-адреси: всі вони матимуть однаковий хеш-код (адже всі вони починаються з "http: //", що означає, що вони зберігаються під тим же відром у хеш-карті, демонструючи жахливу ефективність.

Ось історія війни, перефразована на хеш-коді String з " Ефективна Java ":

Функція String хеш, реалізована у всіх випусках до 1,2, розглянута не більше шістнадцяти символів, рівномірно розміщених по всій рядку, починаючи з першого символу. Для великих колекцій ієрархічних імен, таких як URL-адреси, ця хеш-функція відображала жахливу поведінку.


1
Якщо ви використовуєте подвійну колекцію колекції, можливо, перший хеш може бути дійсно швидким і брудним. Якщо в одному є тисяча довгих рядків, половина з яких відображається за допомогою функції крихти на одне конкретне значення, а половина з них відображається на окремі значення, продуктивність в односхильній таблиці буде поганою, але продуктивність у подвійному режимі - хеш-таблиця, де другий хеш досліджував усю рядок, може бути майже вдвічі більший за таблицю з окремим хешем (оскільки половина рядків не повинна бути повністю хешованою). Жодна зі стандартних колекцій Java не робить подвійне хешуваннявання.
supercat

Посилання Effective Java зламана @Frederik
кГс

17

Якщо ви робите це на Java, то навіщо це робити? Просто зателефонуйте .hashCode()на рядок


2
Я роблю це як частина класу, і частина завдання полягає в тому, щоб записати кілька різних хеш-функцій. Професор сказав нам, щоб отримати зовнішню допомогу для «кращих».
Лейф Андерсен

20
Якщо вам потрібно, щоб ваша версія була послідовною у всіх версіях та версіях JVM, ви не повинні покладатися на це .hashCode(). Швидше скористайтеся відомим алгоритмом.
Стівен Остерміллер

7
Алгоритм до String::hashCodeвказаний в JDK, тому він такий же портативний, як і саме існування класу java.lang.String.
yshavit


8

Ця функція, яку надає Нік, хороша, але якщо ви використовуєте новий String (байт [] байт) для перетворення в String, він не вдався. Ви можете використовувати цю функцію для цього.

private static final char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

public static String byteArray2Hex(byte[] bytes) {
    StringBuffer sb = new StringBuffer(bytes.length * 2);
    for(final byte b : bytes) {
        sb.append(hex[(b & 0xF0) >> 4]);
        sb.append(hex[b & 0x0F]);
    }
    return sb.toString();
}

public static String getStringFromSHA256(String stringToEncrypt) throws NoSuchAlgorithmException {
    MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
    messageDigest.update(stringToEncrypt.getBytes());
    return byteArray2Hex(messageDigest.digest());
}

Може, це може комусь допомогти


Ви можете просто передати масив байтів до messageDigest.update ().
szgal

byteArray2Hex () - це ідеально те, що я шукав! Велике спасибі :)
Krzysiek


5

По чутках, FNV-1 є гарною хеш-функцією для рядків.

Для довгих рядків (довше, ніж, скажімо, близько 200 символів) ви можете отримати хороші показники з хеш-функції MD4 . Як криптографічна функція, вона була зламана близько 15 років тому, але для некриптографічних цілей вона все ще дуже хороша і на диво швидка. У контексті Java вам доведеться перетворити 16-бітні charзначення в 32-бітні слова, наприклад, згрупувавши такі значення в пари. Швидку реалізацію MD4 на Java можна знайти в shlib . Можливо, надмірність у контексті завдання в класі, але в іншому випадку варто спробувати.


Ця хеш-функція набагато краще, ніж та, яка постачається з java.
clankill3r

3

Якщо ви хочете побачити стандартні впровадження в галузі, я погляну на java.security.MessageDigest .

"Дайджести повідомлень - це безпечні хеш-функції з одностороннім способом, які приймають дані довільного розміру та виводять хеш-фіксовану фіксовану довжину."


1

ось посилання, що пояснює багато різних хеш-функцій, я зараз віддаю перевагу хеш-функції ELF для вашої конкретної проблеми. Він приймає в якості рядка довільну довжину.


1

sdbm: цей алгоритм був створений для бібліотеки баз даних sdbm (повторне втілення ndbm у відкритому домені)

static unsigned long sdbm(unsigned char *str)
{   
    unsigned long hash = 0;
    int c;
    while (c = *str++)
            hash = c + (hash << 6) + (hash << 16) - hash;

    return hash;
}

0
         public String hashString(String s) throws NoSuchAlgorithmException {
    byte[] hash = null;
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        hash = md.digest(s.getBytes());

    } catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < hash.length; ++i) {
        String hex = Integer.toHexString(hash[i]);
        if (hex.length() == 1) {
            sb.append(0);
            sb.append(hex.charAt(hex.length() - 1));
        } else {
            sb.append(hex.substring(hex.length() - 2));
        }
    }
    return sb.toString();
}

-1

Хороша ідея працювати з непарним числом, намагаючись розробити гарну функцію прискорення для рядка. ця функція приймає рядок і повертає значення індексу, поки її робота досить хороша. і має менше зіткнень. індекс коливається від 0 до 300, можливо, навіть більше, ніж це, але я поки що не домігся вищого навіть з довгими словами типу "електромеханічна інженерія"

int keyHash(string key)
{
    unsigned int k = (int)key.length();
    unsigned int u = 0,n = 0;

    for (Uint i=0; i<k; i++)
    {
        n = (int)key[i];
        u += 7*n%31;
    }
    return u%139;
}

Ще одна річ, яку ви можете зробити, - це множення кожного символу int розбору на індекс, оскільки він збільшується, як слово "ведмідь" (0 * b) + (1 * e) + (2 * a) + (3 * r), яке дасть вам значення int, з яким потрібно грати. Перша хеш-функція вище стикається на "тут" і "чути", але все ще чудово дає хороші унікальні значення. наведений нижче не стикається з "тут" і "чути", тому що я помножую кожен символ на індекс у міру його збільшення.

int keyHash(string key)
{
    unsigned int k = (int)key.length();
    unsigned int u = 0,n = 0;

    for (Uint i=0; i<k; i++)
    {
        n = (int)key[i];
        u += i*n%31;
    }
    return u%139;
}

-1

Ось проста функція хешу, яку я використовую для створеної хеш-таблиці. Це в основному для прийняття текстового файлу і зберігає кожне слово в індексі, який представляє алфавітний порядок.

int generatehashkey(const char *name)
{
        int x = tolower(name[0])- 97;
        if (x < 0 || x > 25)
           x = 26;
        return x;
}

Це, в основному, це слова, хешовані відповідно до їх першої літери. Отже, слово, що починається з "a", отримає хеш-ключ 0, "b" отримає 1 і так далі, а "z" буде 25. Номери та символи мали б хеш-ключ 26. Ось перевага, яку це забезпечує ; Ви можете легко і швидко обчислити, де дане слово буде індексовано в хеш-таблиці, оскільки все це в алфавітному порядку, приблизно так: Код можна знайти тут: https://github.com/abhijitcpatil/general

Даючи наступний текст у якості введення: Аттікус одного дня сказав Джему: «Я вважаю за краще ви стріляли з жерстяних банок на задньому дворі, але я знаю, що підете за птахами. Розстріляйте всі сині сорочки, які вам захочеться, якщо їх вдарить, але пам’ятайте, що вбити гнучка птаха гріх ”. Це був єдиний раз, коли я чув, як Аттікус сказав, що було гріхом щось робити, і я поцікавився міс Модді про це. "Право твого батька", - сказала вона. «Знущаючі птахи не займаються одним, окрім того, що створюють музику для нас. Вони не їдять сади людей, не гніздяться на кукурудзяних ліжечках, вони не роблять одного, але співають для нас своє серце. Ось чому гріхом вбити глумливого птаха.

Це буде вихід:

0 --> a a about asked and a Atticus a a all after at Atticus
1 --> but but blue birds. but backyard
2 --> cribs corn can cans
3 --> do dont dont dont do dont do day
4 --> eat enjoy. except ever
5 --> for for fathers
6 --> gardens go
7 --> hearts heard hit
8 --> its in it. I it I its if I in
9 --> jays Jem
10 --> kill kill know
11 --> 
12 --> mockingbird. music make Maudie Miss mockingbird.”
13 --> nest
14 --> out one one only one
15 --> peoples
16 --> 17 --> right remember rather
18 --> sin sing said. she something sin say sin Shoot shot said
19 --> to Thats their thing they They to thing to time the That to the the tin to
20 --> us. up us
21 --> 
22 --> why was was want
23 --> 
24 --> you you youll you
25 --> 
26 --> Mockingbirds  Your em Id

2
Хороша хеш-функція розподіляє значення порівну по відрах.
Джонатан Петерсон

-1

Це дозволить уникнути будь-якого зіткнення, і це буде швидко, поки ми не використаємо зрушення в розрахунках.

 int k = key.length();
    int sum = 0;
    for(int i = 0 ; i < k-1 ; i++){
        sum += key.charAt(i)<<(5*i);
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.