2010-10-31 3 views
6

Я пытаюсь tokenize строки. У меня есть таблица доступных токенов, упорядоченных в виде trie. Каждый токен знает, что у него есть дети. Простая таблица лексем будет выглядеть,Эффективный способ tokenize строки - C

pattern value   has_children 
-------- ------  -------- 
s   s-val   1 
stack  stack-val  0 
over  over-val  1 
overflow overflow-val 0 

В этой таблице stack является дочерним s и overflow является потомком over. На практике эта таблица будет иметь 5000+ записей, упорядоченных таким образом.

Теперь, учитывая строку stackover, она должна выводить stack-valover-val. Алгоритм жадный, и он попытается найти самый длинный матч всегда.

Для этого я начну читать каждый символ из ввода, искать совпадение, если совпадение найдено, а токен имеет дочерние элементы, снова найдите совпадение, включив следующий символ. Сделайте это, пока мы не найдем самый длинный матч. Если совпадение не найдено, попробуйте выполнить сопоставление, включив следующий символ, пока мы не достигнем конца строки или успешного совпадения.

Если мы достигли конца строки без соответствия, выведите символ ? и удалите первый символ со входа. Повторите весь процесс с оставшимися символами.

Этот алгоритм работает, но откат и повторение всех возможных комбинаций входа делает его медленным и сложным.

Мне интересно, есть ли лучший способ решить это? Любая помощь будет оценена по достоинству.

+0

Почему я отслеживаю детей: если я нашел совпадение и искал самый длинный матч, мне не нужно это делать, если у токена нет детей. Это повышает эффективность. –

ответ

2

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

токены: S СТЕК StackOverflow STAG НАД OVERFLOW
Строка: SSTACKOVERFUN

1 - Найдено S на месте 0, есть маркеры, которые начинаются с S, попробовать их все, только S является допустимым, так решительность S
2 - S на 1, есть такие жетоны, попробуйте их, допустимыми являются S и STACK. Не решайте, просто имейте это в виду.
3 - T на 2, не имеют таких жетонов, поэтому S может быть разрешен сейчас, но у нас также есть более длинный токен (STACK), поэтому S не годится. Ditch S, и STACK остается только, но у него есть дети. Попробуйте строку для детей.Там нет возможных детей так Решимость STACK
4 - O 6, имеют такие маркеры, попробовать их, только OVER, так решительность НАД
5 - F на 10, нет таких лексем, и ничего не решить от раньше, так что это не-tokenizable
6 и 7 - то же, шаг 5

Конечный результат: S стопке весело

1

Не могли бы вы использовать алгоритм Aho-Corasick? Он создает автомат для поиска дерева ключевых слов (trie).

1

Я думаю, что вы хотите взять все ваши ключевые слова и отсортировать их в алфавитном порядке обратном, так что ваш список стал бы (плюс несколько дополнительных услуг)

0 stack  1 
1 s   0 
2 overflow 3 
3 over  5 
4 ovum  5 
5 o   0 
6 exchange 7 
7 ex   0 

Третий столбец этого списка являются указателями родительский токен, который всегда ниже в списке. Затем вы можете взять целевую строку и бинарный поиск, где она подходит в этом списке. Если он приземляется над маркером, который соответствует, то вы снимаете эту часть и повторяете процесс для остатка. Если он не соответствует, вы используете указатель родителя, чтобы найти следующий самый длинный потенциальный соответствующий токен.

Если вы хотите получить действительно фантазию, вы также можете разбить строки на 64-битные слова и сравнить 8 символов сразу в двоичном поиске.

1

Я предлагаю вам попробовать Ragel, он может генерировать эффективные сканеры, которые могут сделать самый длинный матч/возвраты. См. Главу 6.3 в Ragel user guide для получения дополнительной информации.

Я создал крошечный тест, который я думаю, что соответствует вашей спецификации, это только состояние описание машины, без кода, чтобы кормить вход:

%%{ 
machine test; 

main := |* 
's' => { puts("s-val");}; 
'stack' => { puts("stack-val");}; 
'over' => { puts("over-val");}; 
'overflow' => { puts("overflow-val");}; 

# Anything else matches to any, outputs a '?' and continues 
any => {putc('?');}; 
*|; 
}%% 
0

The following token_tree code is based on the prefix_tree class from ZeroMQ

Класс prefix_tree только возвращает «true», когда один из префиксов дерева соответствует началу ввода текста. Он даже не скажет вам, какой префикс или как долго этот префикс.

Это token_tree будет искать самый длинный токен, который соответствует началу ввода текста. Поиск функции token_tree_longest_token() требует только вернуть длину самого длинного маркера против начала ввода текста.

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

