ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 24.09.04 20:25
Оценка: 156 (19)
Допустим у нас есть некая таблица(а.к.а. словарь) состоящая из N ключевых слов[1].
Нам нужно написать функцию типа:
  int search(const char* some_word);


которая быстро возвращает либо число из диапазона [1..N] если some_word в таблице или 0 если такого слова там нет.

Задача построения такой функции решаема несколькими вариантами, один из них например
построить т.н. minimal perfect hash function с помощью например gperf (но это возможно не на каждом наборе).

Второй вариант — воспользоваться предлагаемой имплементацией ternary tree [2]:

Класс ternary_tree кроме двух очевидных и полезных методов:

dword ternary_tree::insert(const char* s);
dword ternary_tree::search(const char* s);


имеет также бонус методы:

size_t ternary_tree::partial_match_search(const char *s, array<dword>& result_set );
size_t ternary_tree::neighbour_search(const char *s, int d, array<dword>& result_set);


partial_match_search позволяет находить множество слов из словаря удовлетворяющим шаблону.
например их словаря:

"one" "twa" "ane" "ono" "twas"

partial_match_search("?n?") найдет "one" "ane" "ono".

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


[1]слово — здесь есть конечная последовательность символов заканчивающаяся нулем)
[2]Теория ternary tree: http://home.od.ua/~relayer/algo/data/tern/lead.htm
[3]HAMMING DISTANCE: http://pespmc1.vub.ac.be/ASC/HAMMIN_DISTA.html

-------------------------------------------
source code:

namespace tool 
{
  template <typename CHAR>
    class ternary_tree
  {
    typedef unsigned int node_index;

  public:
    enum constants 
    {
        any_one_char = '?'
    };

    ternary_tree(): max_no(0) {}

    // 
    // returns number [1..max uint] of inserted or existing item.
    // does no insertion if item already exists. 
    //
    dword insert(const CHAR *s)
    {
      return (dword)nodes[insert(0, s)].eqkid;
    }

    // returns n[1..max_no] or zero if not found
    dword search(const CHAR *s) const
    {   
      node_index nid = 0; // root index;
      while (nid < total_nodes()) 
      {
         const node& n = nodes[nid];
         if (*s < n.splitchar)
             nid = n.lokid;
         else if(*s > n.splitchar)
             nid = n.hikid;
         else //(*s == p->splitchar) 
         {
            if (*s++ == 0)
                return n.value();
            nid = n.eqkid;
         } 
      }
      return 0;
    }
    // partial match search
    // e.g. 
    size_t partial_match_search(const CHAR *s, array<dword>& result_set ) const
    {
      partial_match_search(0,s,result_set);
      return result_set.size();
    }

    //
    // neighbour search 
    //
    // 'd' is Hamming distance of a query word: 
    //      A measure of the difference between two strings, 
    //      expressed by the number of characters that need to be changed to 
    //      obtain one from the other. 
    //      E.g., 0101 and 0110 has a Hamming distance of two 
    //      whereas "Butter" and "ladder" are four characters apart.
    //
    size_t neighbour_search(const CHAR *s, int d, array<dword>& result_set) const
    {
      neighbour_search(0, s, result_set, d);
      return result_set.size();
    }

 protected:
    typedef unsigned int node_index;
    
    struct node 
    {
       CHAR       splitchar;
       node_index lokid, eqkid, hikid;
       node():splitchar(0), lokid(-1), eqkid(-1), hikid(-1){}
       dword value() const 
       {
         assert(splitchar == 0);
         return (dword) eqkid;
       }
       void value(dword v)  
       {
         assert(splitchar == 0);
         eqkid = (node_index)v;
       }
    };

    array<node> nodes;
    dword       max_no;

