mapを使って形態素にidをふる

C++の標準ライブラリであるmapを使ってシンボルにidを振っていく方法をメモ.自然言語処理をしているため,今回はシンボル=形態素ということにして書いていく.

以下のようなテストファイルに対して,重複なくidを振りたい.

//test.txt
取材

帰り


以前


買った
こと

ある
豆腐

さん

覗いたら
美味し
そうな
稲荷
寿司

売って
いた

C++では,mapという標準ライブラリを使うことで,以前使っていたPythonの辞書型のような処理を書くことが可能らしい.
mapの宣言にはテンプレートを用いて,の型を宣言する.例えば,今回は,単語をkeyとして,idをvalueとして格納したいので,std::mapのように宣言する.mapに対して逐次的にアクセスするためには,イテレータを宣言する.このイテレータは,扱う対象のmapのテンプレート型と同じ型で宣言をする.

  std::map<std::string, int> word_dictionary;         // 単語とid
  std::map<std::string, int>::iterator word_iterator; // word_dictionary を扱うイテレータ

idをふる部分では,find()メソッドによってmap内に既に対象の単語が存在するかどうかを確かめる.そして,存在しない場合にのみ,新たな要素として単語とidのペアを追加することで,重複なくidをふることが可能である.

  while(ifs && getline(ifs, word)){
    if(word_dictionary.find(word) == word_dictionary.end()){
      word_dictionary.insert(std::pair<std::string, int>(word, number));
      number++;
    }
  }

結果の表示は,先ほど作ったイテレータによる逐次アクセスで実現される.

  for (word_iterator = word_dictionary.begin(); word_iterator != word_dictionary.end(); word_iterator++){
    std::cout << word_iterator->first << " : " << word_iterator->second << std::endl;
  }


以上をまとめると,次のようなプログラムが書ける.

/*
 unigram_id.cc
 単語にidを振っていく
 */
#include <iostream>
#include <fstream>
#include <cstring>
#include <map>

int main()
{
  std::ifstream ifs("./test.txt");

  std::string word;                                   // 処理対象のword
  int id = 0;                                         // 何種類目の単語か
  std::map<std::string, int> word_dictionary;         // 単語とid
  std::map<std::string, int>::iterator word_iterator; // word_dictionary を扱うイテレータ

  // 一行ずつ単語を読み込む
  // 新しい単語の場合はidとともにmapに挿入
  while(ifs && getline(ifs, word)){
    if(word_dictionary.find(word) == word_dictionary.end()){
      word_dictionary.insert(std::pair<std::string, int>(word, id));
      id++;
    }
  }

  //結果の表示
  for (word_iterator = word_dictionary.begin(); word_iterator != word_dictionary.end(); word_iterator++){
    std::cout << word_iterator->first << " : " << word_iterator->second << std::endl;
  }

  return 0;
}

実行結果.
確かに,重複なくidをふることができた.

、 : 4
。 : 22
ある : 9
いた : 21
が : 19
こと : 8
さん : 12
そうな : 16
に : 3
の : 1
も : 6
を : 13
以前 : 5
取材 : 0
売って : 20
寿司 : 18
屋 : 11
帰り : 2
稲荷 : 17
美味し : 15
覗いたら : 14
豆腐 : 10
買った : 7

どうやら,イテレータによる逐次アクセスはkeyの昇順にアクセスされるらしい.明示的にソートしなければならないPythonの辞書型と少し異なる部分なのかな.


mapの使い方とかは以下を参考にした.

【C++】mapを使うサンプル。【STL】
http://www.cppll.jp/cppreference/cppmap_details.html


おまけ

mapを使ってペアを保存する際に,その最大保存数が気になったので調べてみた.ひとまず,以下のように様々なサイズの要素を持つmapを用意し,その最大保存数をmax_size()で出力してみる.

/*
 maxsize_of_map.cc
 mapの最大保存件数を表示
 */
#include <iostream>
#include <map>

struct Fivebytes{
  char c1;
  int i1;
};

struct Longs2{
  long l1;
  long l2;
};

struct Longs3{
  long l1;
  long l2;
  long l3;
};

int main()
{
  std::map<std::string, std::string> string_map;
  std::map<char, char> char_map;
  std::map<short, short> short_map;
  std::map<int, int> int_map;
  std::map<Fivebytes, Fivebytes> five_map;
  std::map<long, long> long_map;
  std::map<Longs2, Longs2> longs2_map;
  std::map<Longs3, Longs3> longs3_map;

  std::cout << "string: "     << string_map.max_size() << std::endl;
  std::cout << "char(1): "    << char_map.max_size()   << std::endl;
  std::cout << "short(2): "   << short_map.max_size()  << std::endl;
  std::cout << "int(4): "     << int_map.max_size()    << std::endl;
  std::cout << "five(5): "    << five_map.max_size()   << std::endl;
  std::cout << "long(8): "    << long_map.max_size()   << std::endl;
  std::cout << "longs2(16): " << longs2_map.max_size() << std::endl;
  std::cout << "longs3(32): " << longs3_map.max_size() << std::endl;

  return 0;
}

出力結果

> string: 384307168202282325
> char(1): 461168601842738790
> short(2): 461168601842738790
> int(4): 461168601842738790
> five(5): 384307168202282325
> long(8): 384307168202282325
> longs2(16): 288230376151711743
> longs3(32): 230584300921369395

括弧内はそれぞれの要素のbyte数.実際には,同じ型の要素2つをペアとしているので,その2倍の容量にしています.で,結果を見てみたのですが,どうも大きすぎる.今使ってるマシンのメモリが250G以上あることを考えてもちょっと多すぎないか.うーん,なんでだろうか.ひとまず今日のところは保留にする.(保留のままかもしれないが笑



2012/01/22 追記 --------------------------------
コメントで教えていただいたが,Pythonの辞書型とC++のstd::mapはデータ構造が違うらしい.勝手な思い込みできっと同じなんだろうと思ってた...気を付けなければ!
あと,今思えばstd::mapが木構造であることを考えれば,出力した際にkeyの順番で表示されるのももっともな気がする.

とにかく,コメントに感謝><