Форум программистов, компьютерный форум, киберфорум
villu
Войти
Регистрация
Восстановить пароль
Оценить эту запись

А не написать ли тут что? Про префиксное дерево и язык C

Запись от villu размещена 09.06.2014 в 19:05
Обновил(-а) villu 13.06.2014 в 23:56

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

Вот например раскраска синтаксиса?...
Что, если нам нужно вывести на консоль файл и разукрасить (подсветить некоторые слова).
откроем man console_codes ( например есть тут http://linux.die.net/man/4/console_codes )
Видим, что чтобы вывести цветную строку на экран, нужно вывести некоторый префикс. Вдаваться в детали этого префикса я не буду, но если коротко, то это строка вида
Код:
\x1b[XX;YY;ZZm
Где XX - атрибуты текста (жирный, наклонный...) XX < 10
YY - цвет текста 30 < YY < 40
ZZ - цвет фона 40 < ZZ < 50
На самом деле там есть допустимые значение, но это не важно.

Важно то, что, например синяя строка выводится как:
Код:
echo -e "\x1b[34mtest"
Будет выведено синее слово "test"
Чтоб вернуть все как было (дефолтная консоль) нужно вывести "\x1b[0m"

и того
echo -e "\x1b[34mblue\x1b[0m"
echo -e "\x1b[33myelloe\x1b[0m"
echo -e "\x1b[32mgreen\x1b[0m"
увидим на консоле
blue
yellow
green

А теперь задание: Есть файл, нужно вывести его на консоль и все встречающиеся названия цветов покрасить в соответствующий цвет.

Самый простой способ -- это просто искать в строке (содержимое файла) нужное слово, и "обкладывать" его префиксом и суффиксом...работает, то не так быстро, особенно, если слов достаточно много.

И так: у нас есть небольшой (или большой файл) нужно быстро пройтись по нему и вывести все перечисленные слова в цвете (дописать префикс и суффикс).

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

Что такое префиксное дерево. Это такая структура данных, для хранения ассоциативного набора данных со строкой (а точнее некоторой последовательностью) в качестве ключа. По поводу этого контейнера можно просветится в Wiki (http://ru.wikipedia.org/wiki/%... 0%B2%D0%BE либо http://en.wikipedia.org/wiki/Trie как мне кажется более развернуто)

Итого. Нам нужно дерево, которое будет хранить последовательность символов (строку) в качестве ключевой информации и некоторое значение префикса в качестве данных. Реализацию самого дерева, как и весь исзодник примера можно получить тут: https://github.com/newenclave/... -tree-test

Файл prefix-tree.h содержит описание интерфейса дерева
prefix-tree.с содержит реализацию

Дополнительно включены mm-block - реализация динамического блока памяти. (можно сравнить с std::vector<char>)
mm-array - обертка над mm-block но уже работает с элементами (определенного размера ), кроме того умеет применять функцию удаления для каждого элемента при удалении всего контейнера и функцию копирования при операциях копирования. В общем ничего сверхъестественного.
+ ко всему mm-array умеет двоичный поиск и двоичную вставку, которой пользуется реализация prefix-tree. На самом деле prefix-tree хранит указатели на элементы в отсортированном виде. Сортировка по значению элемента ключа (key_);

В эти элементы входят:
  1. один символ символ последовательности
  2. флаги текущего элемента
  3. нагрузка; Указатель на пользовательские данные
  4. указатель на следующий массив с ключами
  5. указатель на главный корень дерева

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

C
1
2
3
4
5
6
7
struct pt_key_info {
    union pt_key_type key_;
    unsigned short flags_;
    struct prefix_tree *parent_;
    void *data_;
    struct mm_array *next_keys_;
};
Интерфейс дерева довольно прост:
C
1
2
struct prefix_tree *prefix_tree_new( );
struct prefix_tree *prefix_tree_new2( prefix_tree_data_free free_call );
Создает экземпляр дерева; вызов prefix_tree_new2 принимает функцию для удаления пользовательских данных.

C
1
2
3
void prefix_tree_free( struct prefix_tree *pt );
void prefix_tree_free2( struct prefix_tree *pt,
                          prefix_tree_data_free free_call);
Удаляет дерево. В prefix_tree_free2, опять же, можно указать функцию для удаления. Если она не указана, будет использована та, что была передана в prefix_tree_new2 или в prefix_tree_set_free. Если и там не было - удаляться элементы не будут (ну а вдруг там просто int какой; зачем его удалять).

вызов
C
1
2
void *prefix_tree_get_next( const struct prefix_tree *pt,
                            const void **stream, size_t *length );
принимает указатель на указатель потока, и указатель на размер входящего потока (это же не обязательно может быть строка, которая закончится '\0'); В случае, если вызов обнаружит в потоке один из ключей, он:
  • передвинет указатель на длину этого ключа
  • уменьшит length на длину ключа
Если же не обнаружит, то все оставит как есть.


Ну и наконец:

C
1
2
3
4
5
int prefix_tree_insert( struct prefix_tree *pt,
                          const void *key, size_t length, void *data );
 
int prefix_tree_insert_string( struct prefix_tree *pt,
                          const char *key_string, void *data );
Вызовы, которые добавляют ключи с данными. Второй вариант добавляет строку (до '\0'); Если ключ уже встречается в дереве, то старое значение будет удалено (применен вызов, который передан при создании ) и заменено новыми данными.

В main.c: описан массив с парами "цвет"->"префикс"
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static struct {
 
    const char *name_;
    const char *value_;
 
} const global_colors[ ] = {
 
      { "black", "\x1b[30;1m" }
     ,{ "red", "\x1b[31;1m" }
     ,{ "green", "\x1b[32;1m" }
     ,{ "yellow", "\x1b[1;33m" }
     ,{ "orange", "\x1b[33;1m" }
     ,{ "brown", "\x1b[0;33m" }
     ,{ "blue", "\x1b[34;1m" }
     ,{ "purple", "\x1b[35;1m" }
     ,{ "lightblue", "\x1b[36;1m" }
     ,{ "white", "\x1b[37;1m" }
     ,{ "white", "\x1b[37;1m" }
 
     ,{ "Black", "\x1b[30;1m" }
     ,{ "Red", "\x1b[31;1m" }
     ,{ "Green", "\x1b[32;1m" }
     ,{ "Yellow", "\x1b[1;33m" }
     ,{ "Orange", "\x1b[33;1m" }
     ,{ "Brown", "\x1b[0;33m" }
     ,{ "Blue", "\x1b[34;1m" }
     ,{ "Purple", "\x1b[35;1m" }
     ,{ "Lightblue", "\x1b[36;1m" }
     ,{ "White", "\x1b[37;1m" }
};
 
static const size_t global_count =
             sizeof(global_colors) / sizeof(global_colors[0]);
В качестве данных в дереве будут указатель на структуру:
C
1
2
3
4
struct value_data {
    const char *color_ptr_; // указатель на префикс
    size_t length_;          /// дина префикса
};
Далее создаем дерево:
C
1
2
3
4
5
6
7
    // указываем free в качества функции-дестркутора
    struct prefix_tree *trie = prefix_tree_new2( free ); 
 
    if( !trie ) {
        fprintf( stderr, "Create trie failed\n" );
        return 1;
    }
Заполняем дерево:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int fill_trie( struct prefix_tree *trie )
{
    size_t i;
    int result = 1;
 
    for( i=0; i<global_count && result; ++i ) {
       // новое значение с цветом
       /// На самом деле, так как всё константное, 
       /// можно просто создавать константный массив таких 
       /// структурок и просто отдавать указатели на него в качестве данных. 
       /// Но динамически интереснее и нагляднее имхо. 
 
        struct value_data *value = create_data( global_colors[i].value_ ); 
        if( value ) { 
            // создано, добавим
            prefix_tree_insert_string( trie, global_colors[i].name_, value ); 
        } else {
            result = 0; // ошибка. выйдем
        }
    }
    return result;
}
Читаем входящий поток и выводим найденный ключ своим цветом:
C
1
2
3
4
5
        char block[4096 + 1];
 
        while( fgets( block, 4096, stdin ) && res ) {
            res = print_colored_line( trie, &block[0] );
        }
и сама функция поиска и вывода.
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
int print_colored_line( const struct prefix_tree *trie, const char *line )
{
    /// вместо tmp_str можно использовать обычный char[N]
    /// Но mm_block динамически расширяется, если ему не хватает места
    struct mm_block *tmp_str = mm_block_new_reserved( 128 ); // начнем со 128 байт
    int result = 1;
 
    const size_t none_length = strlen(cp_none); 
 
    if( !tmp_str ) { 
        fprintf( stderr, "Create mm_block failed\n" );
        return 0;
    }
 
    // получим длину строки, поскольку длину требует prefix_tree_get_next
    const char *p = line;
    size_t len = strlen( p );
 
    while ( len != 0 ) { /// пока длина не закончилась
 
        /// запоминаем старый указатель, 
        /// поскольку алгоритм поиска передвинет текущий 
        const char *old_p = p; 
 
       /// поиск
        struct value_data *value = (struct value_data *)
                prefix_tree_get_next( trie, (const void **)&p, &len );
 
        if( value ) { /// нашли ключ; Добавим к tmp_str: prefix, ключ, постфикс ("\x1b[0m")
 
            /// по-хорошему тут нужна проверка результата mm_block_append, 
            /// потому как он может выделять память, а система ему отказать в этом
 
            /// префикс из данных 
            mm_block_append( tmp_str, value->color_ptr_, value->length_ );
 
            /// сам ключ; длина - разница между указателями
            mm_block_append( tmp_str, old_p, p - old_p ); 
 
            /// постфикс
            mm_block_append( tmp_str, cp_none, none_length ); 
 
       } else {
            mm_block_push_back( tmp_str, *p++ ); /// ничего не нашли, перейдем дальше
            --len; // и длину тоже уменьшим
        }
    }
 
    /// добавим 0 в конец блока
    mm_block_push_back( tmp_str, '\0' );
   
    /// выведем блок, как строку; 
    /// mm_block_const_begin вернет указатель на первый элемент результата
    fputs( (const char *)mm_block_const_begin( tmp_str ), stdout );
 
    /// не забыли освободить
    mm_block_free( tmp_str );
 
    return result;
}
В конце main.c удалим дерево
C
1
    prefix_tree_free( trie );
Будут удалены все узлы, а так же данные функцией free, которая указана при создании дерева.

Все ....
В качестве примера есть файлик c песенкой про цвета https://github.com/newenclave/... colors.txt
И вот результат выполнения https://raw.githubusercontent.... output.png (как тут вообще картинки вставлять?)

А дальше можно немного модифицировать дерево, чтоб оно могло принимать любые последовательности (в качестве ключа), предоставить функции для сравнения элементов этих последовательностей, функции для "решения" о переходе на следующий элемент входящих данных...Так можно получить некое подобие регулярных выражений. Но это уже за рамками.

Надеюсь кому-нибудь пригодится.
Миниатюры
Нажмите на изображение для увеличения
Название: output.png
Просмотров: 624
Размер:	36.4 Кб
ID:	2450  
Размещено в Без категории
Показов 2517 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2023, CyberForum.ru