Также есть несколько способов улучшить использование памяти, которая может ускорить ее работу.

#include <stdint.h> 
#include <stdlib.h> 

/* #define TEST_TOKEN_TREE */ 
/* 
* TODO: possible improvements, use multiple types of nodes: string/branch/leaf. 
* The string node would replace a chain of normal token_nodes and save memory. 
* This would require spliting a node to add branch points. 
* Use these structs: 
* struct token_node { 
* uint32_t ref_count; 
* uint8_t node_type; -- node is token_node_str/token_node_branch/token_node_leaf 
* }; 
* struct token_node_str { 
* token_node base; 
* uint8_t reserved; 
* uint16_t len;   -- string length 
* token_node *child;  -- string nodes can only have one child. 
* uint8_t str[0];  -- embedded string (not null-terminated) 
* }; 
* struct token_node_branch { 
* token_node base; 
* uint8_t min;   -- smallest char in child list. 
* uint16_t count;   -- child count. 
* token_node *children[0]; 
* }; 
* struct token_node_leaf { -- leaf nodes have no children. 
* token_node base; 
* }; 
* This will save memory, but will make code much more complex. 
*/ 

typedef struct token_tree token_tree; 
typedef struct token_node token_node; 

struct token_tree { 
    token_node *root; /**< root node of token tree. */ 
}; 

struct token_node { 
    uint32_t ref_count; /**< how many token references end at this node. */ 
    uint8_t min;   /**< smallest 'char' in children's list. */ 
    uint8_t reserved;  /**< padding. */ 
    uint16_t count;  /**< number of children. (max count = 256, so count must be 16bits) */ 
    token_node *children[0]; /**< list of children nodes. index by (c - min) */ 
}; 

#define NODE_SIZE(count) (sizeof(token_node) + (sizeof(token_node *) * count)) 

static token_node *token_node_new(uint16_t count) { 
    token_node *node = calloc(1, NODE_SIZE(count)); 
    node->count = count; 
    return node; 
} 

static void token_node_build_chain(token_node **pnode, const uint8_t *token, size_t len) { 
    token_node *node; 
    do { 
     /* the last node in the chain will have no children. */ 
     node = token_node_new((len == 0) ? 0 : 1); 
     *pnode = node; /* add node to slot in parent's children list. */ 
     if(len == 0) break; 
     /* new node will have one child. */ 
     node->min = *token; 
     node->count = 1; 
     /* slot where next node will be saved. */ 
     pnode = &(node->children[0]); 
     /* consume char. */ 
     token++; 
     len--; 
    } while(1); 
    /* mark last node as end of a valid token. */ 
    node->ref_count++; 
} 

static void token_node_free(token_node *node) { 
    uint32_t i; 
    uint32_t count = node->count; 

    /* free children nodes. */ 
    for(i=0; i < count; i++) { 
     if(node->children[i]) token_node_free(node->children[i]); 
    } 
    free(node); 
} 

static void token_node_grow(token_node **pnode, uint8_t c) { 
    token_node *node = *pnode; 
    token_node **children; 
    uint8_t old_min = node->min; 
    uint16_t old_count = node->count; 
    uint32_t i; 
    uint8_t min; 
    uint16_t count; 

    if(c < old_min) { 
     min = c; 
     count = old_count + (old_min - min); 
    } else { 
     if(old_count == 0) { 
      /* the list was empty, so this is the first char. */ 
      old_min = c; 
     } 
     min = old_min; 
     c -= old_min; 
     if(c < old_count) { 
      /* don't need to grow. */ 
      return; 
     } 
     count = c + 1; 
    } 

    node = realloc(node, NODE_SIZE(count)); 
    *pnode = node; 
    children = node->children; 
    /* if the 'min' value changed, then we need to move all the old slots up. */ 
    if(old_min != min) { 
     uint32_t diff = old_min - min; 
     for(i=count-1; i >= diff; i--) { 
      children[i] = children[i - diff]; 
     } 
     /* null new slots at start of children list. */ 
     for(i=0; i < diff; i++) { 
      children[i] = NULL; 
     } 
    } else { 
     /* null new slots at end of children list. */ 
     for(i=old_count; i < count; i++) { 
      children[i] = NULL; 
     } 
    } 
    node->min = min; 
    node->count = count; 
} 