    size_t      total_nodes() const { return (dword) nodes.size(); }
    
    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(); 
       }
       const node& n = nodes[nid];
       if (*s < n.splitchar)
          nodes[nid].lokid = insert(n.lokid, s);
       else if(*s > n.splitchar)
          nodes[nid].hikid = insert(n.hikid, s);
       else //*s == p->splitchar 
       {
          if (*s == 0)
          //"...we'll exploit the fact that a node with a 
          // null splitchar cannot have an eqkid, 
          // and store the data in that field."
              nodes[nid].value(++max_no);
          else
              nodes[nid].eqkid = insert(n.eqkid, ++s);
       }
       return nid;
    }

    //partial match search
    void partial_match_search(node_index nid, const CHAR *s, array<dword>& result_set ) const 
    {    
         if (nid >= total_nodes()) 
           return;
         const node& n = nodes[nid];
         if (*s == any_one_char || *s < n.splitchar)
            partial_match_search(n.lokid, s, result_set);
         if (*s == any_one_char || *s == n.splitchar)
            if (n.splitchar && *s)
                partial_match_search(n.eqkid, s+1, result_set);
         if (*s == 0 && n.splitchar == 0)
                result_set.push( (dword) n.value());
         if (*s == any_one_char || *s > n.splitchar)
                partial_match_search(n.hikid, s, result_set);
    }

    static int  length(const CHAR* s)
    {
      const CHAR* ps = s; while(*s) ++s; return s - ps;
    }

    void neighbour_search(node_index nid, const CHAR *s, array<dword>& result_set, int d) const
    {   
        if (nid >= total_nodes() || d < 0) return;

        const node& n = nodes[nid];      

        if (d > 0 || *s < n.splitchar)
            neighbour_search(n.lokid, s, result_set, d);
        if (n.splitchar == 0) 
        {
           if ( length(s) <= d)
              result_set.push(n.value());
        } 
        else
           neighbour_search(n.eqkid, *s ? s+1:s, result_set, (*s == n.splitchar) ? d:d-1 );
        if (d > 0 || *s > n.splitchar)
           neighbour_search(n.hikid, s, result_set, d);
    }


  };

}
Re: ternary tree / minimal perfect hash
От: korzhik Россия  
Дата: 24.09.04 20:53
Оценка: +1
Здравствуйте, c-smile, Вы писали:

CS>Второй вариант — воспользоваться предлагаемой имплементацией ternary tree [2]:


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

кстати имплементацию в boost::spirit'е не смотрели?
если интересно то путь такой boost\spirit\symbols\impl\tst.ipp
Re[2]: ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 24.09.04 21:25
Оценка:
Здравствуйте, 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).Т.е. при аллокации на хипе несколько затратно/неэффективно получается.
Re: ternary tree / minimal perfect hash
От: MaximE Великобритания  
Дата: 27.09.04 08:33
Оценка:
Здравствуйте, c-smile, Вы писали:

[]

А что за array<> такой?
Re[2]: ternary tree / minimal perfect hash
От: Dmitry A. Sinyagin www.astawireless.com
Дата: 27.09.04 09:56
Оценка: :)
ME>А что за array<> такой?
возьму на себя смелость предположить, что из c-smile
Re[2]: ternary tree / minimal perfect hash
От: korzhik Россия  
Дата: 27.09.04 10:34
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>А что за array<> такой?


c-smile

src/tool/cs_array.h
Re[3]: ternary tree / minimal perfect hash
От: korzhik Россия  
Дата: 27.09.04 10:36
Оценка:
Здравствуйте, korzhik, Вы писали:

K>Здравствуйте, MaximE, Вы писали:


ME>>А что за array<> такой?


K>c-smile


K>src/tool/cs_array.h


ну или так:

Замечания по имплементации:
1) Я использую свой array который может быть легко заменен на std::vector

Re: ternary tree / minimal perfect hash
От: Sergeem Израиль  
Дата: 27.09.04 16:11
Оценка:
Здравствуйте, c-smile, Вы писали:


CS>Допустим у нас есть некая таблица(а.к.а. словарь) состоящая из N ключевых слов[1].

CS>Нам нужно написать функцию типа:
CS>
CS>  int search(const char* some_word);
CS>


CS>которая быстро возвращает либо число из диапазона [1..N] если some_word в таблице или 0 если такого слова там нет.


У меня честно говоря не было времени вникать в сорсы и теорию, можно ли воспроизвести слово "быстро" в математических терминах типа О(х)?
И второй вопрос: может ли такое случиться, что при определенном наборе слов тернарное дерево вырождается, и какие действия предлагаются в этом случае для его балансировки?
Serge.

Hасколько проще была бы жизнь, если бы она была в исходниках.
Re[2]: ternary tree / minimal perfect hash
От: Виталий Россия  
Дата: 27.09.04 16:42
Оценка:
Здравствуйте, Sergeem, Вы писали:

CS>>которая быстро возвращает либо число из диапазона [1..N] если some_word в таблице или 0 если такого слова там нет.


S>У меня честно говоря не было времени вникать в сорсы и теорию, можно ли воспроизвести слово "быстро" в математических терминах типа О(х)?

