LINUX.ORG.RU

8-битный хэш для строки


0

0

Есть строка из 3-8 латинских строчных букв, хочу получить для нее 8-битный хэш для создания таблицы.
Все допустимые строки заранее известны, их примерно 40 (потом может добавиться десяток новых строк, но тогда я могу и функцию сменить).
В идеале, обойтись бы без коллизий.
Как подобрать функцию? Где об этом почитать?

★★★★

Ответ на: комментарий от xorik
$ echo "проверка 1" | md5sum
6c9fb6abfb53a35e4e468e6b3cd479d5  -

$ echo "проверка 12" | md5sum
6c64bb10b95bfc1c39b77e3b27bea720  -
Deleted
()

Может просто сумму всех символов использовать? Прогони на всех строках и проверь, есть ли коллизии.

tanenn
()

Если строки заранее известны, почему бы им просто идентификаторы не приделать от 1 до 255?

xorik ★★★★★
()

Раз строки знаешь то просто поиграйся с xor, сдвигами/вращениями и сложением. Весьма вероятно тупой xor для 40 строк уже не даст коллизий.

bga_ ★★★★
()

в 8 бит и 2 символа без коллизии не упаковать

H(S) = S[0] + S[1]*31 + ... + S[N]*31^N

FANATID
()

> строки заранее известны, их примерно 40

Пронумеруй их. // К.О.

Deleted
()
Ответ на: комментарий от bga_

вероятность отсутствия коллизий при ксоре в указанных условиях задачи 256 / 40 * 40 = 0.16

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

anymouse
()

обойтись бы без коллизий

Ага :)

o
()

crc8, поиграйся с полиномом...

arsi ★★★★★
()
Ответ на: комментарий от Deleted

в 8 бит не упаковать хеш. сплошные коллизии будут.

Почему это? Для 40 вариантов вроде и 6 бит хватило бы, только найти бы быструю функцию.

unsigned ★★★★
() автор топика
Ответ на: комментарий от xorik

Если строки заранее известны, почему бы им просто идентификаторы не приделать от 1 до 255?

Потому что на входе строка, а не инденитификатор. «Приделывание» - это и есть искомая функция.

unsigned ★★★★
() автор топика
Ответ на: комментарий от anymouse

ксоре

Долго думал, что за приложение KDE )

префиксное дерево

Наверно, накладно по памяти, но вообще интересно, попробую.

unsigned ★★★★
() автор топика

Называется - «идеальное хеширование»

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

recon88
()

Специальные программы есть для этого. gperf к примеру

unC0Rr ★★★★★
()

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

anonymous
()
Ответ на: комментарий от anymouse

Ну вот я сейчас ради эксперимента взял 40 названий цветов и что то к больше чем 11 и из них мне хеш функцию без коллизии не подобрать руками. Причем тупой xor валится уже на 2.

var strs = ['Air Force blue','Alice blue','Alizarin','Almond','Amaranth','Amber','Amber','American rose','Amethyst','Anti-flash white','Antique brass','Antique fuchsia','Antique white','Ao','Apple','Apricot','Aqua','Aquamarine','Army green','Arsenic','Arylide yellow','Ash grey','Asparagus','Atomic tangerine','Auburn','Aureolin','AuroMetalSaurus','Baby blue','Baby blue eyes','Baby pink','Ball Blue','Banana Mania','Banana yellow','Battleship grey','Bazaar','Beau blue','Beaver','Beige','Bisque','Bistre','Bittersweet'];

var _hash = function(s)
{
  var a = 0, i = s.length; while(i--) { a += s.charCodeAt(i); a = (a << 3) | (a >>> 5); }
  return a & 255;
  //return (s.charCodeAt(0) + s.charCodeAt(s.length/2 - 1) + s.charCodeAt(s.length - 1)) & 255;
};

var _test = function()
{
  var map = {};
  var i = strs.length; while(i--)
  {
    var hash = _hash(strs[i]);
    if(hash in map) 
      return console.log('fail', i, hash);
    else
      map[hash] = 1;
  }
  
  console.log('ok');
};

_test();

bga_ ★★★★
()

упрощаем задачу: есть около 40 64-битных чисел, которые надо узнавать. решение задачи в лоб: создаём сортированный массив, заполняем его допустимыми числами.

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

ckotinko ☆☆☆
()
Ответ на: комментарий от ckotinko

зачем тогда хэш нужен, если тогда достаточно строки хранить в дереве? а тут ведь именно хэш почему-то нужен

anonymous
()
Ответ на: комментарий от bga_

Да, вероятность отсутствия коллизий будет не 0.16, а еще меньше. Надо будет вечером книжку по терверу почитать, это ж одна из элементарных задач.

anymouse
()
Ответ на: комментарий от ckotinko

алсо. автор, двоичный поиск тормозит изза кэш-промахов

для массива 0 1 2 3 4 5 6 7 8 9 лучше будет его переупорядочить, чтоб элементы, которые будут сравнены первее, шли впереди. лучше 1 клок ждать данных, чем сотню.

если хеш надо будет гонять часто учти этот момент.

ckotinko ☆☆☆
()
Ответ на: комментарий от ckotinko

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

anonymous
()
Ответ на: комментарий от bga_

языка не знаю, но такое

a = (a << 3) | (a >>> 5);

в сочетании с

return a & 255;

подсказывает мне, что a не восьмибитное, и в результате вычислений скорее всего получится число большее, чем 247

unC0Rr ★★★★★
()
Ответ на: комментарий от anonymous

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

ckotinko ☆☆☆
()
Ответ на: комментарий от ckotinko

кстати. лютое бешеное спасибо ТС за то, что благодаря ему я теперь знаю как сделать парсер без деревьев на голом obstack.

