9
9

More than 5 years have passed since last update.

可変長のフィールドをLTSVを使う上で悩んでいる

Last updated at Posted at 2013-02-10

LTSVって何?

LTSVとはLabeled Tab-separated Valuesというテキストフォーマットです。
id:naoyaさんのLTSV FAQ - LTSV って何? どういうところが良いの? - naoyaのはてなダイアリーがわかりやすいです。

例えば、
url:http://qiita.com<TAB>title:some title<TAB>tab:LTSV
みたいな、
key1:value1<TAB>key2:value2<TAB>…
という形式で、httpdのログとかに便利だよね、と話題になっています。

LTSVの何が嬉しいの?

  • 特別なパーサがなくても簡単にparseできる。Pythonだと{ elem[0]:elem[1] for elem in [ f.split(":",2) for f in s.split("\t")]}とか
  • 行指向なので、fulentdやMapReduceなどいろいろ扱いやすい
  • ltsviewなんかを使うと人にも見やすくできる
  • ltsv.orgでいろんな言語の実装もある

LTSVで可変長フィールドを扱いたいがどうすればいいんだ

可変長のフィールドがあるとき、どう表現すればいいか悩ましいのです。
例えば、qiitaのtagのようなものであったりとか、HTMLからテキスト取り出して文単位で並べたい場合とか。後者だと順序も保持したくなる。

今回は、TSVの時代にこういう形式で保存されているデータがあったとします。
url<TAB>date<TAB>sentence<TAB>sentence<TAB>…

具体的にはこんな感じ
http://qiita.com<TAB>05/Feb/2013:15:34:47 +0000<TAB>foo<TAB>bar

Idea 1) 可変長のフィールドを1フィールドとしてtabをエスケープする

YAMLで表現するとこうなる。

url: http://qiita.com
date: 05/Feb/2013:15:34:47 +0000
sentence: foo\\tbar

こういうLTSVで保持する。
url:http://qiita.com<TAB>date:05/Feb/2013:15:34:47 +0000<TAB>sentence:foo\\tbar

難点。エスケープが仕様で決まっておらず、parseも面倒になる。
エスケープはあえて仕様に入れていないそうです。

ただ、Rubyのltsv gemとかでもescapeしていたりするので、そのうちデファクトスタンダードができないかなーとも思います。

後、httpdのログにはタブは出ないそうなのですが、一般のテキストになるとタブ出てくる可能性が高いので、そこをどう割り切るかが必要です。

このアイデアの亜種として、CSVのようにダブルクォートでクォートするとかもありますが、これも面倒です。

Idea 2) 同じkeyが出てきたら、配列に入れる

YAMLで表現するとこういう表現に落とせるようにする。

url: http://qiita.com
date: 05/Feb/2013:15:34:47 +0000
sentence:
  - foo
  - bar

こういうLTSVで保持する。

url:http://qiita.com<TAB>date:05/Feb/2013:15:34:47 +0000<TAB>sentence:foo<TAB>sentence:bar

難点。parseが面倒くさくなる。
あと、なんか同じ行に同じラベルがあるのが気持ち悪い、かも。

Ideat 2') 同じkeyが出てきたら、Hashのvalueにタブ区切りで追加する

多分、これはltsv読み込み時にはエスケープされたタブしかないという前提になるんだろう。

入力ltsvはこうする。

input.ltsv
label1:value1\tlabel2:value2-1\tlabel2:value2-2\tlabel3:value3

すると、これくらいでparseできる。

parse.rb
str="label1:value1\tlabel2:value2-1\tlabel2:value2-2\tlabel3:value3"
values = str.split("\t").map{|f| f.split(":", 2)}.inject({}){|h,k| h.has_key?(k[0]) ? h[k[0]] <<"\t" << k[1]  : h[k[0]]=k[1]; h }
values["label2"]                                                                         => "value2-1\tvalue2-2"

dumpするときもこれくらい。

dump.rb
values.inject([]){|s, k| s << (k[1].empty? ? k.join(":") : k[1].split("\t").map{|v2| "#{k[0]}:#{v2}" }) ; s }.join("\t")

多少面倒だけど、なんとかなるレベルか。

Idea 3) label名に番号を付加する

url:http://qiita.com<TAB>date:05/Feb/2013:15:34:47 +0000<TAB>sentence1:foo<TAB>sentence2:bar

これはイヤだ。。。

Idea 4) 可変長のフィールドの行を分ける

url:http://qiita.com<TAB>date:05/Feb/2013:15:34:47 +0000<TAB>sentence:foo<TAB>
url:http://qiita.com<TAB>date:05/Feb/2013:15:34:47 +0000<TAB>sentence:bar

オーバーヘッドが大きくなるから流石にこれはない。

で、どうするか

個人的には、Idea2の方向で拡張かIdea2'だと自分の用途には幸せになれそうという気がしています。
ただ、「特別なパーサがなくてもいいよね」という旨味が減ることが悩み。
この方向でRubyでいうところのネストしたHashやらArrayをどうするのか?とかに進めていくつもりはありません。

お前それjsonで、とか言われそうだけどjsonを1行にしてMapReduceとかね、、、

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9