Насколько я понимаю поиск строки длины k в дереве с n элементами займет O(log n+k) шагов в худшем случае.
Re[2]: ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 27.09.04 19:23
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>А что за array<> такой?


Примерно вот это
http://www.rsdn.ru/Forum/?mid=724485
Код этого array уже давно устарел (например сейчас сделана оптимизация под POD типы).
Здесь же приводится в качестве примера начальный вариант.
Re[2]: ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 27.09.04 19:53
Оценка:
Здравствуйте, 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) количество сравнений

(n+1) * k

что маловероятно но может случится.

Примерно так.
Re[3]: поправка
От: c-smile Канада http://terrainformatica.com
Дата: 27.09.04 20:28
Оценка:
Здравствуйте, 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


http://www.cs.caltech.edu/~shang/project/report.doc
Re[4]: поправка
От: Аноним  
Дата: 27.09.04 23:00
Оценка:
Здравствуйте, 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


CS>http://www.cs.caltech.edu/~shang/project/report.doc


Написание сбалансированного третичного дерева оставим студентам в качестве домашнего задания
Re[2]: ternary tree / minimal perfect hash
От: Dr.Gigabit  
Дата: 28.09.04 21:41
Оценка:
Здравствуйте, 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>И второй вопрос: может ли такое случиться, что при определенном наборе слов тернарное дерево вырождается, и какие действия предлагаются в этом случае для его балансировки?

По многим вопросам, и в частности по вопросу тернарного дерева(кстати, на днях сам реализовывал, поздно заглянул сюда), ИМХО, лучше обратиться к Седжвику. Там все достаточно подробно и авторитетно расписано, в том числе есть ответы и на ваши вопросы.
... << RSDN@Home 1.1.4 @@subversion >>
Re: ternary tree / minimal perfect hash
От: Dmitry521  
Дата: 14.10.04 13:12
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Класс ternary_tree кроме двух очевидных и полезных методов:


CS>
CS>dword ternary_tree::insert(const char* s);
CS>dword ternary_tree::search(const char* s);
CS>


Тестировал функцию insert она всегда возвращает 1.
Это так и задумывалось?
Re[2]: ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 15.10.04 15:39
Оценка:
Здравствуйте, Dmitry521, Вы писали:

CS>>
CS>>dword ternary_tree::insert(const char* s);
CS>>dword ternary_tree::search(const char* s);
CS>>


D>Тестировал функцию insert она всегда возвращает 1.

D>Это так и задумывалось?

Да.

Потому как 0 возвращаемый dword search(const char* s)
означает — такого слова нет.
Re: ternary tree / minimal perfect hash
От: Dr.Gigabit  
Дата: 17.10.04 20:32
Оценка: :)
Здравствуйте, c-smile, Вы писали:


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? Думаю, измерения проводились...
... << RSDN@Home 1.1.4 @@subversion >>
Re[2]: ternary tree / minimal perfect hash
От: uw  
Дата: 18.10.04 01:06
Оценка:
Здравствуйте, Dr.Gigabit, Вы писали:

Вот тот же код, слегка переделанный под std::vector, вроде работает.
#include <vector>

typedef unsigned int dword;

namespace tool 
{
  template <typename CHAR>
    class ternary_tree
  {
    typedef unsigned int node_index;

  public:
    enum constants 
    {
        any_one_char = '?'
    };

    ternary_tree(): max_no(0) {}

    // 
    // returns number [1..max uint] of inserted or existing item.
    // does no insertion if item already exists. 
    //
    dword insert(const CHAR *s)
    {
      return (dword)nodes[insert(0, s)].eqkid;
    }

    // returns n[1..max_no] or zero if not found
    dword search(const CHAR *s) const
    {   
      node_index nid = 0; // root index;
      while (nid < total_nodes()) 
      {
         const node& n = nodes[nid];
         if (*s < n.splitchar)
             nid = n.lokid;
         else if(*s > n.splitchar)
             nid = n.hikid;
         else //(*s == p->splitchar) 
         {
            if (*s++ == 0)
                return n.value();
            nid = n.eqkid;
         } 
      }
      return 0;
    }
    // partial match search
    // e.g. 
    size_t partial_match_search(const CHAR *s, std::vector<dword>& result_set ) const
    {
      partial_match_search(0,s,result_set);
      return result_set.size();
    }

    //
    // neighbour search 
    //
    // 'd' is Hamming distance of a query word: 
    //      A measure of the difference between two strings, 
    //      expressed by the number of characters that need to be changed to 
    //      obtain one from the other. 
    //      E.g., 0101 and 0110 has a Hamming distance of two 
    //      whereas "Butter" and "ladder" are four characters apart.
    //
    size_t neighbour_search(const CHAR *s, int d, std::vector<dword>& result_set) const
    {
      neighbour_search(0, s, result_set, d);
      return result_set.size();
    }

 protected:
    typedef unsigned int node_index;
    
    struct node 
    {
       CHAR       splitchar;
       node_index lokid, eqkid, hikid;
       node():splitchar(0), lokid(-1), eqkid(-1), hikid(-1){}
       dword value() const 
       {
         //assert(splitchar == 0);
         return (dword) eqkid;
       }
       void value(dword v)  
       {
         //assert(splitchar == 0);
         eqkid = (node_index)v;
       }
    };

    std::vector<node> nodes;
    dword       max_no;

    size_t      total_nodes() const { return (dword) nodes.size(); }
    
    node_index  insert(node_index nid, const CHAR *s)
    { 
       if (nid >= total_nodes()) 
       {
            node n; n.splitchar = *s;
            nodes.push_back(n);
            nid = nodes.size() - 1; 
       }
       const node& n = nodes[nid];
       if (*s < n.splitchar)
          nodes[nid].lokid = insert(n.lokid, s);
       else if(*s > n.splitchar)
          nodes[nid].hikid = insert(n.hikid, s);
       else //*s == p->splitchar 
       {
          if (*s == 0)
          //"...we'll exploit the fact that a node with a 
          // null splitchar cannot have an eqkid, 
          // and store the data in that field."
              nodes[nid].value(++max_no);
          else
              nodes[nid].eqkid = insert(n.eqkid, ++s);
       }
       return nid;
    }

    //partial match search
    void partial_match_search(node_index nid, const CHAR *s, std::vector<dword>& result_set ) const 
    {    
         if (nid >= total_nodes()) 
           return;
         const node& n = nodes[nid];
         if (*s == any_one_char || *s < n.splitchar)
            partial_match_search(n.lokid, s, result_set);
         if (*s == any_one_char || *s == n.splitchar)
            if (n.splitchar && *s)
                partial_match_search(n.eqkid, s+1, result_set);
         if (*s == 0 && n.splitchar == 0)
                result_set.push_back( (dword) n.value());
         if (*s == any_one_char || *s > n.splitchar)
                partial_match_search(n.hikid, s, result_set);
    }

    static int  length(const CHAR* s)
    {
      const CHAR* ps = s; while(*s) ++s; return s - ps;
    }

    void neighbour_search(node_index nid, const CHAR *s, std::vector<dword>& result_set, int d) const
    {   
        if (nid >= total_nodes() || d < 0) return;

        const node& n = nodes[nid];      

        if (d > 0 || *s < n.splitchar)
            neighbour_search(n.lokid, s, result_set, d);
        if (n.splitchar == 0) 
        {
           if ( length(s) <= d)
              result_set.push_back(n.value());
        } 
        else
           neighbour_search(n.eqkid, *s ? s+1:s, result_set, (*s == n.splitchar) ? d:d-1 );
        if (d > 0 || *s > n.splitchar)
           neighbour_search(n.hikid, s, result_set, d);
    }


  };

}
Re[3]: ternary tree / minimal perfect hash
От: Dmitry521  
Дата: 18.10.04 08:23
Оценка:
Здравствуйте, 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

Наблюдается очень сильная зависимость от начала слова.
Re[3]: ternary tree / minimal perfect hash
От: Dr.Gigabit  
Дата: 18.10.04 14:07
Оценка:
Здравствуйте, 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. По идее должны выводиться найденные слова
... << RSDN@Home 1.1.4 @@subversion >>
Re[5]: поправка
От: Dr.Gigabit  
Дата: 21.10.04 11:45
Оценка:
Здравствуйте, <Аноним>, Вы писали:


А>Написание сбалансированного третичного дерева оставим студентам в качестве домашнего задания



#define MAXWORDS 250000
#define MAXCHARS 3000000
char space[MAXCHARS], *a[MAXWORDS];

void rinsall(char *a[], int n)
{   int m;
    if (n < 1) return;
    m = n/2;
    insert(a[m]);  // insert - ф-ция вставки
    rinsall(a, m);
    rinsall(a + m+1, n-m-1);
}

