先行事例
整数配列の連続区間をハイフンで連結してグループ化する定番のアレを「Scalaでできないか?」って話題で盛り上がってたので、挑戦してみました。
たどり着いた答え(数字のみ)
scala> val sample = Seq(1,2,3,4,5,8,9,10,13,14,20,22)
sample: Seq[Int] = List(1, 2, 3, 4, 5, 8, 9, 10, 13, 14, 20, 22)
scala> sample.foldLeft(Seq((1, 0)))((z, n) => if((z.head._2 + 1) == n) (z.head._1, n) +: z.tail else (n, n) +: z).reverse
res0: Seq[(Int, Int)] = List((1,5), (8,10), (13,14), (20,20), (22,22))
解説
ぶっちゃけ関数型まだ慣れきってなくてちゃんと正しい処理してるかわからないのだけれども。たぶん、大丈夫。
ポイント
何をしてるか
前提として:与えられる数値のリストはソートされてるものとします。(されてなければソートしてください)
連続する数値をその最小値と最大値で表すタプル (min, max)
を作って行きます。
まず最初にfoldLeftの初期値として1個目を与えてます。これは本当なら連続を確かめたいListの (最小値, 最小値-1)
じゃないといけないと思う。いくつの連続ができるかわからないですがとりあえずタプルを含むSeqにしてます。
さて、このタプルのSeqが z
、そして一つ目の要素が n
として与えられるので、先頭に入っているタプルの max
に1を足してみてnと比較します。もし等しいなら前の数値より一つ大きいということになるので、数値が連続していると判断します。
数値が連続していた場合にはSeqの先頭のタプルを破棄して、代わりに最小値はそのままだが最大値をnに置き換えたタプルを作って、先頭にくっつけます。
もし数値が連続していなかった場合は、既存のタプルはそのままに新しくnからnまでの連続した数値、ということにしてタプルを生成して、同じく先頭にくっつけます。
さて、一つの要素について処理を終えると、そこには例のタプルをいくつか含んだSeqとまだ処理されてない要素が残ります。
このSeqに対してまたmaxと次のnを比較する処理を、要素が空になるまでひたすらに繰り返しています。
最終的に、最大値と最小値を表すタプルをいくつか含んだSeqが完成します。
varとか使いたくなかったので、タプルを作っては捨て、作っては捨て、とカジュアルにぶん投げてるあたりが無理矢理で気に入ってます。
最後に
scala> sample.foldLeft(Seq((1, 0)))((z, n) => if((z.head._2 + 1) == n) (z.head._1, n) +: z.tail else (n, n) +: z).reverse.map(t => if(t._1 != t._2) t._1 + "-" + t._2 else t._1).mkString(",")
res6: String = 1-5,8-10,13-14,20,22