2011-03-22 21:20:23     分类: C++
摘要: AC自动机(Aho-Corasick automation), 是一种常用的多模匹配算法,常用于查找一组关键词在某一篇文章是否存在,以及单个词组出现的频率.

AC自动机-多模匹配算法C++实现(兼容中文)

项目地址 Gayhub

AC自动机(Aho-Corasick automation), 是一种常用的多模匹配算法,常用于查找一组关键词在某一篇文章是否存在,以及单个词组出现的频率.

1 基础知识

AC自动机可以认为是TrieTree结构+KMP算法.

1.1 TrieTree

即字典树,又叫单词查找树或键树,是一种树状机构,属于哈希树的变种。

TrieTree常被用于统计和排序大量字符串(不仅限于字符串),所以经常被搜索引擎用于词频统计。优点是:最大限度地减少无所谓的字符串比较,查询效率比哈希表高。

TrieTree的核心思想是用空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提交效率的目的。

TrieTree有3个基本特征:

  • 跟节点不包含任何字符,除根节点外每一个节点都只包含一个字符;
  • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
  • 每个几点的所有子节点都不相同

假设有一串字符串, {tree, try, thread, talk, other, ocean} , 那么其建立的字典树如下:

cbaee5ae98f367d26dea463b33108fdb.jpeg

每个红色节点为根节点, 通过树挨个节点查找如未到根节点则该关键词未匹配,反之则匹配。

以上是英文字典树的建立,英文只有26个字母,所以也就是每个节点下最多26叉,即使考虑特殊词组包含数字 标点 及特殊字符,也不会有很多分叉。

由于中文的特殊性 导致中文的字典树可能会非常的庞大,假设有千万词组的字典,那么第一级子节点就会很庞大,常用中文就有5万字,所以不管你用什么查找方案都不会很快,如果词组都是2字以上,那么子节点的分叉数量也有很大的不可预测性。

可以考虑将汉字转utf8编码再创建字典,再标记字符开始节点和结束节点,这样每个节点最大也只有36个分叉。

字典的创建可以只创建一次,只要避锋,用转码方式创建字典的性能损耗也许可以忽略不计,但是在匹配文章时,文章的转换为编码的性能损耗就需要考虑一下了。

1.2 构建失配指针(fail指针)

在KMP算法中,当我们比较到一个字符发现失配的时候我们会通过next数组,找到下一个开始匹配的位置,然后进行字符串匹配,当然KMP算法试用与单模式匹配,所谓单模式匹配,就是给出一个模式串,给出一个文本串,然后看模式串在文本串中是否存在。

在AC自动机中,我们也有类似next数组的东西就是fail指针,当发现失配的字符失配的时候,跳转到fail指针指向的位置,然后再次进行匹配操作,AC自动机之所以能实现多模式匹配,就归功于Fail指针的建立。

常见的失配指针的构建方法如下图:

e4514483dc8ab4095308c968967257d0.jpeg

大部分网上有关AC自动机的失配指针都是如此设置,有以下特征:

  • 空节点下的第一级节点的失配指针直接指向root空节点(蓝色线)
  • 所有尾部节点的失配指针都指向了root空节点(蓝色线)
  • 空节点下的非第一级节点失配,如果当前字符在第一节点中存在,则指向相应的第一级子节点(紫色线), 如果第一级不存在则指向root空节点(黑色线);

这是最常见的失配指针的构建方式,也是性能最好的一种构建方式,

但这种失配指针有一个很明显的缺陷,就是字典必须要一次性构建完成,或者最顶层的字符可预测,这是因为下级节点失配指针并非全部都是指向root空节点,这就导致如果你想向字典中增加一个新的关键词,就需要遍历所有子节点重建失配指针。如果这个字典足够大,那么重建失配指针就是非常漫长的一个过程了。

一个解决方法是不为中间的节点设置失配指针,或全部指向root节点,在中间节点失配时,再从root节点多匹配一次来决定下一次循环的根节点,但是不建议这么做,最好的还是做好字典间的衔接切换,有计划的维护字典。

2 实现

根据以上知识,我们要需要定义两个类: 节点类和匹配类

2.1 Node.h 节点类的定义

// 字典树
// Created by 神奇的胖子 on 2011/3/18.
//

#ifndef AC_AUTOMATION_NODE_H
#define AC_AUTOMATION_NODE_H

#include <map>
#include <string>
#include <iostream>

using namespace std;

