ページ

2012-11-22

Scalaで2行ずつ処理する (修正済)


Scalaで2行ずつ処理するお話。

ちょっとまずいところがあったので以下の点を修正しました。
Twitterで@rirakkumyaさんに指摘されて理解しました。ありがとうございます!!

修正点
ファイルを読み込む際にgetLines()した後にいきなりtoList()を行なっていた処理を削除した。
なぜ?
getLines()で返ってくるのはIteratorなので、ファイルへの参照を持っているだけでメモリに中身はロードされない。

toList()を行うとファイルの全て読み込みListに変換しようとする。この時巨大なファイルだと、メモリが溢れて実行できなくなる。

Iteratorに対する操作ならIteratorに対して操作が行われるので、取り扱う集合が無限に大きくても問題無さそうなのが凄いと思いました。

修正終了。

例えば下のようなファイルがあるとします。

1 11123343 hogehoge
2 31241534 hugahuga
3 13234124 hogehoge
4 14415214 hugahuga
view raw data.csv hosted with ❤ by GitHub
こいつの2個目の要素を2行毎に引き算したい場面があるとします。
例)31241534 ー 11123343

例えば処理時間とか2点間の時刻をロギングした後に、その差を求める的な場面はよくあると思います。

一要素ずつ順番に処理をするなら特に気にならないのですが、ひとつ飛ばしになった瞬間急激に気持ち悪くなる病気に罹ってるのでScalaでなんか良い感じに書けないのかなーっと思ってついったーで呟いてみました。

しばらくしたら「grouped(2)ってのはどうでしょう?」というリプライが来ました!!

ありがたやありがたや。

groupedを使って書いたら、ひとつ飛ばしの部分がなくなったので非常に気分が爽快になりました!!

下のような感じのコードです。
本気出せばgoupedとforeachだけで行けそうですね。

import scala.io.Source
// 引数で指定されたargs(0)のファイルを読み込む(引数指定しないと例外が吐かれる)
// ここでいきなりtoList()をやるとファイルの内容がメモリに読み込まれて巨大なファイルの場合、悲しいことになる
val lines = Source.fromFile(args(0)).getLines.filter(_.length > 0) /* 改行のみの行を削除 */
lines.map{
/* カンマで分割 */
_.split(",")
} map {
/* あるべき型に変換してタプルにする */
l => (l(0).toInt, l(1).toLong, l(2))
} grouped(2) map {
/* タプルは1始まり */
/* 差を求めて */
pear => pear(1)._2 - pear(0)._2
} foreach {
/* 表示して終わり */
println
}
//20118191
//1181090
view raw CalcDiff.scala hosted with ❤ by GitHub

あと、途中でListとIteratorの違いで嵌りました。

ただ単純に処理を書いてるだけだったらよかったのですが、途中で要素数を調べたりしてたら。急に中身が0個になったりしたりして?????ってなりました。

これもまた、ついったーで呟いたら、「それはListじゃなくてIteratorだよ」って言われました。

よくよく見てみるとその通りでした。
ちゃんと型を見ないとダメですね・・・

Iteratorの場合はsizeとかで一度、走査してしまうとそれ以後はsizeが0になってしまいます。
一番後ろまで行っちゃうのですから当然の話しですね・・・

ってことで、何回も弄り回したい時はtoListを使ってちゃんとListにしておけば良いそうです。
※ただし、ここでのtoList()はファイルの中身が全てロードされてしまうので危険。どうしてもListで欲しい時に限ってListにしたほうが良い。もしくはtakeメソッドを使って少しずつ読む。

今日はいろいろ勉強になって捗った一日でした。

もっとこういうのをサクッと書けるようになりたいです。

0 件のコメント:

コメントを投稿