テキストファイル(UTF8)を一文字ずつ分割したかった話
今回は記事タイトル通り。
UTF-8でエンコードされたテキストファイルを
一文字ずつ分割するプログラムを作成したかった話である。
ぶっちゃけそんなプログラムは簡単に書ける人が多いだろう。
特に記法がシンプルなスクリプト言語(PythonやPerl、Rubyなど)であれば
本当にちょろっとコードを書けば終了である。
しかし今回は事情が違った。
というのも、処理しなければならないテキストファイルが
6GBほどの量だったからだ。
PythonやRubyのようなスクリプト言語を使用すると
膨大なテキストを流し込んだ時には速度が遅いため時間がかかってしまう。
そこで最終的に処理の速いC言語で実装することにした。
Pythonによるコード
ひとまず最初に作成したPythonによるソースコードを載せることにする。
# -*- coding: utf-8 -*- import codecs import sys i = 0 fin = codecs.open('text8.txt', 'r', 'utf-8') fout = codecs.open('output.txt','w','utf-8') while True: i += 1 c = fin.read(1) if (c==' ') : pass elif (c=='\n') : fout.write("\n") else : fout.write(c) fout.write(" ") if (i%200000==0) : print(str(i)+"_clear") if not c: break
Pythonで書くとわりと簡単に作成できた。
このプログラムの機能はいたってシンプルである。
一文字ずつテキストを読み込んでスペースだったらスペース、改行であれば改行を出力先のテキストに書き込む。
文字であれば文字とスペースをテキストに書き込む。
20万文字読み込むたび途中経過を出力する。
これで一文字ずつ分割されたテキストファイルができる。
ただし実行してみたところ処理速度がいかんせん遅かった。
そこで、膨大な量のテキストを処理する場合はやはり処理の速いコンパイラ言語を使うのが適切だと思い、
C言語を使って同じ処理をするプログラムを書いてみた。
C言語によるコード
gist6bfba928e6f972e62c2678bfc490f957
C言語でもPythonと同様にread(1)
のようなものでできると思っていたのだが、
どうも最初うまくいかなかった。
というのもUTF-8は英語のアルファベットなどは1バイト、
日本語の漢字などは3バイトといった具合に
文字の種類によって一文字のバイトの単位が異なる。
その場合1バイトずつ読み込んでスペースを入れたのでは
日本語のワイド文字が分裂してしまう。
簡単に説明すると、
アルファベットや数字のようなASCIIであれば
abc = ○□△ のように表されているため(○□△はそれぞれ1バイト)
abc -> ○/□/△ ->a b c
このように分割できる
しかし日本語の漢字のようなものだと3バイトなどで一文字を構成しているため
日 = ○□△ という具合に表現されている。これを分割してしまうと
日 -> ○/□/△ -> ???
と文字化けしてしまう(バイナリファイルと化す)
そこで、その一文字が何バイトで構成されているかを最初に調べてから
どのタイミングでスペースを入れるのかを考慮することになったわけである。
ちなみにこのプログラムは
コマンドライン引数にテキストファイルを指定することに注意。
それと、読み込んだ文字数が大きすぎるとi++
が
最大値をオーバーするので1000万突破したらリセットしたりする方が良いかも。
締め
今回はプログラムをダンプすることになったわけだが
そもそもなぜ膨大なテキストファイルを一文字ずつ分割する必要が
あったのかといえば現在取り組んでいる研究が原因である。
ニューラルネットワークでよく利用されているWord2vecのような
単語埋め込み表現はWikipediaなどの膨大なテキストファイルを利用して
単語の特徴量を出力する。
Word2vecの場合名前の通り「Word=単語」レベルで特徴量を調べるが
今回取り組んだのは「文字」レベルでの特徴量の抽出である。
日本語の場合ひらがな、カタカナ、漢字と文字の種類が多く、
文字レベルで特徴量を調べても役に立つのではないだろうかと考えた。
こういう理由によってテキストファイルを一文字ずつ分割するプログラムが
必要になりネットで情報を探して自作することになった。終。