Допустим у нас есть некая таблица(а.к.а. словарь) состоящая из N ключевых слов[1].
Нам нужно написать функцию типа:
int search(const char* some_word);
которая быстро возвращает либо число из диапазона [1..N] если some_word в таблице или 0 если такого слова там нет.
Задача построения такой функции решаема несколькими вариантами, один из них например
построить т.н. minimal perfect hash function с помощью например gperf (но это возможно не на каждом наборе).
Второй вариант — воспользоваться предлагаемой имплементацией ternary tree [2]:
Класс ternary_tree кроме двух очевидных и полезных методов:
neighbour_search позволяет находить "похожие" словаю. Мера похожести задается параметром d — HAMMING DISTANCE [3].
например neighbour_search("twa",2) найдет два слова "twa" и "twas"
Замечания по имплементации:
1) Я использую свой array который может быть легко заменен на std::vector
2) Выбранный способ аллокации nodes (элементы в масиве) помимо всего прочего позволяет
задавать compiled static node tables — т.е. если у вас есть статическая таблица keywords то в принципе её можно скомпилировать и разместить в коде как static. Т.е. для проведения search не надо будет делать inserts при инициализации.
Алгоритм известен своей практически идеальной производительностью.
Во всяком случае теоретически лучше чем hash_table's
Здравствуйте, korzhik, Вы писали:
K>Здравствуйте, c-smile, Вы писали:
CS>>Второй вариант — воспользоваться предлагаемой имплементацией ternary tree [2]:
K>спасибо, очень полезная вещь. K>правда я пока не тестил, но думаю от вас всё идёт со знаком качества.
Спасибо
K>кстати имплементацию в boost::spirit'е не смотрели? K>если интересно то путь такой boost\spirit\symbols\impl\tst.ipp
Спасибо за ссылку, глянул.
На вскидку что бросается в глаза сразу — это аллокция nodes в heap что в общем-то не эффективно для массива структур имеющих одинаковый размер экземпляров. Плюс расход памяти.
У меня например вместо указателя на node хранится его индекс который в большинстве случав может быть short int а не pointer как в boost/spirit.
Например для моего тестового примера таблица имен цветов в HTML (http://www.w3schools.com/html/html_colornames.asp) примерно 140 элементов.
Количество nodes примерно 1030. (Для справки общее количество символов во всех словах таблицы около 1200).Т.е. при аллокации на хипе несколько затратно/неэффективно получается.
CS>Допустим у нас есть некая таблица(а.к.а. словарь) состоящая из N ключевых слов[1]. CS>Нам нужно написать функцию типа: CS>
CS> int search(const char* some_word);
CS>
CS>которая быстро возвращает либо число из диапазона [1..N] если some_word в таблице или 0 если такого слова там нет.
У меня честно говоря не было времени вникать в сорсы и теорию, можно ли воспроизвести слово "быстро" в математических терминах типа О(х)?
И второй вопрос: может ли такое случиться, что при определенном наборе слов тернарное дерево вырождается, и какие действия предлагаются в этом случае для его балансировки?
Serge.
Hасколько проще была бы жизнь, если бы она была в исходниках.
Здравствуйте, Sergeem, Вы писали:
CS>>которая быстро возвращает либо число из диапазона [1..N] если some_word в таблице или 0 если такого слова там нет.
S>У меня честно говоря не было времени вникать в сорсы и теорию, можно ли воспроизвести слово "быстро" в математических терминах типа О(х)?
Насколько я понимаю поиск строки длины k в дереве с n элементами займет O(log n+k) шагов в худшем случае.
Здравствуйте, Sergeem, Вы писали:
S>У меня честно говоря не было времени вникать в сорсы и теорию, можно ли воспроизвести слово "быстро" в математических терминах типа О(х)? S>И второй вопрос: может ли такое случиться, что при определенном наборе слов тернарное дерево вырождается, и какие действия предлагаются в этом случае для его балансировки?
Time Complexity
In a balanced ternary search tree with n nodes where all the keywords
of the nodes are k characters long, we want to search a k character long input string. The total number of character-to-character comparisons is at most
lg n + k.
For a balanced binary search tree of n-nodes and k character long keywords, the total number of character-to-character comparisons is at most
k * lg n
which is substantially larger than lg n + k for large n.
Ternary tree не требует балансировки.
Оно всегда в "хорошем состянии"
In what order should you insert the nodes into a tree? No matter in what order you insert the nodes, you end up with the same digital search trie -- the data structure is totally insensitive to insertion order.
Binary search trees are at the opposite end of the spectrum: If you insert the nodes in a good order (middle element first), you end up with a balanced tree. If you insert the nodes in sorted order, the result is a long skinny tree that is very costly to build and search. Fortunately, if you insert the nodes in random order, a binary search tree is usually close to balanced.
Тем не менее скорость работы функции insert зависит от порядка следования подаваемых слов, search — нет.
Справедливости ради надо отметить что hash-table как механизм тоже вельми эффеткивен.
При хорошем раскладе (а.к.а. неловленный мизер) количество character-to-character comparisons
может быть вообще
2 * k
(я приравниваю сложность обработки одного символа при вычислении хэш функции к одному сравнению, что в общем не так, но близко).
При плохом раскладе (все слова попали в один collision chain) количество сравнений
Здравствуйте, c-smile, Вы писали:
CS>Ternary tree не требует балансировки. CS>Оно всегда в "хорошем состянии"
Скорее всего это было мое оптимистичное заявление.
Потому как вот:
The input order of data cells into a ternary search trie is still important to create balanced tree, but less important than in a binary search tree.
According to many authors ([Mehlhorn79] and [Bentley and Saxe79]), by choosing the true median of a set of data-cells as the starting node, we can create a well-balanced ternary tree without the randomizing proces
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, c-smile, Вы писали:
CS>>Ternary tree не требует балансировки. CS>>Оно всегда в "хорошем состянии"
CS>Скорее всего это было мое оптимистичное заявление.
CS>Потому как вот: CS>
CS>The input order of data cells into a ternary search trie is still important to create balanced tree, but less important than in a binary search tree.
CS>According to many authors ([Mehlhorn79] and [Bentley and Saxe79]), by choosing the true median of a set of data-cells as the starting node, we can create a well-balanced ternary tree without the randomizing proces
Здравствуйте, Sergeem, Вы писали:
S>Здравствуйте, c-smile, Вы писали:
CS>>Допустим у нас есть некая таблица(а.к.а. словарь) состоящая из N ключевых слов[1]. CS>>Нам нужно написать функцию типа: CS>>
CS>> int search(const char* some_word);
CS>>
CS>>которая быстро возвращает либо число из диапазона [1..N] если some_word в таблице или 0 если такого слова там нет.
S>У меня честно говоря не было времени вникать в сорсы и теорию, можно ли воспроизвести слово "быстро" в математических терминах типа О(х)? S>И второй вопрос: может ли такое случиться, что при определенном наборе слов тернарное дерево вырождается, и какие действия предлагаются в этом случае для его балансировки?
По многим вопросам, и в частности по вопросу тернарного дерева(кстати, на днях сам реализовывал, поздно заглянул сюда), ИМХО, лучше обратиться к Седжвику. Там все достаточно подробно и авторитетно расписано, в том числе есть ответы и на ваши вопросы.
CS>Допустим у нас есть некая таблица(а.к.а. словарь) состоящая из N ключевых слов[1]. CS>Нам нужно написать функцию типа: CS>
CS> int search(const char* some_word);
CS>
CS>которая быстро возвращает либо число из диапазона [1..N] если some_word в таблице или 0 если такого слова там нет.
CS>Задача построения такой функции решаема несколькими вариантами, один из них например CS>построить т.н. minimal perfect hash function с помощью например gperf (но это возможно не на каждом наборе).
[skiped] CS>
Решил испытать ваше решение,
После объявления
tool::ternary_tree<char> TST;
при попытке компиляции вываливается:
d:\Prog. NET\Solution1\Dict_Search\main.cpp(230) : error C2440: '=' : cannot convert from 'const tool::ternary_tree<CHAR>::node' to 'tool::ternary_tree<CHAR>::node_index'
node_index insert(node_index nid, const CHAR *s)
{
if (nid >= total_nodes())
{
node n; n.splitchar = *s;
nodes.push(n);
nid = nodes.last_index(); // здесь ошибка
}
Может быть подскажете, что не так делаю?
И еще вопрос параллелльно : на сколько ваш array по быстродействию выигрывает у vector? Думаю, измерения проводились...
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, Dmitry521, Вы писали:
CS>>>
size_t ternary_tree::neighbour_search(const char *s, int d, array<dword>& result_set);
CS>>>
Посмотрел как работает эта функция.
Мне кажется она неверно считает HAMMING DISTANCE.
например я ввел пять слов
woman, man, many, wman, mwan
искал слово man.
результаты
d = 1 man, many Я считаю должно было быть — man, many, mwan, wman
d = 2 man, many Я считаю должно было быть — man, many, mwan, wman, woman
d = 3 man, many, mwan
d = 4 man, many, mwan, wman
d = 5 man, many, mwan, wman, woman
Наблюдается очень сильная зависимость от начала слова.
Здравствуйте, uw, Вы писали:
uw>Здравствуйте, Dr.Gigabit, Вы писали:
uw>Вот тот же код, слегка переделанный под std::vector, вроде работает.
vector<dword> resultSet;
int i;
tool::ternary_tree<char> TST;
TST.insert("Andy");
TST.insert("Andyx");
i = TST.neighbour_search("Andy",1, resultSet); // здесь i = 2 как положеноfor(vector<dword>::iterator it = resultSet.begin(); it != resultSet.end(); it++)
cout << *it << endl; //здесь что-то не то
Что не так делаю с resultSet. По идее должны выводиться найденные слова
В моем array метод last_index() возвращает int — последний валидный индекс в массиве или -1.
node_index это unsigned int; должно приводится без сайдэффектов здесь.
DG>Может быть подскажете, что не так делаю? DG>И еще вопрос параллелльно : на сколько ваш array по быстродействию выигрывает у vector? Думаю, измерения проводились...
Мой массив в его ьекущей инкарнации работает с абсолютно той же скоростью что и std::vector.
В ситуациях например типа array< smart_ptr<something> > мой работает на insert/append быстрее. Иногда значительно. Все мои классы позиционно независимы поэтому я могу себе позволить memcpy.
Что требует определенной дисциплины. Поэтому я его и не выкладываю.
CS>В моем array метод last_index() возвращает int — последний валидный индекс в массиве или -1. CS>node_index это unsigned int; должно приводится без сайдэффектов здесь.
Смотрим array.h:
const element&
last_index () const
{
return _size — 1;
}
Видимо, вместо const element& нужно int? Подправил — работает
Еще по одному вопросу не могу сообразить (что-то в 3 часа ночи не думается)
Здравствуйте, Dr.Gigabit, Вы писали:
DG>Смотрим array.h: DG> const element& DG> last_index () const DG> { DG> return _size — 1; DG> } DG>Видимо, вместо const element& нужно int? Подправил — работает DG>Еще по одному вопросу не могу сообразить (что-то в 3 часа ночи не думается)
У-у-у, какая древность, но все равно спасибо, надо опять на SF идти изменять а там все не просто.
Да конечно нужно так:
inline const element& last () const;
inline int last_index () const
{
return _size - 1;
}
DG>Выводит 1. Каким образом слова отображать-то? DG>В своей реализации TST я использую vector<char*> resultSet; DG>А как с dword'ом быть?
CS>Второй вариант — воспользоваться предлагаемой имплементацией ternary tree [2]:
[skiped]
Не могли бы вы прояснить такую вещь:
При записи всех узлов в бинарный файл, количество записываемых узлов 107184
(значение, возвращаемое total_nodes() ). Но в одном из узлов lokid имеет значение 4294967295
В то же время при проецировании бинарного файла, в который предварительно записаны все узлы, в память,
их количество 3435973836 (проверяю в цикле, пока указатель не NULL)
Как locid может иметь значение, большее кол-ва узлов? Или я где-то не прав?
Есть серьезный недостаток — требовательность к памяти. Чем длиннее ключ — тем больше хавает. А если ключи еще и различаются в начале — вообще абзац.
Проверял практическим способом
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Здравствуйте, CreatorCray, Вы писали:
CC>Здравствуйте, c-smile, Вы писали:
CC>Есть серьезный недостаток — требовательность к памяти. Чем длиннее ключ — тем больше хавает. А если ключи еще и различаются в начале — вообще абзац.
Ну в общем и целом утверждение "требовательность к памяти" справедливо для любой конструкции такого рода.
Для hash tables нужно тоже где-то ключи хранить.
Причем если ключи примерно такого типа:
Здравствуйте, c-smile, Вы писали:
CS>Ну в общем и целом утверждение "требовательность к памяти" справедливо для любой конструкции такого рода. CS>Для hash tables нужно тоже где-то ключи хранить.
Но там то ключи фиксированного размера = размеру хеша. А хеш можно посчитать по ооочень длинной строке.
CS>Причем если ключи примерно такого типа: [skipped] CS>То оверхед у hash-table по памяти будет больше.
Это понятно, речь не об этом. Сравнивать его лучше с Patricia как с близким по принципу действия.
Впрочем вы с cranky и так уже пересеклись
Когда я запхал в него и в patricia телефонный справочник (номер + фио как один ключ — все ключи уникальны) то ternary отожрал больше гига памяти. Тут просто трабл в том, что на 1 символ ключа выделяется +3*sizeof (uint). И в ситуации, когда ключи различаются в самом начале (Ex: "123 мама мыла раму" "124 мама мыла раму" "223 мама мыла раму" и т.д.) то оверхед получается максимальным — все "хвосты" ключей добавляются в nodes, а это 13 байт на char.
Также этот алго проигрывает в случае если ключи — пути к файлам. К примеру список всех файлов HDD с путями: 99519 строк. Ternary: 32.9 Mb 15508 CPU ticks per lookup, Patricia: 23.8 Mb 3811 CPU ticks per lookup.
Но на простых текстах (выборка всех слов из 400 мб. текстовика: слил много книг в 1 txt файл) Ternary рвет patricia с отрывом в 3-5 раз по скорости и почти в 2 раза по памяти. Но при этом следует учесть что Patricia хранит имена ключей.
IMHO ternary алго отличный, но область применения ограничена по причине зависимости потребления ресурсов от входных данных.
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Здравствуйте, CreatorCray, Вы писали:
CC>Здравствуйте, c-smile, Вы писали:
CS>>Ну в общем и целом утверждение "требовательность к памяти" справедливо для любой конструкции такого рода. CS>>Для hash tables нужно тоже где-то ключи хранить. CC>Но там то ключи фиксированного размера = размеру хеша. А хеш можно посчитать по ооочень длинной строке.
Но сами строки в полном виде ты хранить тоже обязан для разрешения коллизий.
Так что хэш таблица как структура тоже зависит от длины ключа.
CS>>Причем если ключи примерно такого типа: [skipped] CS>>То оверхед у hash-table по памяти будет больше.
CC>Это понятно, речь не об этом. Сравнивать его лучше с Patricia как с близким по принципу действия. CC>Впрочем вы с cranky и так уже пересеклись
CC>Когда я запхал в него и в patricia телефонный справочник (номер + фио как один ключ — все ключи уникальны) то ternary отожрал больше гига памяти. Тут просто трабл в том, что на 1 символ ключа выделяется +3*sizeof (uint). И в ситуации, когда ключи различаются в самом начале (Ex: "123 мама мыла раму" "124 мама мыла раму" "223 мама мыла раму" и т.д.) то оверхед получается максимальным — все "хвосты" ключей добавляются в nodes, а это 13 байт на char.
[skiped].
На самом деле возможны варианты.
1) Хранить хвосты после последних развилок как фрагменты строк т.е. получается 1/2 байта (char/wchar) на 1 символ.
2) Ввести ограничение на длину ключа — а дальше collision tables.
Да, еще нужно обратить внимание что алгоритм зависит от способа наполнения — если ключи
поступают в случайном порядке то дерево получается более отимальным чем в случае с "по порядку"
Здравствуйте, c-smile, Вы писали:
CS>Но сами строки в полном виде ты хранить тоже обязан для разрешения коллизий. CS>Так что хэш таблица как структура тоже зависит от длины ключа.
Верно. Упустил из виду...
CS>На самом деле возможны варианты.
CS>1) Хранить хвосты после последних развилок как фрагменты строк т.е. получается 1/2 байта (char/wchar) на 1 символ.
Верно. Но надо уже смотреть на реализацию: ведь тогда как я понимаю надо будет при вставке если возникнет необходимость эти хвосты разрезАть.
CS>2) Ввести ограничение на длину ключа — а дальше collision tables.
ИМХО это уже неправильно.
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Здравствуйте, c-smile, Вы писали:
CS>Второй вариант — воспользоваться предлагаемой имплементацией ternary tree [2]:
CS>Замечания по имплементации: CS>1) Я использую свой array который может быть легко заменен на std::vector
Попал в интересную ситуацию...
следующий код нормально отрабатывает в Debug, в релизе валится.
Компилятор VC6, VC2003 Release (/O1 или /O2)
--------------------------------------------------
Прошу прощения за столь длинный код, но валится именно на таком количестве.
Если заменить std::vector на std::deque то всё нормально.
Если удалить хотя бы одну строчку вставки ttree.insert(...); то тоже всё нормально.
Вот такие дела....
Сам не смог разобраться...
---------------------------------------------------