void main()
{
   char *s = space;
    int    n = 0
    a[0] = s;
    FILE *fp = fopen("file.txt");
     while ((i = getc(fp)) != EOF) 
    {    
        if (i == '\n')
        {
            *s++ = 0;
           a[++n] = s;
       }
        else
        *s++ = i;
    }
    rinsall(a, n);    
}


... << RSDN@Home 1.1.4 @@subversion >>
Re[4]: ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 24.10.04 23:08
Оценка:
Здравствуйте, Dmitry521, Вы писали:

D>Мне кажется она неверно считает HAMMING DISTANCE.



D>woman, man, many, wman, mwan

D>искал слово man.
D>результаты
D>d = 1 man, many Я считаю должно было быть — man, many, mwan, wman

По определению hamming distance
wman ^ man = 4 и
mwan ^ man = 3

Все вроде правильно. Для целей spell check имхо надо еще SOUNDEX
на result_set напускать.
Re[2]: ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 24.10.04 23:22
Оценка:
Здравствуйте, Dr.Gigabit, Вы писали:

DG>Здравствуйте, c-smile, Вы писали:


DG>
DG> node_index  insert(node_index nid, const CHAR *s)
DG>    { 
DG>       if (nid >= total_nodes()) 
DG>       {
DG>            node n; n.splitchar = *s;
DG>            nodes.push(n);
DG>            nid = nodes.last_index();        // здесь ошибка
DG>         }
DG>


В моем array метод last_index() возвращает int — последний валидный индекс в массиве или -1.
node_index это unsigned int; должно приводится без сайдэффектов здесь.


DG>Может быть подскажете, что не так делаю?

DG>И еще вопрос параллелльно : на сколько ваш array по быстродействию выигрывает у vector? Думаю, измерения проводились...

Мой массив в его ьекущей инкарнации работает с абсолютно той же скоростью что и std::vector.

В ситуациях например типа array< smart_ptr<something> > мой работает на insert/append быстрее. Иногда значительно. Все мои классы позиционно независимы поэтому я могу себе позволить memcpy.
Что требует определенной дисциплины. Поэтому я его и не выкладываю.
Re[3]: ternary tree / minimal perfect hash
От: Dr.Gigabit  
Дата: 24.10.04 23:56
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Здравствуйте, Dr.Gigabit, Вы писали:


DG>>Здравствуйте, c-smile, Вы писали:


DG>>
DG>> node_index  insert(node_index nid, const CHAR *s)
DG>>    { 
DG>>       if (nid >= total_nodes()) 
DG>>       {
DG>>            node n; n.splitchar = *s;
DG>>            nodes.push(n);
DG>>            nid = nodes.last_index();        // здесь ошибка
DG>>         }
DG>>


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 часа ночи не думается)

    tool::array<unsigned int> resultSet;
    TST.insert("andy");
    TST.neighbour_search("aldy",1,resultSet);
    cout << resultSet[0] << endl;


Выводит 1. Каким образом слова отображать-то?
В своей реализации TST я использую vector<char*> resultSet;
А как с dword'ом быть?
... << RSDN@Home 1.1.4 @@subversion >>
Re[4]: ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 26.10.04 00:07
Оценка:
Здравствуйте, 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'ом быть?


class lookup {

   ternary_tree<char> index;
   vector<string>     values; 

   void insert(const char* s)
   {
     int i = index.insert(s) - 1;
     if(i >= values.size()) 
     {
       assert(i == values.size()); 
       values.push_back(s);
     }
   }
   string get_value_by_id(int idx) const { return (idx > 0)? values[idx-1] : "{unknown}"; }
};
Re: ternary tree / minimal perfect hash
От: Аноним  
Дата: 26.11.04 21:13
Оценка:
Здравствуйте, c-smile, Вы писали:


CS>Второй вариант — воспользоваться предлагаемой имплементацией ternary tree [2]:

[skiped]

Не могли бы вы прояснить такую вещь:
При записи всех узлов в бинарный файл, количество записываемых узлов 107184
(значение, возвращаемое total_nodes() ). Но в одном из узлов lokid имеет значение 4294967295

В то же время при проецировании бинарного файла, в который предварительно записаны все узлы, в память,
их количество 3435973836 (проверяю в цикле, пока указатель не NULL)

Как locid может иметь значение, большее кол-ва узлов? Или я где-то не прав?

Спасибо.
Re: ternary tree / minimal perfect hash
От: CreatorCray  
Дата: 07.09.05 21:40
Оценка: 1 (1) +1
Здравствуйте, c-smile, Вы писали:

Есть серьезный недостаток — требовательность к памяти. Чем длиннее ключ — тем больше хавает. А если ключи еще и различаются в начале — вообще абзац.
Проверял практическим способом
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[2]: ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 08.09.05 03:37
Оценка:
Здравствуйте, CreatorCray, Вы писали:

CC>Здравствуйте, c-smile, Вы писали:


CC>Есть серьезный недостаток — требовательность к памяти. Чем длиннее ключ — тем больше хавает. А если ключи еще и различаются в начале — вообще абзац.


Ну в общем и целом утверждение "требовательность к памяти" справедливо для любой конструкции такого рода.

Для hash tables нужно тоже где-то ключи хранить.
Причем если ключи примерно такого типа:

"Маша мыла Раму"
"Маша мыла Мессершмит"
"Маша мыла Вову"

То оверхед у hash-table по памяти будет больше.
Re[3]: ternary tree / minimal perfect hash
От: CreatorCray  
Дата: 08.09.05 10:38
Оценка: 1 (1)
Здравствуйте, 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, значит пора закрыть эту страницу.
Всем пока
Re[4]: ternary tree / minimal perfect hash
От: c-smile Канада http://terrainformatica.com
Дата: 08.09.05 15:56
Оценка:
Здравствуйте, 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.

Да, еще нужно обратить внимание что алгоритм зависит от способа наполнения — если ключи
поступают в случайном порядке то дерево получается более отимальным чем в случае с "по порядку"
Re[5]: ternary tree / minimal perfect hash
От: CreatorCray  
Дата: 08.09.05 21:59
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Но сами строки в полном виде ты хранить тоже обязан для разрешения коллизий.

CS>Так что хэш таблица как структура тоже зависит от длины ключа.
Верно. Упустил из виду...

CS>На самом деле возможны варианты.


CS>1) Хранить хвосты после последних развилок как фрагменты строк т.е. получается 1/2 байта (char/wchar) на 1 символ.