class Node
{
public:
    /**
     * 字
     */
    string word;

    /**
     * 下一节点
     */
    map<string, Node*> next;

    /**
     * 失配指针
     */
    Node *fail;

    /**
     * 是否匹配,即是否为根节点
     */
    bool matched;

    /**
     * 命中的词频
     */
    int repeats;

    /**
     * 字长
     * 注意: 非字符长
     */
    int wordLength;

    /**
     * 第一次出现位置
     */
    int index;

public:
    Node() : word(""), fail(0), matched(false), repeats(0), wordLength(0), index(-1) {};
};

#endif //AC_AUTOMATION_NODE_H

Node类很简单,用于承载TrieTree的节点。仅有一个文件

2.2 Autiomation.h 匹配类

// 匹配类
// Created by 神奇的胖子 on 2011/3/18.
//

#ifndef AC_AUTOMATION_AUTOMATION_H
#define AC_AUTOMATION_AUTOMATION_H

#include <map>
#include <queue>
#include <vector>
#include <string>
#include <iostream>
#include "Node.h"

using namespace std;

class Automation
{
public:
    /**
     * 根节点
     */
    Node *root;

    /**
     * 用于存储所所有尾节点
     */
    vector<Node*> instances;

public:
    Automation();
    ~Automation();

    /**
     * 将字符串拆分成数组
     *
     * @param current
     * @param wordLength
     * @param characters
     */
    void splitWord(const string &word, int &wordLength, vector<string> &characters);

    /**
     * 获取当前节点的下一节点
     *
     * @param current    当前节点
     * @param character  键值
     * @return
     */
    Node* getNext(Node* current, string &character);

    /**
     * 添加字典
     *
     * @param word
     */
    void add(const string &word);

    /**
     * 创建字典
     */
    void build();

    /**
     * 匹配 词典中任何一个词汇匹配成功都会返回
     *
     * @param buf
     * @return
     */
    bool match(const string &buf);

    /**
     * 返回所有命中的节点信息
     *
     * @param buf
     */
    void search(const string &buf, map<string, Node*> &nodes);

    /**
     * 打印字典
     */
    void print();
};

#endif //AC_AUTOMATION_AUTOMATION_H

匹配类包含字符串分割、字典树创建、失配指针创建、匹配及搜索机箱功能,看下具体的实现。

2.3 Automation.cpp 实现

2.3.1 构造函数和析构函数
Automation::Automation()
{
    root = new Node;
}

Automation::~Automation()
{
    delete root;
}

在构造的时候创建一个空的根节点。

2.3.2 字符串分割方法
void Automation::splitWord(const string &word, int &wordLength, vector <string> &characters)
{
    int wordSize = word.size();

    int i = 0;
    while (i < wordSize) {
        int characterSize = 1;

        if (word[i] & 0x80) {
            char character = word[i];
            character <<= 1;
            do {
                character <<= 1;
                ++characterSize;
            } while (character & 0x80);
        }

        string subWord;
        subWord = word.substr(i, characterSize);
        characters.push_back(subWord);

        i += characterSize;
        ++wordLength;
    }
}

由于需要兼容中文,所以需要独立的字符串分割的方法。

2.3.3 从当前节点出发获取下一节点
Node* Automation::getNext(Node *current, string &character)
{
    map<string, Node*>::iterator item = current->next.find(character);

    if (item != current->next.end()) {
        return item->second;
    }

    if (current == root) {
        return root;
    }

    return 0;
}

每个节点的子节点都存储在next向量中, next是一个map<string, Node*>向量,之所以用map来存储而不用[]数组来存储, 是由于中文字较多,使用[]数组1是初始化不方便,2是比对查找性能较差。

2.3.4 创建字典树
void Automation::add(const string &word)
{
    int wordLength = 0;
    vector<string> characters;
    splitWord(word, wordLength, characters);

    Node *temp = root;

    int i = 1;
    string pathWord = "";
    // 遍历字符串
    for (vector<string>::iterator character = characters.begin(); character != characters.end(); ++character) {
        pathWord.append(*character);
        map<string, Node*>::iterator item = temp->next.find(*character);

        if (item != temp->next.end()) {   // 中间节点,指针移到下一节点
            temp = item->second;
        } else {                          // 尾节点,添加新的节点
            Node *n = new Node;
            n->word = pathWord;
            n->wordLength = i;
            temp->next.insert(make_pair(*character, n));
            temp = n;
        }

        if (character + 1 == characters.end()) {
            temp->matched = true;
            // cout<< "完整词组:" << temp->word << endl;
        }

        ++i;
    }
}