static token_node **token_node_find_last_node(token_node **pnode, const uint8_t **ptoken, size_t *plen) { 
    const uint8_t *token = *ptoken; 
    size_t len = *plen; 
    uint32_t c; 
    token_node *node = *pnode; 

    while(node && len) { 
     /* next char. */ 
     c = (*token); 
     /* if c < node->min, then it will underflow and be > node->count. */ 
     c -= node->min; 
     /* make sure c is in range. */ 
     if(c >= node->count) { 
      /* 
      * NOTE: we don't consume this char and "*pnode" will not be null. 
      * When adding tokens, this node will be grown to hold more children. 
      */ 
      break; 
     } 

     /* consume char. */ 
     token++; 
     len--; 
     /* get pointer to next node's slot. */ 
     pnode = &(node->children[c]); 
     node = *pnode; 
    } 

    *ptoken = token; 
    *plen = len; 
    /* return pointer to last node's slot. */ 
    return pnode; 
} 

static void token_node_add(token_node **pnode, const uint8_t *token, size_t len) { 
    token_node *node; 

    /* find last node in chain for this token. */ 
    pnode = token_node_find_last_node(pnode, &token, &len); 

    /* if full token was consumed then we found the last node for this token. */ 
    if(!len) { 
     node = *pnode; 
     node->ref_count++; 
     return; 
    } 

    /* check if the children list of the last node needs to be grown. */ 
    node = *pnode; 
    if(node) { 
     uint32_t c = *token; 
     /* consume char. */ 
     token++; 
     len--; 
     /* grow node to make room for new char. */ 
     token_node_grow(pnode, c); 
     node = *pnode; /* token_node_grow() may change the node's pointer. */ 
     /* get slot for new child. */ 
     pnode = &(node->children[c - node->min]); 
    } 
    /* build node chain for un-consumed part of token. */ 
    token_node_build_chain(pnode, token, len); 
} 

static size_t token_node_longest_token(token_node *node, const uint8_t *text, size_t len) { 
    size_t last_token_len = 0; 
    size_t off = 0; 
    uint32_t c; 

    /* loop until we get a NULL node or run out of text. */ 
    do { 
     if(node->ref_count > 0) { 
      /* found a token, keep track of it's length. */ 
      last_token_len = off; 
     } 
     /* end of input text. */ 
     if(off >= len) break; 
     /* next char. */ 
     c = text[off]; 
     /* if c < node->min, then it will underflow and be > node->count. */ 
     c -= node->min; 
     /* make sure c is in range. */ 
     if(c >= node->count) { 
      /* End of search, no more child nodes. */ 
      break; 
     } 

     /* consume char. */ 
     off++; 
     /* get pointer to next node's slot. */ 
     node = node->children[c]; 
    } while(node); 

    /* return length of largest token found. */ 
    return last_token_len; 
} 

extern token_tree *token_tree_new() { 
    token_tree *tree = malloc(sizeof(token_tree)); 
    tree->root = token_node_new(0); 
    return tree; 
} 

extern void token_tree_free(token_tree *tree) { 
    token_node_free(tree->root); 
    free(tree); 
} 

extern void token_tree_add(token_tree *tree, const char *token, size_t len) { 
    token_node_add(&(tree->root), token, len); 
} 

extern size_t token_tree_longest_token(token_tree *tree, const char *text, size_t len) { 
    return token_node_longest_token(tree->root, text, len); 
} 

#ifdef TEST_TOKEN_TREE 
#include <stdio.h> 
#include <string.h> 

static const char *test_tokens[] = { 
    "s", 
    "stack", 
    "stackoverflow", 
    "over", 
    "overflow", 
    NULL, 
}; 

static const char *test_input[] = { 
    "aastackoverasdfasdf", 
    "stack7777", 
    "777stack777", 
    "overstackflow", 
    NULL, 
}; 

static void add_tokens(token_tree *tree, const char **tokens) { 
    int i; 
    for(i = 0; tokens[i] != NULL; i++) { 
     token_tree_add(tree, tokens[i], strlen(tokens[i])); 
    } 
} 

static void print_tokens(token_tree *tree, const char *text) { 
    size_t len = strlen(text); 
    size_t token_len; 

    printf("input: \"%s\"\n", text); 
    printf("tokens: ["); 
    while(len) { 
     token_len = token_tree_longest_token(tree, text, len); 
     if(token_len > 0) { 
      printf("<%.*s>", (int)token_len, text); 
     } else { 
      printf("?"); 
      token_len = 1; 
     } 
     text += token_len; 
     len -= token_len; 
    } 
    printf("]\n"); 
} 

static void run_test(token_tree *tree, const char **texts) { 
    int i; 
    for(i = 0; texts[i] != NULL; i++) { 
     print_tokens(tree, texts[i]); 
    } 
} 

int main(int argc, char *argv[]) { 
    token_tree *tree = token_tree_new(); 

    add_tokens(tree, test_tokens); 

    run_test(tree, test_input); 
    run_test(tree, test_tokens); 

    token_tree_free(tree); 
} 

#endif 
Смежные вопросы