ckotinko ☆☆☆
()
Ответ на: комментарий от ckotinko

Действительно, для случая статичного набора 40 строк достаточно загнать всё в отстортированный массив и далее работать двоичным поиском.

anonymous
()

Пока у меня 25 строк; сумма символов дала 2 коллизии, crc8 - 3.
gperf пока под рукой нет. Вообще, это хороший вариант, потому что при добавлении новых слов make будет достаточно. Но идеальной функции он может и не дать, как я понимаю, поэтому ненадежно.
Так что поэкспериментирую еще с деревом и с бинарным поиском. Не то, что хотелось, но хотя бы динамичнее получится. Вопрос лишь, насколько это быстро.

unsigned ★★★★
() автор топика
Ответ на: комментарий от bga_

Убери повторяющееся «Amber» и сделай s/Bisque/Bisque1/, ибо оно и 'Bistre' не очень дружат.
После этого можешь воспользоваться:

var _hash = function(s) {
    var a = 0, i = s.length;
    while (i--) {
        a += (s.charCodeAt(i) + i * i * 0.01)
    }
    return a & 255;
};

anonymous
()
Ответ на: комментарий от anonymous

Спасибо за трюк. Просто ищем n в формуле a += s.charCodeAt(i) + i * i * n. Конкретно для этих слов n = [2, 10, 14, ...] :)

bga_ ★★★★
()
Ответ на: комментарий от bga_

Проблема 'bistre' и 'bisque' заключается в том, что длины у них одинаковые и единственные символы, которые отличаются, 't' + 'r' == 's' + 'q', то есть по «весу» равны, а порядок их не учитывается. Но это можно исправить, умножая charcode на некоторую функцию f(i), которая вполне подбирается:

        var _hash = function(s) {
            var a = 0, i = s.length;
            while (i--) {
                a += s.charCodeAt(i) * (i + 44) + (i + 22)
            }
            return a & 255;
        };
anonymous
()

> 8-битный хэш для строки

Чем плох 32-х битный виртуальный адрес первого символа строки(как 32-х битныйй хеш)? Если строки не пересекаются, самое дубовое и быстрое решение.
ЕМНИП, как-то так реализовано хеширование для строк в Qt.

gogi
()
Ответ на: комментарий от anonymous

тут даже покороче есть :) a += s.charCodeAt(i) * (i + 35); но суть мне ясна

bga_ ★★★★
()
Ответ на: комментарий от unsigned

Он отсортирован в том порядке, в котором может производиться сравнение. И таким образом минимизирует количество кэш-промахов.

Вообще выступление ckotinko доставило безмерно.

Manhunt ★★★★★
()

Напиши на Perl или Python скрипт, который будет принимать список слов и выдавать C-шную функцию (пример для строк «api», «abi» и «foo»):

int keyword_to_integer(const char* p)
{
  switch (*p++) {
  case 'a' :
    switch (*p++) {
      case 'b' :
        switch (*p++) {
          case 'i' :
            return 1;
        }
        break;
      case 'p' :
        switch (*p++) {
          case 'i' :
            return 2;
        }
        break;
    }
    break;
  case 'f' :
    switch (*p++) {
      case 'o' :
        switch (*p++) {
          case 'o' :
            return 3;
        }
        break;
    }
    break;
  }

  return -1;
}

undet
()
Ответ на: комментарий от ckotinko

> алсо. автор, двоичный поиск тормозит изза кэш-промахов

Только идиоты будут оптимизировать двоичный поиск, особенно для 40 элементов, особенно, не зная полной задачи.

anonymous
()

Что-то «перевыдумываете» вы. В такой ситуации можно конечно искать hash-функцию (и gperf наверное даже её найдет). Но прямой двоичный поиск будет ничуть не хуже, т.е. получить не hash, а просто номер ключа.

Причем можно заюзать gperf --switch=256 и получить тоже самое. Если нужно (хочется) супер-быстро, то просто начинать сравнение с длины, а дальше по по 4 байта...

ly
()
Ответ на: комментарий от ly

Во, можно еще вот так вые$%^$%ся:

while (head_ != end_) {
        /*!re2c
                "keyword1" {return 1;}
                "keyword2" {return 2;}
                "keyword3" {return 3;}
                "keyword4" {return 4;}
                "keyword5" {return 5;}
        */
}

re2c сам все сделает ;-)

ly
()

>Как подобрать функцию?

ColorForth для подобных целей использует коды Шеннона-Фано. Хотя странно, вообще-то коды Хаффмана или арифметическое кодирование будут поэффективнее.

Вероятнострую модель сам подберешь.

В идеале, обойтись бы без коллизий.


Не бывает. По определению.

Macil ★★★★★
()
Ответ на: комментарий от anonymous

Хорошо пошутил, мне понравилось.

А что не так? Все строки известны заранее, хранятся в файле keywords.txt. Во время сборки (ну, или когда обновился keywords.txt) делаешь:

cat keywords.txt | ./tools/magic.py > keywords.c
Без коллизий, легко поддерживается (правкой файла keywords.txt), этот самый magic.py пишется один раз и занимает строк 50.

Я не понимаю вопроса ТС?

undet
()

Как-то так. На твоих известных строках коллизий не будет.

int hash(const char * str) {
  if (!strcmp(str, "stroka1")) {
     return 1;
  } else if (!strcmp(str, "stroka2")) {
     return 2;
  } ....
  } else if (!strcmp(str, "stroka40")) {
     return 40;
  } else {
     return crc32(str) % 256;
  }
}

Reset ★★★★★
()
Ответ на: комментарий от Macil

>> В идеале, обойтись бы без коллизий.

Не бывает. По определению.

По какому такому определению? У ТС ключей меньше, чем допустимая мощность множества значений хеша.

const86 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.