遍历关键词,然后从root根节点进入,如果根据字符知道子节点,就移动到子节点进行下一次循环,如果没有,则创建节点。如果字符遍历到尾部,则将找到活创建的最后一个节点的matched设置为true。

有几个问题需要注意:1. 由于word是常量参数,所以word只能在传参前修改,需要考虑到关键词尾部可能有\r|\n的换行符导致关键词不会如预期那样到最后一个字符结束。 2. 这样的创建在某一条路线上可能会有多个节点会被标记为完整的词组。并不是最尾部的一个才是完整词组.

2.3.5 设置失配指针
void Automation::build()
{
    queue<Node*> Q;

    // 将第一级节点的所有失配指针都设置为root 并将所有第一级节点加入队列
    for (map<string, Node*>::iterator item = root->next.begin(); item != root->next.end(); ++ item) {
        item->second->fail = root;
        Q.push(item->second);
    }

    instances.clear();
    //instances.push_back(root);

    // 遍历队列
    while (!Q.empty()) {
        Node* temp = Q.front();
        Q.pop();

        // 遍历当前节点的下一节点
        for (map<string, Node*>::iterator item = temp->next.begin(); item != temp->next.end(); ++ item) {
            // 键值
            string character = item->first;

            // 将当前节点加入队列循环
            Q.push(item->second);

            // 设置失配指针 从上一节点的失配指针查起
            Node  *parent = temp->fail;
            while (!getNext(parent, character)) parent = parent->fail;
            item->second->fail = getNext(parent, character);
            if (!getNext(parent, character)) throw 1;
        }

        // 存储尾节点
        if (temp->matched) {
            instances.push_back(temp);
        }
    }
}

设置失配指针分两步:

  1. 将所有空节点下的第一级子节点的失配指针都设置为root, 并将其加入队列
  2. 遍历队列,为当前节点的next节点设置失配指针,所有next接地男介入队列继续循环。处理过的节点弹出队列。
2.3.6 匹配方法
bool Automation::match(const string &buf)
{
    int bufLength = 0;
    vector<string> characters;
    splitWord(buf, bufLength, characters);

    Node *temp = root;
    // 遍历字符串
    for (vector<string>::iterator character = characters.begin(); character != characters.end(); ++character) {
        while (!getNext(temp, *character)) {
            temp = temp->fail;
        }
        temp = getNext(temp, *character);

        if (temp->matched) {
            return true;
        }
    }

    return false;
}

遍历要匹配的字符串,从字典的root节点出发,逐个字比对,一旦不匹配,则节点指针移动到失配指针, 匹配到关键词则退出循环不再进行后面的字符匹配。

2.3.7 搜索方法
void Automation::search(const string &buf, map<string, Node*> &nodes)
{
    int bufLength = 0;
    vector<string> characters;
    splitWord(buf, bufLength, characters);

    int index = 0;

    Node *temp = root;
    // 遍历字符串
    for (vector<string>::iterator character = characters.begin(); character != characters.end(); ++character) {

        while (!getNext(temp, *character)) {
            temp = temp->fail;
        }

        temp = getNext(temp, *character);

        if (temp->matched) { //如果匹配
            map<string, Node*>::iterator nodeFind = nodes.find(temp->word);
            if (nodeFind == nodes.end()) {
                temp->repeats = 1;
                temp->index   = index + 1 - temp->wordLength ;
                nodes.insert(make_pair(temp->word, temp));
            } else {
                nodeFind->second->repeats += 1;
            }
        }
        ++ index;
    }
}

搜索方法和匹配方法原理几乎一致,不同的是匹配方法在匹配到关键词的时候继续匹配,会使用一个map结构来存储匹配的节点。

2.3.8 字典打印方法
void Automation::print()
{
    int n = 0;
    for (vector<Node*>::iterator node = instances.begin(); node != instances.end(); ++node ) {
        ++n;
        cout << "--------------------------------" << endl;
        cout << "编号 : " << n << endl;
        cout << "分词 :" << (*node)->word << endl;
        cout << "长度 : " << (*node)->wordLength << endl;
        cout << "尾点 : " << ((*node)->matched ? "是" : "否") << endl;
    }
    cout << "--------------------------------" << endl;
}

2.4 测试