Верно. Но надо уже смотреть на реализацию: ведь тогда как я понимаю надо будет при вставке если возникнет необходимость эти хвосты разрезАть.

CS>2) Ввести ограничение на длину ключа — а дальше collision tables.

ИМХО это уже неправильно.
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re: ternary tree / minimal perfect hash
От: korzhik Россия  
Дата: 21.03.06 21:53
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Второй вариант — воспользоваться предлагаемой имплементацией ternary tree [2]:


CS>Замечания по имплементации:

CS>1) Я использую свой array который может быть легко заменен на std::vector

Попал в интересную ситуацию...
следующий код нормально отрабатывает в Debug, в релизе валится.
Компилятор VC6, VC2003 Release (/O1 или /O2)
--------------------------------------------------
Прошу прощения за столь длинный код, но валится именно на таком количестве.
Если заменить std::vector на std::deque то всё нормально.
Если удалить хотя бы одну строчку вставки ttree.insert(...); то тоже всё нормально.
Вот такие дела....
Сам не смог разобраться...
---------------------------------------------------
//-------------------------------------------------
#include <vector>
#include <cassert>
//------------------------------------------------
namespace tool 
{
    typedef unsigned int dword;

    template <typename CHAR>
    class ternary_tree
    {
        typedef unsigned int node_index;
    public:
        ternary_tree(): max_no(0) {}

        dword insert(const CHAR *s)
        {
            return (dword)nodes[insert(0, s)].eqkid;
        }

        // returns n[1..max_no] or zero if not found
        dword search(const CHAR *s) const
        {   
            node_index nid = 0; // root index;
            while (nid < total_nodes()) 
            {
                const node& n = nodes[nid];
                if (*s < n.splitchar)
                    nid = n.lokid;
                else if(*s > n.splitchar)
                    nid = n.hikid;
                else //(*s == p->splitchar) 
                {
                    if (*s++ == 0)
                        return n.value();
                    nid = n.eqkid;
                } 
            }
            return 0;
        }

    protected: 
        struct node 
        {
            CHAR       splitchar;
            node_index lokid;
            node_index eqkid;
            node_index hikid;

            node() : splitchar(0), lokid(-1), eqkid(-1), hikid(-1) {}

            dword value() const 
            {
                assert(splitchar == 0);
                return (dword)eqkid;
            }
            void value(dword v)  
            {
                assert(splitchar == 0);
                eqkid = (node_index)v;
            }
        };

        std::vector<node> nodes;
        dword             max_no;

        size_t     total_nodes() const { return nodes.size(); }

