概要
TSVなどのちょっとした行列データを絞り込んだり、照合したりという処理を楽にするためにツールとJavaライブラリを作ってみた。
利用の前提
JDK 11 で動作確認しています。多分JRE8あたりから動くのではないかと思います。
機能
コマンドラインツール
- 入出力ファイルのエンコード指定
- ファイル先頭のスキップ行数指定
- 列区切り文字の指定
- 出力列の指定
- ソート条件の指定
- SQLのWHEREもどき
- SQLのJOINもどき
Javaライブラリ
コマンドラインツールとほぼ同等ですが、SQLのWHEREもどきの表現力が上がり、Javaプログラムで使いやすくなっています。
利用例
以下は、全てダウンロードパッケージ内にサンプルとして付いていますので、色々試したい方はそちらからどうぞ。
利用するファイル
person.tsv
名前 性別 年齢 住所 趣味
佐藤 男 25 東京 旅行
鈴木 女 12 大阪 映画
山本 男 33 名古屋 工作
cities.csv
都市,人口,平均気温
東京,10000000,22.5
大阪,5000000,24.3
名古屋,3000000,23.1
福岡,1500000,25.7
仙台,1200000,19.2
札幌,1000000,15.5
hobbies.ssv
趣味;イベント;場所
旅行;浅草散策;東京
旅行;博多湾クルーズ;福岡
映画;未公開映画試聴;名古屋
シンプルなJOIN
java -jar select.jar 0@0;2@1 persons.tsv;1 cities.csv;System1Comma;INNER;3@0=0
- 0@0;2@1
- 0番目ファイル0列目と1番目ファイルの2列目を出力
- persons.tsv;1
- 0番目ファイルとしてpersions.tsvを使う
- 先頭1行を読み飛ばす
- cities.csv;System1Comma;INNER;3@0=0
- 1番目ファイルとしてcities.csvを使う
- Systemデフォルト(SJIS)エンコードで、先頭1行を読み飛ばし、列区切りはカンマとする
- 直前のファイルとINNER JOINをする
- 0番目ファイルの3列目とこのファイルの0列目が一致する事をJOIN条件とする
出力
佐藤 22.5
山本 23.1
鈴木 24.3
シンプルなJOIN(列名テキスト版で出力は同じ)
java -jar select.jar -c Text 名前@0;平均気温@1 persons.tsv;0 cities.csv;S0C;INNER;住所@0=都市
シンプルなWHERE
java -jar select.jar -c Text @-0 "persons.tsv;0;性別@=@男;年齢@>=@30"
- @-0
- -は反転マークなので、「0番目ファイルの指定列なし」の反転→0番目ファイルの全列
- persons.tsv;0;性別@=@男;年齢@>=@30
- 0番目ファイルとしてpersons.tsvを使う
- 先頭0行を読み飛ばす
- 性別が男の行だけに絞り込む
- 年齢が30以上の行だけに絞り込む
- ""で囲わないと、不等号がリダイレクトマークと誤認識されて意図した結果にならない
出力
山本 男 33 名古屋 工作
「男」とか「30」とかの絞り込み条件は、だいたい直感と合う様に、テキスト・整数・小数で切り替えてくれます
- 数字だけなら64bit整数(いわゆるlong)とみなす
- それ以外で小数と解釈できれば2倍長浮動小数(いわゆるdouble)とみなす
- それ以外はテキストとみなす
- ファイルの列中で指定の型(整数・小数)に解釈できない行は、絞り込み条件に合致していないとみなす
複雑な例
java -jar select.jar -c Text @-0;都市@-1;イベント@2 persons.tsv;0;性別@=@男 "cities.csv;S0C;平均気温@<@23.0;LEFT;住所@0=都市" hobbies.ssv;0SC;FULL;都市@1=場所;趣味@0=趣味
出力
佐藤 男 25 東京 旅行 10000000 22.5 浅草散策
山本 男 33 名古屋 工作 _NUL_ _NUL_ _NUL_
_NUL_ _NUL_ _NUL_ _NUL_ _NUL_ _NUL_ _NUL_ 博多湾クルーズ
_NUL_ _NUL_ _NUL_ _NUL_ _NUL_ _NUL_ _NUL_ 未公開映画試聴
Javaライブラリとして使って、同等の処理を行うサンプル
try (BufferedReader personReader = Files.newBufferedReader(Path.of("persons.tsv")) ;
BufferedReader cityReader = Files.newBufferedReader(Path.of("cities.csv"), Charset.defaultCharset()) ;
BufferedReader hobbyReader = Files.newBufferedReader(Path.of("hobbies.ssv"))) {
Select.byText().selectAllExcept(0, null).
selectAllExcept(1, "都市").
select(2, "イベント").
from(personReader).where("性別", v -> v.equals("男")).
leftJoin(cityReader).tokenizer(Select.COMMA_TOKENIZER).
whereD("平均気温", v -> v < 23).on(0, "住所", "都市").
fullJoin(hobbyReader).tokenizer(Select.SEMICOLON_TOKENIZER).
on(1, "都市", "場所").on(0, "趣味", "趣味").
execute().
map(t -> Arrays.stream(t).collect(Collectors.joining("\t"))).
forEach(System.out::println);
}
他ツールとの比較
SQLite3などのSQLを使う方が高機能だが
- 簡単・定型的な処理なら、簡単で高速
Linuxのjoinコマンドの方がJava不要で手軽だが
- 事前のソート処理が不要
- 簡易WHERE句による絞り込みが可能
- 複数ファイルの一括JOINが可能
などの違いがある。
ご意見をいただけると助かる点
- 似たようなツールがあれば
- 車輪の再発明を避けるため
- コマンドラインツールの書式の改善案
- 絞り込みで不等号を使うと、””で囲う必要があり、ちょっとイケてない感