Skip to content
This repository was archived by the owner on Dec 20, 2021. It is now read-only.
This repository was archived by the owner on Dec 20, 2021. It is now read-only.

Trieを用いた絵文字判定の実装案 #1

Open
@hec12

Description

@hec12

絵文字判定の実装案として、Trie ( https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%A9%E3%82%A4%E6%9C%A8 )を用いるのはどうでしょうか?

Trieは文字列の集合を管理するのに適しているデータ構造です。
具体的には、各ノードが文字列の接頭辞を表しており、ノードの関係が木構造となっています。

  • 時間面

    • Trieの生成
      Trieの生成の実行時間は 全絵文字のcodePointの合計に比例します。
    • 絵文字の判定
      絵文字判定の最悪実行時間は、絵文字を構成するcodePointの最大個数(現在では7)で抑えられます。
  • メモリ面
    Trieのノードの個数は最悪で全絵文字のcodePointの合計個数です。
    しかし、{0x1F469} と {0x1F469, 0x1F3FF, 0x200D 0x1F692} が絵文字である場合は、
    CodePoint の 接頭辞が共通している部分だけノードの個数を減らせるため、 1 つの絵文字につき 1 つの std::vector を用いるよりかはメモリ消費を減らすことが可能だと考えています。

  • 拡張面
    木構造で管理しているため、codePoint を構成する個数が変更されてもコードの変更をする必要がないと考えられます。

以下のソースコードは、Trieを用いた絵文字判定の実装例です。
ここでは、木構造の子供の管理にmapを用いています。
注意の欄から絵文字と判断される場合には、最長のcodePointで構成される絵文字にマッチングすると仮定しています。
このソースコードを参考にする場合、「EmojiCodePoints.txt」からcodePointを抽出する部分の実装が甘いので、書き換える必要があると思います。

#include <cstdint>
#include <iostream>
#include <vector>

/* 追加でインクルードしたヘッダファイル */
#include <fstream>
#include <sstream>
#include <map>

class EmojiData
{
private:
    /* データセット (約 2000 種類の絵文字の codePoint 一覧) */
    using Node = struct {
        bool isEmoji = false; /* 絵文字に対応するノードであるかどうか? */
        std::map<uint32_t, size_t> nxt; /* Trieのノード管理 */
    };

    std::vector<Node> trieTree; /* trieTreeを管理する動的配列 */
    const int rootNode = 0; /* 根のノード番号 */
public:

    EmojiData()
    {
        trieTree = {{false, {}}}; // trieTreeの初期化
        const std::string fileName = "EmojiCodePoints.txt";

        std::ifstream readingFile;
        readingFile.open(fileName, std::ios::in);
        std::string bufferString;

        while (!readingFile.eof())
        {
            // read by line
            std::getline(readingFile, bufferString);

            /* </ul> のタグ除去をしています。 (ここのブロックは重要ではないです。)*/
            {
                const size_t bufferLength = bufferString.size();
                if (bufferLength >= 5 and bufferString.substr(bufferLength - 5, 5) == "</ul>")
                {
                    bufferString = bufferString.substr(0, bufferLength - 5); 
                }
            }

            /* 文字列を次のように書き換えています。 {codePoint,...,codePoint}, -> codePoint,...,codePoint */
            {
                const size_t bufferLength = bufferString.size();
                if(bufferLength >= 3 and bufferString[0] =='{' and bufferString[bufferLength-2] =='}' and bufferString[bufferLength-1] ==','){
                    bufferString = bufferString.substr(1, bufferLength - 3); 
                }
            }
            std::istringstream stream(bufferString);
            std::string hexString;
            int currentNode = rootNode; /* 現在のノード番号 */

            /* 根からノードの遷移を行い、たどり着いた先のノードに絵文字であるというフラグを立てます。 */
            while (getline(stream, hexString, ',')) /* stream を ','区切りで hexStringに渡す */
            {
                const uint32_t codePoint = std::stoul(hexString, nullptr, 16); /* hexString を uint32_t に変換 */
                if (trieTree[currentNode].nxt.find(codePoint) == trieTree[currentNode].nxt.end())
                {
                    /* ノードがない場合には新しくノードを生成する */
                    trieTree[currentNode].nxt[codePoint] = trieTree.size();
                    const Node addNode = {false, {}};
                    trieTree.push_back(addNode);
                }
                currentNode = trieTree[currentNode].nxt[codePoint];
            }
            trieTree[currentNode].isEmoji = true;
        }

        return;
    }

    size_t check(auto it, const auto & itEnd)
    {
        size_t emojiLength = 0;
        size_t currentLength = 0;
        size_t currentNode = rootNode; /* 現在のノード番号 */

        while (it != itEnd)
        {
            const std::uint32_t codePoint = *(it++);
            if (trieTree[currentNode].nxt.find(codePoint) == trieTree[currentNode].nxt.end()) {
                /* ノードがない場合は探索を打ち切る。*/
                break;
            }

            /* ノードの遷移と文字列の長さの調整 */
            currentNode = trieTree[currentNode].nxt[codePoint];
            currentLength++;

            if (trieTree[currentNode].isEmoji)
            {
                /* 絵文字が存在するノードの場合、絵文字の長さを更新*/
                /* 複数マッチングする絵文字がある場合には、最長の長さとなる */
                emojiLength = currentLength;
            }
        }

        return emojiLength;
    }
};

int main()
{
    EmojiData emojiData;

    // [入力] 0 個以上の codePoint で表現された文字列
    const std::vector<std::uint32_t> codePoints =
    { 0x30, 0x1F46E, 0x200D, 0x2642, 0x48, 0x1F600, 0x1F800, 0x3042, 0x26CF, 0x30, 0x20E3, 0x1F469, 0x1F469, 0x1F3FF, 0x200D, 0x1F692 };

    auto it = codePoints.begin();

    const auto itEnd = codePoints.end();

    while (it != itEnd)
    {
        const size_t emojiLength = emojiData.check(it, itEnd);

        if (emojiLength == 0) // 非絵文字
        {
            std::cout << "non-emoji: " << std::hex << *(it++) << '\n';
        }
        else // 絵文字
        {
            std::cout << "emoji: {";

            for (size_t i = 0; i < emojiLength; ++i)
            {
                if (i != 0)
                {
                    std::cout << ", ";
                }

                std::cout << std::hex << *(it++);
            }

            std::cout << "}\n";
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions