2010-10-31 34 views
6

Tôi đang cố gắng mã hóa một chuỗi. Tôi có một bảng các thẻ có sẵn được đặt hàng dưới dạng trie. Mỗi mã thông báo biết rằng nó có con. Một bảng thẻ đơn giản sẽ như thế nào,Cách hiệu quả để mã hóa một chuỗi - C

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

Trong bảng này, stack là con của soverflow là con của over. Trong thực tế, bảng này sẽ có hơn 5000 bản ghi được sắp xếp theo cách này.

Bây giờ, với một chuỗi stackover, nó sẽ xuất ra stack-valover-val. Thuật toán là tham lam và nó sẽ cố gắng tìm thấy trận đấu dài nhất luôn.

Để làm điều này, tôi sẽ bắt đầu đọc từng ký tự từ đầu vào, tìm kết quả phù hợp, nếu tìm thấy kết quả phù hợp và mã thông báo có con, hãy tìm kiếm lại bằng cách bao gồm ký tự tiếp theo. Làm điều này cho đến khi chúng tôi tìm thấy trận đấu dài nhất. Nếu không tìm thấy kết quả phù hợp nào, hãy thử so khớp bằng cách bao gồm ký tự tiếp theo cho đến khi chúng tôi kết thúc chuỗi hoặc kết quả thành công.

Nếu chúng tôi đến cuối chuỗi mà không khớp, hãy xuất biểu tượng ? và xóa ký tự đầu tiên khỏi đầu vào. Lặp lại toàn bộ quá trình với các ký tự còn lại.

Thuật toán này hoạt động, nhưng việc quay lại và lặp lại trên tất cả các kết hợp có thể có của đầu vào làm cho nó chậm và phức tạp.

Tôi tự hỏi có cách nào tốt hơn để giải quyết vấn đề này không? Bất kỳ trợ giúp sẽ được đánh giá cao.

+0

Tại sao tôi theo dõi trẻ em: Nếu tôi tìm thấy kết quả phù hợp và tìm kiếm trận đấu dài nhất, tôi không phải làm điều đó nếu mã thông báo không có con. Điều này cải thiện hiệu quả. –

Trả lời

2

Thay vì quay ngược lại, bạn có thể ghi nhớ tất cả các kết quả có thể, cho đến khi một kết quả được chọn ra tại một điểm nhất định trong luồng đầu vào. Ví dụ

Tokens: S STACK Stackoverflow Stag VỀ OVERFLOW
String: SSTACKOVERFUN

1 - Tìm thấy S về địa điểm 0, có thẻ bắt đầu với S, thử tất cả, chỉ có S là hợp lệ, vì vậy quyết tâm S
2 - S trên 1, có các mã thông báo như vậy, hãy thử chúng, có thể hợp lệ là S và STACK. Đừng giải quyết, chỉ cần ghi nhớ chúng.
3 - T vào ngày 2, không có mã thông báo như vậy, vì vậy S có thể được giải quyết ngay bây giờ, nhưng chúng tôi cũng có mã thông báo dài hơn (STACK) nên S không tốt. Mương S, và STACK chỉ còn lại, nhưng nó có con. Hãy thử chuỗi cho trẻ em.Không có trẻ em càng tốt để quyết STACK
4 - O trên 6, đã tokens như vậy, thử chúng, đã chỉ VỀ, vì vậy quyết tâm VỀ
5 - F trên 10, không có thẻ như vậy, và không có gì để giải quyết từ trước vì vậy đây là phi tokenizable
6 và 7 - tương tự như bước 5

kết quả cuối cùng: S STACK VỀ vui

1

Bạn có thể sử dụng thuật toán Aho-Corasick không? Nó tạo ra một automaton để tìm kiếm một cây từ khóa (trie).

1

Tôi nghĩ rằng bạn muốn lấy tất cả các từ khóa của bạn và sắp xếp chúng theo thứ tự abc đảo ngược, vì vậy danh sách của bạn sẽ trở thành (cộng với một vài tính năng bổ sung)

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

Cột thứ ba trong danh sách này là con trỏ đến mã thông báo gốc luôn thấp hơn trong danh sách. Sau đó, bạn có thể lấy chuỗi mục tiêu và tìm kiếm nhị phân của bạn, nơi nó phù hợp với danh sách này. Nếu nó nằm trên một mã thông báo khớp với thì bạn sẽ cắt bỏ phần đó và lặp lại quy trình cho phần còn lại. Nếu nó không phù hợp với bạn, hãy sử dụng con trỏ cha mẹ để tìm mã thông báo phù hợp tiềm năng dài nhất tiếp theo.

Nếu bạn muốn thực sự ưa thích, bạn cũng có thể chia chuỗi thành các từ 64 bit và so sánh 8 ký tự cùng một lúc trong tìm kiếm nhị phân.

1

tôi đề nghị bạn thử Ragel, Nó có thể tạo ra máy quét hiệu quả mà có thể làm trận đấu dài nhất/backtracking. Xem chương 6.3 trong số Ragel user guide để biết thêm thông tin.

Tôi đã tạo một thử nghiệm nhỏ mà tôi nghĩ phù hợp với đặc điểm kỹ thuật của bạn, đây chỉ là mô tả máy nhà nước, mà không cần mã để đầu vào nuôi:

%%{ 
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

Lớp prefix_tree chỉ trả lại "true" khi một trong các tiền tố của cây khớp với phần bắt đầu của văn bản đầu vào. Nó thậm chí sẽ không cho bạn biết tiền tố nào hoặc tiền tố đó là bao lâu.

Mã thông báo này sẽ tìm mã thông báo dài nhất khớp với phần bắt đầu của văn bản nhập. Tìm kiếm chức năng token_tree_longest_token() chỉ cần trả lại độ dài của mã thông báo dài nhất phù hợp với so với đầu văn bản nhập.

Thuật toán cơ bản tương tự như thuật toán được mô tả trong câu hỏi, nhưng nó có thể nhanh hơn.

Ngoài ra còn có một số cách để cải thiện mức sử dụng bộ nhớ, điều này có thể nhanh hơn.

#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 
Các vấn đề liên quan