现在写个测试方法来测试一下

main.cpp

#include <iostream>
#include<fstream>
#include <time.h>
#include "Node.h"
#include "Automation.h"

using namespace std;

void string_replace(string &strBig, const string &strsrc, const string &strdst);

int main() {

    cout << "\n\n" << endl;

    clock_t starts, ends;
    Automation *automation = new Automation;

    ifstream fin("d:\\words.txt");
    string word;

    starts = clock();
    int counts = 0;
    if (fin) {
        while (getline (fin, word)) {
            string_replace(word, "\n", "");
            string_replace(word, "\r", "");
            automation->add(word);
            ++counts;
        }
    } else {
        cout <<"字典读取失败!" << endl;
    }
    automation->build();
    ends = clock();
    cout << "创建字典成功! 字典条目数: " << counts << " 耗时:" << ends - starts << "ms\n" << endl;

    // 打印字典
    // automation->print();

    ifstream buf("d:\\post.txt");
    string str((istreambuf_iterator<char>(buf)), istreambuf_iterator<char>());

    //string_replace(str, "\r\n", "  ");

    starts = clock();
    bool isMatched = automation->match(str);
    ends   = clock();
    cout << "字典命中结果: " << (isMatched ? "成功 !" : "失败 !") << " 耗时:" << (ends - starts) << "ms\n" << endl;

    starts = clock();
    cout << "命中的关键词:" << endl;
    cout << "--------------------------------" << endl;
    map<string, Node*> nodes;
    automation->search(str, nodes);
    int n = 0;
    for (map<string, Node*>::iterator item = nodes.begin(); item != nodes.end(); ++ item) {
        cout << "匹配  : " << item->second->word << endl;
        cout << "词频  : " << item->second->repeats << endl;
        cout << "首位  : " << item->second->index << endl;
        ++n;
        cout << "--------------------------------" << endl;
    }
    ends   = clock();
    cout << "\n文章长度: " << str.size() << "命中 " << n << "个关键词" << " 耗时:" << (ends - starts) << "ms\n" << endl;

    delete automation;

    return 0;
}

void string_replace(string &strBig, const string &strsrc, const string &strdst)
{
    string::size_type pos = 0;
    string::size_type srclen = strsrc.size();
    string::size_type dstlen = strdst.size();

    while( (pos=strBig.find(strsrc, pos)) != string::npos )
    {
        strBig.replace( pos, srclen, strdst );
        pos += dstlen;
    }
}

输出结果:


创建字典成功! 字典条目数: 11384 耗时:125ms

字典命中结果: 成功 ! 耗时:0ms