        node_index insert(node_index nid, const CHAR *s)
        { 
            if (nid >= total_nodes()) 
            {
                node n; n.splitchar = *s;
                nodes.push_back(n);
                nid = nodes.size() - 1; 
            }
            const node& n = nodes[nid];
            if (*s < n.splitchar)
                nodes[nid].lokid = insert(n.lokid, s);
            else if(*s > n.splitchar)
                nodes[nid].hikid = insert(n.hikid, s);
            else //*s == p->splitchar 
            {
                if (*s == 0)
                //"...we'll exploit the fact that a node with a 
                // null splitchar cannot have an eqkid, 
                // and store the data in that field."
                    nodes[nid].value(++max_no);
                else
                    nodes[nid].eqkid = insert(n.eqkid, ++s);
            }
            return nid;
         }

        static int length(const CHAR* s)
        {
            const CHAR* ps = s; while(*s) ++s; return s - ps;
        }
    };
}

int main()
{
    tool::ternary_tree<char> ttree;

    ttree.insert("accent-height");
    ttree.insert("accumulate");
    ttree.insert("alignment-baseline");
    ttree.insert("alphabetic");
    ttree.insert("amplitude");
    ttree.insert("animate");
    ttree.insert("arabic-form");
    ttree.insert("ascent");
    ttree.insert("attributeType");
    ttree.insert("azimuth");
    ttree.insert("baseFrequency");
    ttree.insert("baseProfile");
    ttree.insert("baseline-shift");
    ttree.insert("bbox");
    ttree.insert("bias");
    ttree.insert("by");
    ttree.insert("calcMode");
    ttree.insert("cap-height");
    ttree.insert("class");
    ttree.insert("clip");
    ttree.insert("clip-path");
    ttree.insert("clip-rule");
    ttree.insert("clipPathUnits");
    ttree.insert("color");
    ttree.insert("color-interpolation");
    ttree.insert("color-interpolation-filters");
    ttree.insert("color-profile");
    ttree.insert("color-rendering");
    ttree.insert("contentScriptType");
    ttree.insert("contentStyleType");
    ttree.insert("cursor");
    ttree.insert("cx");
    ttree.insert("cy");
    ttree.insert("d");
    ttree.insert("descent");
    ttree.insert("diffuseConstant");
    ttree.insert("direction");
    ttree.insert("display");
    ttree.insert("divisor");
    ttree.insert("dominant-baseline");
    ttree.insert("dur");
    ttree.insert("dx");
    ttree.insert("dy");
    ttree.insert("edgeMode");
    ttree.insert("elevation");
    ttree.insert("enable-background");
    ttree.insert("end");
    ttree.insert("exponent");
    ttree.insert("externalResourcesRequired");
    ttree.insert("feColorMatrix");
    ttree.insert("feComposite");
    ttree.insert("feGaussianBlur");
    ttree.insert("feMorphology");
    ttree.insert("feTile");
    ttree.insert("fill");
    ttree.insert("fill-opacity");
    ttree.insert("fill-rule");
    ttree.insert("filter");
    ttree.insert("filterRes");
    ttree.insert("filterUnits");
    ttree.insert("flood-color");
    ttree.insert("flood-opacity");
    ttree.insert("font-family");
    ttree.insert("font-size");
    ttree.insert("font-size-adjust");
    ttree.insert("font-stretch");
    ttree.insert("font-style");
    ttree.insert("font-variant");
    ttree.insert("font-weight");
    ttree.insert("format");
    ttree.insert("from");
    ttree.insert("fx");
    ttree.insert("fy");
    ttree.insert("g1");
    ttree.insert("glyph-name");
    ttree.insert("glyph-orientation-horizontal");
    ttree.insert("glyph-orientation-vertical");
    ttree.insert("glyphRef");
    ttree.insert("gradientTransform");
    ttree.insert("gradientUnits");
    ttree.insert("hanging");
    ttree.insert("height");
    ttree.insert("horiz-adv-x");
    ttree.insert("horiz-origin-y");
    ttree.insert("id");
    ttree.insert("ideographic");
    ttree.insert("image-rendering");
    ttree.insert("in");
    ttree.insert("in2");
    ttree.insert("intercept");
    ttree.insert("k");
    ttree.insert("k1");
    ttree.insert("k2");
    ttree.insert("k3");
    ttree.insert("k4");
    ttree.insert("kernelMatrix");
    ttree.insert("kernelUnitLength");
    ttree.insert("kerning");
    ttree.insert("keyPoints");
    ttree.insert("keySplines");
    ttree.insert("keyTimes");
    ttree.insert("lang");
    ttree.insert("lengthAdjust");
    ttree.insert("letter-spacing");
    ttree.insert("lighting-color");
    ttree.insert("limitingConeAngle");
    ttree.insert("local");
    ttree.insert("marker");
    ttree.insert("marker-end");
    ttree.insert("marker-mid");
    ttree.insert("marker-start");
    ttree.insert("markerHeight");
    ttree.insert("markerUnits");
    ttree.insert("markerWidth");
    ttree.insert("mask");
    ttree.insert("maskContentUnits");
    ttree.insert("maskUnits");
    ttree.insert("mathematical");
    ttree.insert("max");
    ttree.insert("media");
    ttree.insert("method");
    ttree.insert("min");
    ttree.insert("mode");
    ttree.insert("name");
    ttree.insert("numOctaves");
    ttree.insert("offset");
    ttree.insert("opacity");
    ttree.insert("operator");
    ttree.insert("order");
    ttree.insert("orient");
    ttree.insert("orientation");
    ttree.insert("origin");
    ttree.insert("overflow");
    ttree.insert("overline-position");
    ttree.insert("overline-thickness");
    ttree.insert("panose-1");
    ttree.insert("path");
    ttree.insert("pathLength");
    ttree.insert("patternContentUnits");
    ttree.insert("patternTransform");
    ttree.insert("patternUnits");
    ttree.insert("pointer-events");
    ttree.insert("points");
    ttree.insert("pointsAtX");
    ttree.insert("pointsAtY");
    ttree.insert("pointsAtZ");
    ttree.insert("preserveAlpha");
    ttree.insert("preserveAspectRatio");
    ttree.insert("primitiveUnits");
    ttree.insert("r");
    ttree.insert("radius");
    ttree.insert("refX");
    ttree.insert("refY");
    ttree.insert("rendering-intent");
    ttree.insert("repeatCount");
    ttree.insert("repeatDur");
    ttree.insert("requiredExtensions");
    ttree.insert("restart");
    ttree.insert("result");
    ttree.insert("rotate");
    ttree.insert("rx");
    ttree.insert("ry");
    ttree.insert("scale");
    ttree.insert("seed");
    ttree.insert("shape-rendering");
    ttree.insert("slope");
    ttree.insert("spacing");
    ttree.insert("specularConstant");
    ttree.insert("specularExponent");
    ttree.insert("spreadMethod");
    ttree.insert("startOffset");
    ttree.insert("stdDeviation");
    ttree.insert("stemh");
    ttree.insert("stemv");
    ttree.insert("stitchTiles");
    ttree.insert("stop-color");
    ttree.insert("stop-opacity");
    ttree.insert("strikethrough-position");
    ttree.insert("strikethrough-thickness");
    ttree.insert("stroke");
    ttree.insert("stroke-dasharray");
    ttree.insert("stroke-dashoffset");
    ttree.insert("stroke-linecap");
    ttree.insert("stroke-linejoin");
    ttree.insert("stroke-miterlimit");
    ttree.insert("stroke-opacity");
    ttree.insert("stroke-width");
    ttree.insert("style");
    ttree.insert("surfaceScale");
    ttree.insert("systemLanguage");
    ttree.insert("tableValues");
    ttree.insert("target");
    ttree.insert("targetX");
    ttree.insert("targetY");
    ttree.insert("text-anchor");
    ttree.insert("text-decoration");
    ttree.insert("text-rendering");
    ttree.insert("textLength");
    ttree.insert("title");
    ttree.insert("to");
    ttree.insert("transform");
    ttree.insert("type");
    ttree.insert("u1");
    ttree.insert("u2");
    ttree.insert("underline-position");
    ttree.insert("underline-thickness");
}
Re: ternary tree / minimal perfect hash
От: Аноним  
Дата: 02.10.06 17:08
Оценка: 2 (1)
msvc.net 2003 с оптимизацией включенной вылетает с покорупченным хипом.

я подозреваю что дело в


nodes[nid].hikid = insert(n.hikid, s);


компилятор падклюка сперва вычисляет адрес nodes[nid].hikid а потом делает insert, кусок памяти пеезжает и оно падает.
заменил на

 node_index i = insert(n.lokid, s);
 nodes[nid].lokid = i;


стало работать.. кто не прав -- c-smile или компилятор? или это у меня что-то не так?