ブログが続かない人のブログ

技術ネタについて書いていきたいです。

Scalaわかんなすぎて死ぬ

Scalaを最近触ってるけど、奥が深いっすね...
Scalaがどうこうというより、関数型のセンスがない疑惑が浮上してる。

奥が深いと思った例題があるので、見たい人だけ見て下さい。

val scores = Map[String, Map[String, Int]]

という変数を用意する。これには生徒のテストの成績が入ってて、名前->(科目名->点数)という構造をしている。
ここから英語と数学の平均点が80点以上の生徒を取り出し、名前->平均点のMapとして出力したい。

ここでは5パターンの解法を紹介する。

1. filterを使う

filterメソッドを使って単純に絞り込んで行く方法。

scores.filter {
	case (_, score) => score.contains("math") && score.contains("english")
    	} filter {
    	case (_, score) => (score("math") + score("english")) / 2 >= 80
    	}  map {case (name, score) => (name, (score("math") + score("english")) / 2)}

って感じ。filterで内包表記が使えるの知らなかったからけっこう苦労した。平均点だす式はどこか別で宣言しておきたい。

2. mapValuesを使う

値を更新した新しいMapを返してくれるmapValuesメソッドを使って書く方法。

scores.filter(m => m._2.contains("math") && m._2.contains("english") 
  		    && (m._2("math") + m._2("english"))/2 >= 80).mapValues(m => (m("math") + m("english"))/2)

さっきよりも格段に短く見やすくなりました。あまり目新しいことはやってなくて、ただわかりやすく書き直したって感じ。

3. collectメソッドを使う

collectメソッドを使うと、もっと簡単になります。

val getAverage: PartialFunction[(String, Map[String, Int]), (String,Int)] = {
    		  case (name, m) if m.contains("math") && m.contains("english") 
    		    && (m("math") + m("english"))/2 >= 80 =>(name, (m("math") + m("english"))/2)
    		}
scores.collect(getAverage)

ここで新しくでてくるのはPartialFunctionとかいうものですね。これは特定の引数のときだけ値を返してきて、それ以外は返さないというものです。PartialFuntionの型の書き方は、[引数、戻り値]です。
PartialFunctionの中身のcaseに当てはまる形のものがあった場合のみ、式が実行され、結果を返します。

4. flatMapを使う

flatMapは、要素x1, x2, x3, ...に対して、f(x1), f(x2), f(x3),...を計算し、それらを連結したf(x1)++f(x2)++f(x3)++...を返します。

scores.flatMap {
    	case (name, score) =>
        score.get("math") flatMap {
        math =>  
          score.get("english") flatMap {
            english => 
               val avg = (math + english) / 2
               if(avg >= 80) Some((name, avg)) else None
          }
        }

まず、最初のflatMapで(name, score)のタプルを持ってきて、get("math")をすることでscoreの中でmathの点数を持っている要素をOptionで取り出す。そしてflatMapで要素を持っているものだけに関数を適用させる。
これをenglishにも適用させ、最後に名前と平均点のタプルをOptionとして返している。Optionにしているのは、邪魔な物を排除するためです。

分かりやすいような分かりにくいようなって感じですが、flatMapはかなり使うらしいので、flatMapの練習にはちょうどいいのかもしれないですね。

5. for文

え、for文?って思うかもしれませんが、for文です。

for{
    (name, score) <- scores
     math <- score.get("math")
     english <- score.get("english")
     val ave = (math + english) / 2
     if (ave >= 80)
} yield (name, ave)

4番とやってることは変わってません。(name, score)のタプルを持ってきて、math, englishの点数を持ってきて、平均出してyieldで返してます。

まぁ一番すっきりしてるっちゃしてますね。



ここまで5パターン見ていただきましたが、簡単に書こうと思えばいくらでも書き方がある感じです。自在にそのときの状況に応じて使いこなせるようになれたらかっこいいですね。
一刻も早くScalaに慣れたい人生でした。