命中的关键词:
--------------------------------
匹配  : 一季度
词频  : 1
首位  : 2378
--------------------------------
匹配  : 上市公司
词频  : 45
首位  : 3949
--------------------------------
匹配  : 上涨幅度
词频  : 1
首位  : 555
--------------------------------
匹配  : 上证指数
词频  : 3
首位  : 598
--------------------------------
匹配  : 下探
词频  : 1
首位  : 3414
--------------------------------
匹配  : 下跌
词频  : 1
首位  : 3387
--------------------------------
匹配  : 不低于
词频  : 1
首位  : 5387
--------------------------------
匹配  : 业务板块
词频  : 1
首位  : 6356
--------------------------------
匹配  : 业务资格
词频  : 1
首位  : 6711
--------------------------------
匹配  : 业绩预期
词频  : 2
首位  : 2382
--------------------------------
匹配  : 东方通信
词频  : 6
首位  : 256
--------------------------------
匹配  : 个股
词频  : 3
首位  : 2903
--------------------------------
匹配  : 中介机构
词频  : 2
首位  : 8116
--------------------------------
匹配  : 中国证监会
词频  : 1
首位  : 5145
--------------------------------
匹配  : 中小股东
词频  : 7
首位  : 4076
--------------------------------
匹配  : 买点
词频  : 5
首位  : 2007
--------------------------------
匹配  : 云南白药
词频  : 1
首位  : 142
--------------------------------
匹配  : 五粮液
词频  : 1
首位  : 308
--------------------------------
匹配  : 交割日
词频  : 1
首位  : 6817
--------------------------------
匹配  : 交易
词频  : 19
首位  : 688
--------------------------------
匹配  : 交易完成
词频  : 2
首位  : 5265
--------------------------------
匹配  : 交易所
词频  : 2
首位  : 7028
--------------------------------
匹配  : 交易日
词频  : 1
首位  : 688
--------------------------------
匹配  : 亿元贷款
词频  : 3
首位  : 4939
--------------------------------
匹配  : 今天大盘
词频  : 1
首位  : 534
--------------------------------
匹配  : 以下简称
词频  : 3
首位  : 4469
--------------------------------
匹配  : 价值发现
词频  : 1
首位  : 4242
--------------------------------
匹配  : 会计师
词频  : 1
首位  : 6954
--------------------------------
匹配  : 会计师事务所
词频  : 1
首位  : 6954
--------------------------------
匹配  : 会计报表
词频  : 1
首位  : 7662
--------------------------------
匹配  : 估值
词频  : 2
首位  : 5240
--------------------------------
匹配  : 低吸
词频  : 7
首位  : 1463
--------------------------------
匹配  : 信息披露
词频  : 1
首位  : 6254
--------------------------------
匹配  : 公司业绩
词频  : 1
首位  : 8788
--------------------------------
匹配  : 公司所有权
词频  : 1
首位  : 3972
--------------------------------
匹配  : 公司治理
词频  : 3
首位  : 4162
--------------------------------
匹配  : 公司股价
词频  : 1
首位  : 4254
--------------------------------
匹配  : 公司股票
词频  : 1
首位  : 7806
--------------------------------
匹配  : 公司资产
词频  : 2
首位  : 8401
--------------------------------
匹配  : 公开发行
词频  : 1
首位  : 4899
--------------------------------
匹配  : 关联人
词频  : 2
首位  : 5500
--------------------------------
匹配  : 冲高
词频  : 7
首位  : 619
--------------------------------
匹配  : 冲高回落
词频  : 2
首位  : 779
--------------------------------
匹配  : 净利润
词频  : 3
首位  : 5382
--------------------------------
匹配  : 减仓
词频  : 2
首位  : 2779
--------------------------------
匹配  : 创业板
词频  : 1
首位  : 833
--------------------------------
匹配  : 创出新高
词频  : 1
首位  : 3667
--------------------------------
匹配  : 创投
词频  : 1
首位  : 1798
--------------------------------
匹配  : 创新高
词频  : 2
首位  : 304
--------------------------------
匹配  : 券商
词频  : 8
首位  : 1207
--------------------------------
匹配  : 剩余资产
词频  : 1
首位  : 6620
--------------------------------
匹配  : 募集资金
词频  : 1
首位  : 5518
--------------------------------
匹配  : 博弈
词频  : 4
首位  : 4202
--------------------------------
匹配  : 历史新高
词频  : 1
首位  : 19
--------------------------------
匹配  : 参考依据
词频  : 1
首位  : 7207
--------------------------------
匹配  : 发行价
词频  : 1
首位  : 6776
--------------------------------
匹配  : 发行价格
词频  : 1
首位  : 6776
--------------------------------
匹配  : 向上突破
词频  : 1
首位  : 630
--------------------------------
匹配  : 咨询报告
词频  : 1
首位  : 6560
--------------------------------
匹配  : 回调
词频  : 8
首位  : 1218
--------------------------------
匹配  : 垃圾股
词频  : 3
首位  : 422
--------------------------------
匹配  : 增发
词频  : 1
首位  : 5612
--------------------------------
匹配  : 外资
词频  : 1
首位  : 1019
--------------------------------
匹配  : 大幅高开
词频  : 2
首位  : 2112
--------------------------------
匹配  : 大盘
词频  : 2
首位  : 1337
--------------------------------
匹配  : 好股票
词频  : 1
首位  : 189
--------------------------------
匹配  : 如果按
词频  : 2
首位  : 6907
--------------------------------
匹配  : 子公司
词频  : 1
首位  : 1856
--------------------------------
匹配  : 实现
词频  : 3
首位  : 4810
--------------------------------
匹配  : 实股
词频  : 1
首位  : 1559
--------------------------------
匹配  : 实际控制人
词频  : 1
首位  : 5276
--------------------------------
匹配  : 审计报告
词频  : 4
首位  : 6680
--------------------------------
匹配  : 审计机构
词频  : 4
首位  : 6730
--------------------------------
匹配  : 对公司
词频  : 1
首位  : 4000
--------------------------------
匹配  : 对赌
词频  : 1
首位  : 5099
--------------------------------
匹配  : 导致
词频  : 2
首位  : 4313
--------------------------------
匹配  : 小幅放大
词频  : 1
首位  : 3248
--------------------------------
匹配  : 小股东
词频  : 6
首位  : 4305
--------------------------------
匹配  : 就可以
词频  : 2
首位  : 2451
--------------------------------
匹配  : 尾盘
词频  : 2
首位  : 3231
--------------------------------
匹配  : 差额
词频  : 2
首位  : 5444
--------------------------------
匹配  : 市场呈现
词频  : 1
首位  : 3355
--------------------------------
匹配  : 市场情绪
词频  : 1
首位  : 1757
--------------------------------
匹配  : 市场运行
词频  : 2
首位  : 806
--------------------------------
匹配  : 建议投资者
词频  : 1
首位  : 492
--------------------------------
匹配  : 成交
词频  : 6
首位  : 678
--------------------------------
匹配  : 成交量
词频  : 5
首位  : 695
--------------------------------
匹配  : 成长性
词频  : 1
首位  : 510
--------------------------------
匹配  : 所需资金
词频  : 1
首位  : 5996
--------------------------------
匹配  : 投资建议
词频  : 1
首位  : 3921
--------------------------------
匹配  : 投资集团
词频  : 1
首位  : 4774
--------------------------------
匹配  : 持股比例
词频  : 2
首位  : 4082
--------------------------------
匹配  : 控股
词频  : 1
首位  : 1882
--------------------------------
匹配  : 控股股东
词频  : 1
首位  : 1882
--------------------------------
匹配  : 撤销
词频  : 1
首位  : 7587
--------------------------------
匹配  : 收盘
词频  : 3
首位  : 1273
--------------------------------
匹配  : 放量
词频  : 2
首位  : 1513
--------------------------------
匹配  : 放量突破
词频  : 1
首位  : 1513
--------------------------------
匹配  : 无保留意见
词频  : 1
首位  : 7736
--------------------------------
匹配  : 有效放大
词频  : 1
首位  : 1692
--------------------------------
匹配  : 有望再次
词频  : 1
首位  : 2974
--------------------------------
匹配  : 有限公司
词频  : 4
首位  : 4508
--------------------------------
匹配  : 本次发行
词频  : 1
首位  : 6774
--------------------------------
匹配  : 概念股
词频  : 1
首位  : 397
--------------------------------
匹配  : 流动性
词频  : 1
首位  : 7918
--------------------------------
匹配  : 流动性差
词频  : 1
首位  : 7918
--------------------------------
匹配  : 涨停
词频  : 4
首位  : 1252
--------------------------------
匹配  : 涨幅居前
词频  : 1
首位  : 3284
--------------------------------
匹配  : 涨幅高达
词频  : 1
首位  : 66
--------------------------------
匹配  : 深交所
词频  : 1
首位  : 6268
--------------------------------
匹配  : 深成指
词频  : 1
首位  : 3303
--------------------------------
匹配  : 炒股
词频  : 1
首位  : 3882
--------------------------------
匹配  : 点位
词频  : 1
首位  : 3523
--------------------------------
匹配  : 独立董事
词频  : 4
首位  : 4120
--------------------------------
匹配  : 现金方式
词频  : 2
首位  : 5488
--------------------------------
匹配  : 监事
词频  : 4
首位  : 4009
--------------------------------
匹配  : 监事会
词频  : 4
首位  : 4009
--------------------------------
匹配  : 盘中
词频  : 1
首位  : 3396
--------------------------------
匹配  : 短期
词频  : 4
首位  : 738
--------------------------------
匹配  : 短期调整
词频  : 1
首位  : 2737
--------------------------------
匹配  : 短线
词频  : 2
首位  : 564
--------------------------------
匹配  : 第一大股东
词频  : 10
首位  : 4761
--------------------------------
匹配  : 筹码
词频  : 1
首位  : 4285
--------------------------------
匹配  : 累计涨幅
词频  : 1
首位  : 948
--------------------------------
匹配  : 组选
词频  : 1
首位  : 4373
--------------------------------
匹配  : 结构中
词频  : 1
首位  : 4166
--------------------------------
匹配  : 绩优股
词频  : 5
首位  : 195
--------------------------------
匹配  : 联系方式
词频  : 1
首位  : 3069
--------------------------------
匹配  : 股上市
词频  : 1
首位  : 8716
--------------------------------
匹配  : 股东利益
词频  : 3
首位  : 4306
--------------------------------
匹配  : 股东大会
词频  : 5
首位  : 3978
--------------------------------
匹配  : 股价
词频  : 4
首位  : 8
--------------------------------
匹配  : 股价暴跌
词频  : 1
首位  : 7913
--------------------------------
匹配  : 股份数量
词频  : 1
首位  : 6769
--------------------------------
匹配  : 股指
词频  : 2
首位  : 1054
--------------------------------
匹配  : 股权投资
词频  : 1
首位  : 4484
--------------------------------
匹配  : 股票市场
词频  : 1
首位  : 1106
--------------------------------
匹配  : 获利
词频  : 2
首位  : 3678
--------------------------------
匹配  : 董事会
词频  : 9
首位  : 3996
--------------------------------
匹配  : 董事长
词频  : 1
首位  : 6332
--------------------------------
匹配  : 董秘
词频  : 2
首位  : 6236
--------------------------------
匹配  : 表现强势
词频  : 1
首位  : 400
--------------------------------
匹配  : 观察盘面
词频  : 1
首位  : 3546
--------------------------------
匹配  : 证监会
词频  : 1
首位  : 5584
--------------------------------
匹配  : 评估结果
词频  : 1
首位  : 6565
--------------------------------
匹配  : 请关注
词频  : 1
首位  : 1159
--------------------------------
匹配  : 财务报表
词频  : 1
首位  : 6386
--------------------------------
匹配  : 财务数据
词频  : 1
首位  : 6360
--------------------------------
匹配  : 贵州茅台
词频  : 3
首位  : 4
--------------------------------
匹配  : 费用
词频  : 1
首位  : 3088
--------------------------------
匹配  : 资产价值
词频  : 1
首位  : 6622
--------------------------------
匹配  : 资产减值
词频  : 2
首位  : 6572
--------------------------------
匹配  : 资产评估
词频  : 6
首位  : 5219
--------------------------------
匹配  : 资产评估报告
词频  : 2
首位  : 7232
--------------------------------
匹配  : 资本金
词频  : 3
首位  : 4920
--------------------------------
匹配  : 超跌
词频  : 1
首位  : 3747
--------------------------------
匹配  : 趋势线
词频  : 2
首位  : 1452
--------------------------------
匹配  : 过往业绩
词频  : 1
首位  : 7619
--------------------------------
匹配  : 违约金
词频  : 4
首位  : 5569
--------------------------------
匹配  : 逆势
词频  : 2
首位  : 1724
--------------------------------
匹配  : 部分股份
词频  : 1
首位  : 7268
--------------------------------
匹配  : 部门
词频  : 5
首位  : 4353
--------------------------------
匹配  : 重点关注
词频  : 2
首位  : 1955
--------------------------------
匹配  : 量能
词频  : 4
首位  : 622
--------------------------------
匹配  : 阳线
词频  : 1
首位  : 3242
--------------------------------
匹配  : 阶段性顶部
词频  : 1
首位  : 3694
--------------------------------
匹配  : 震荡整理
词频  : 1
首位  : 3487
--------------------------------
匹配  : 非理性
词频  : 1
首位  : 3453
--------------------------------
匹配  : 顶部
词频  : 3
首位  : 3506
--------------------------------
匹配  : 项目资金
词频  : 1
首位  : 5964
--------------------------------
匹配  : 风险提示
词频  : 2
首位  : 3901
--------------------------------
匹配  : 风险提示公告
词频  : 1
首位  : 7818
--------------------------------
匹配  : 高位
词频  : 3
首位  : 1988
--------------------------------
匹配  : 高度关注
词频  : 1
首位  : 8555
--------------------------------
匹配  : 高开
词频  : 4
首位  : 1202
--------------------------------
匹配  : 高点
词频  : 2
首位  : 3470
--------------------------------
匹配  : 高科技公司
词频  : 1
首位  : 1972
--------------------------------
匹配  : 高管薪酬
词频  : 1
首位  : 8879
--------------------------------
匹配  : 高级经理
词频  : 2
首位  : 4025
--------------------------------
匹配  : 龙头股
词频  : 3
首位  : 1767
--------------------------------

文章长度: 24977命中 180个关键词 耗时:15ms

从搜狗输入法Down了一个金融细胞词库,大约1万多个关键词, 创建字典用了125ms, 时间稍微有些长。 但是匹配和搜索都非常给力, 匹配用时不到1毫秒, 搜索也只用了15ms


文章标签: