LL脳がscalaの勉強を始めたよ その54


Scalaコップ本16章の続きをやっていきますよー、今回はリストの高階メソッドの前半ですね

高階メソッドはパラメーターに関数をとるようなメソッドですね。リストの各要素を特定のルールで変換するとか、特定の要素だけ抽出するとか、なんかそんな感じの処理をするみたいです

ま、とりあえずやっていきましょうかね

リストの各要素のマッピング(変換):map, flatMap, foreach

要素変換系のメソッドを粛々と進めていきますよ

map

リストの各要素に対して関数を適用させるメソッドとしてmapがありますね、形式はパラメータとして関数を渡すかんじですねー

とりあえずサンプルはこんな感じですね

// リストの各要素に対して1を加えます
scala> List(1,2,3).map(_ + 1)
res0: List[Int] = List(2, 3, 4)

// リストの各要素(文字)の長さを取得します
scala> List("one", "two", "three").map(_.length)
res1: List[Int] = List(3, 3, 5)

// リストの各要素(文字を逆順にします)
// toListでList[Char]に変換して逆順に並べ替えますね
scala> List("one", "two", "three").map(_.toList.reverse.mkString)
res2: List[String] = List(eno, owt, eerht)
flatMap

flatMapはmapと同じような処理を行うみたいですが、結果値を一つの連結したリストとして返す部分がちょっと異なるみたいです。

具体的なサンプル見てみますかねー

// リストの各要素をchar型に分割します
scala> List("one", "two", "three").flatMap(_.toList)
// 処理結果となるList(o, n, e), List(t, w, o), List(t, h, r, e, e)を結合してますね
res3: List[Char] = List(o, n, e, t, w, o, t, h, r, e, e)

これをmapでやるとこんな感じですね

scala> List("one", "two", "three").map(_.toList)
// こちらは結合されません
res4: List[List[Char]] = List(List(o, n, e), List(t, w, o), List(t, h, r, e, e))


mapとflatMapを連携させるサンプルとしてはこんなのがあるみたいですね。ちなみに指定範囲の整数リストを作成するユーティリティメソッドrangeを使っておりますです。

//// j < i < 5になる全ての(i, j)ペアリストを作成しますよ
// range(1,5)で1以上5未満の整数リストを生成して処理を開始します
// 実際には2行目の繰り返し演算結果を一つのリストとして返します
List.range(1,5).flatMap(
  // 実際の処理です
  // 外側から渡されたパラメータiについて
  // 1以上i未満の整数リストを生成して
  // パラメータと生成した整数リストを基にペアを生成します
  i => List.range(1, i).map(j =>(i,j))
)

// 結果です
res7: List[(Int, Int)] = List((2,1), (3,1), (3,2), (4,1), (4,2), (4,3))

rangeなんかはPythonなんかでもよく使うアレですな。List.range(a,b)形式で使うっとc⌒っ゚д゚)っφ メモメモ...ちなみに上の式は次のようなfor式でも同じようなことが出来るらしいんですけどね

scala> for(i <- List.range(1,5); j <- List.range(1,i)) yield (i,j)
res11: List[(Int, Int)] = List((2,1), (3,1), (3,2), (4,1), (4,2), (4,3))

yieldスキー(ココ最近のMy流行り)としてはこっちの方が好みかもしれないなぁ(´・ω・`) for式とリストの連携については23章でやるそうデス

foreach

mapやflatMapはパラメータとして関数をわたしてましたがforeachは手続き(結果がUnit)を渡すみたいですね。しかもforeachの結果はUnit型になるので結果をListで取れない…取れないのか(´・ω・`)

とりあえずこんな感じの処理になるですかね

// mutableな変数を用意しますね
scala> var sum = 0
sum: Int = 0
// mutable変数にリスト要素を次々と加えていきますね
scala> List(1,2,3).foreach( sum += _)
// 結果です
scala> sum
res13: Int = 6

まあ、昔ながらのループ処理をList要素に適用した感じなのかしら?Unit型の処理はちょっと気持ち悪いなぁ…と思うようになってきたのはいい傾向なのかも…なのかな?

リストのフィルタリング:filter,partition,find, takeWhile,span

リスト要素の抽出を行うような各要素をザザッと進めていきますよー

filter

パラメータとして渡した論理式に適合する要素を抽出するのがfilterですね。サンプルはこんな感じになるみたいですね。

// リストの要素から偶数だけを抽出してみます
scala> List(1,2,3,4,5).filter(_ % 2 == 0)
res14: List[Int] = List(2, 4)

// 文字列の長さが3になるものを抽出
scala> List("one", "two", "three").filter(_.length == 3)
res15: List[java.lang.String] = List(one, two)
partition

filterでは条件適合要素のみを抽出するのですが、partitionを使うと(条件適合要素リスト、条件不適合要素リスト)という結果を返すので条件に合わない要素も一緒に取得することができますね

例として上でfilterを使ったサンプルをpartitionに置き換えてみますよ

// 偶数要素を抽出
scala> List(1,2,3,4,5).partition(_ % 2 == 0)
// 偶数、奇数のセットが返ってきますね
res17: (List[Int], List[Int]) = (List(2, 4),List(1, 3, 5))

// 文字列長が3のものを取得します
scala> List("one", "two", "three").partition(_.length == 3)
// 条件にあうもの、あわないものがセットで取得できます
res18: (List[java.lang.String], List[java.lang.String]) = (List(one, two),List(three))

うん、いちいち振り分け処理書かなくてよさそうで便利ですな(`・ω・´)

find

条件に合う全ての要素はいらないよ!っていうときはfindを使うといいのかも知れないですね。findは条件を満たす最初の要素を返してくれマスです。戻りはSome(x)で値が存在しない場合はNoneですねー

// 条件に適合する最初の要素がSome(x)で返ってきますね
scala> List(1,2,3,4,5).find(_ % 2 == 0)
res19: Option[Int] = Some(2)

// 要素が存在しない場合はNoneが返ってきますね
scala> List(1,2,3,4,5).find( _ < 0)
res20: Option[Int] = None
takeWhile

takeWhileは条件に合う要素を先頭から順にとれるだけとって、条件に満たない要素が存在したらそこで終了ですね。なので条件不適格要素が途中にあると、その後に条件的確要素がいくらあってもtakeしてくれないデス

ちょっと動きを見てみますかね

// 先頭から順に条件に合う要素を取得します
scala> List(1,2,3,4,5).takeWhile(_ < 3)
res33: List[Int] = List(1, 2)

// 途中に条件に満たない要素があるとそこで処理を終了しマス
// うしろの1,2はtakeされないです
scala> List(1,2,3,4,5,1,2).takeWhile(_ < 3)
res35: List[Int] = List(1, 2)

まあ単純に「条件にあう要素」を取りたければfilter使えってことで

dropWhile

dropWhileはtakeWhileの捨てる版です。先頭から順に条件を満たす要素を捨てていきますね。こちらもtakeWhileと同様に途中で不適格要素が出たら処理を終了しますデス

// 頭文字oの要素を捨てますよ
scala> List("one", "two", "three").dropWhile(_.startsWith("o"))
res36: List[java.lang.String] = List(two, three)

// 頭文字tの要素を捨てますが、先頭が条件外なので処理が止まります
scala> List("one", "two", "three").dropWhile(_.startsWith("t"))
res37: List[java.lang.String] = List(one, two, three)
span

takeとdropをまとめた処理としてsplitAtがあったのですが、同様にtakeWhileとdropWhileをまとめた処理としてspanがありますね

spanはtakeWhileした結果とdropWhileした結果をペアにして返しますよ

scala> List(1,2,3,4,5,1,2).span(_ < 3)
// (takeWhileの結果, dropWhileの結果)が帰ってきますね
res38: (List[Int], List[Int]) = (List(1, 2),List(3, 4, 5, 1, 2))

関係性はこんな感じですかね

scala>  List(1,2,3,4,5,1,2).span(_ < 3)  ==  (List(1,2,3,4,5,1,2).takeWhile(_ < 3),  List(1,2,3,4,5,1,2).dropWhile(_ < 3))
res41: Boolean = true

以上ー

テネイシャスDにやられたせいで思ったより進まなかったです(´・ω・`)。次回は高階関数の続きリストを対象とする述語関数からやりますよー