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


Scalaコップ本の17章の残りをやっていきますよー、とりあえず今回で17章終われるように頑張ります

コレクションの初期化

コレクションを初期化する最も一般的な方法は、コレクションの生成時に要素を渡してやることですネー。実際の要素渡し生成ではコンパイラが内部的にファクトリーメソッドであるapplyメソッドを呼び出してコレクションをつくるみたいです。

// 配列の生成です
scala> Array(1.0, 2.0, 3.0)
res0: Array[Double] = Array(1.0, 2.0, 3.0)

// リストの生成です
scala> List(1, 2, 3)
res1: List[Int] = List(1, 2, 3)

// 集合を生成します
scala> Set('a', 'b', 'c')
res2: scala.collection.immutable.Set[Char] = Set(a, b, c)

// ミュータブル版のマップです
scala> import scala.collection.mutable
import scala.collection.mutable
scala> mutable.Map('a' -> 1, 'b'->2, 'c'->3)
res3: scala.collection.mutable.Map[Char,Int] = Map(a -> 1, c -> 3, b -> 2)

// 内部的にはこういう呼出になりますけどね(´・ω・`)
scala> List.apply(1, 2, 3)
res4: List[Int] = List(1, 2, 3)

ちなみにファクトリーメソッドに渡した要素の型がコレクションの要素型になるのですが、ファクトリーメソッドに渡した値以外の型をコレクション要素として使いたい場合は次のようにすればいいみたいですね。

// ミュータブル版でやってみますね
scala> import scala.collection.mutable
import scala.collection.mutable

// コレクション生成時に要素型を指定します
// ここでは要素型としてAnyを指定します(パラメータはInt)
scala> val anySet = mutable.Set[Any](38)
anySet: scala.collection.mutable.Set[Any] = Set(38)

// String型の要素を追加します
scala> anySet += "Hello Any"
// 無事追加できました
scala> anySet
res6: scala.collection.mutable.Set[Any] = Set(Hello Any, 38)

//// 失敗例デス
// 型指定をしないと Set[Int]で初期化されます
scala> val anySet = mutable.Set(38)     
anySet: scala.collection.mutable.Set[Int] = Set(38)

// String型要素を加えると当然エラーになりますな
scala> anySet += "Hello Any"            
<console>:7: error: type mismatch;
 found   : java.lang.String("Hello Any")
 required: Int
       anySet += "Hello Any"
                 ^ 

上記のような問題の多くはミュータブル版でのことみたいですが、覚えておかないと大変そうですな(´・ω・`)


ついでにコレクションを他の型のコレクションで初期化(再生成?)するときは空のコレクションを生成して++演算子なんかで要素を加えるような感じになるみたいですねー、サンプルとしてリストを格納するTreeSetを作る処理を書いてみますよー

// TreeSetをインポートします
scala> import scala.collection.immutable.TreeSet
import scala.collection.immutable.TreeSet

// 変換元のリストを用意しますよ
scala> val nums = List(1,2,3,4,5)
nums: List[Int] = List(1, 2, 3, 4, 5)

// 空のTreeSetを作成して要素を突っ込めばOKです
scala> val treeSet = TreeSet[Int]() ++ nums
treeSet: scala.collection.immutable.SortedSet[Int] = Set(1, 2, 3, 4, 5)

//// 失敗例をやってみます
// 変換元のリストです
scala> val nums = List(1,2,3,4,5)
nums: List[Int] = List(1, 2, 3, 4, 5)

// applyメソッドを使うと…違う種類だってばYO!って怒られますな
scala> val treeSet = TreeSet(nums)        
<console>:6: error: no implicit argument matching parameter type (List[Int]) => Ordered[List[Int]] was found.
       val treeSet = TreeSet(nums)
                     ^
// これとは違う解釈ですからしゃーないですけども
scala> val treeSet = TreeSet(1,2,3,4,5)
treeSet: scala.collection.immutable.SortedSet[Int] = Set(1, 2, 3, 4, 5)
配列やリストへの変換

ほとんどのコレクション変換は上のように若干遠回りな方法が必要なんですが、でリストや配列への変換はメソッドが用意されているので気軽にできまっせ!とのことです。

リストへの変換はtoList、配列への変換はtoArrayデス。そんなわけでサンプルやってみますかねー

// 変換元としてTreeSetを使ってみますよ
scala> import scala.collection.immutable.TreeSet
import scala.collection.immutable.TreeSet
// TreeSetを生成
scala> val treeSet = TreeSet(3,4,2,7,12,1,9)
treeSet: scala.collection.immutable.SortedSet[Int] = Set(1, 2, 3, 4, 7, 9, 12)

// リストに変換します
scala> treeSet.toList
res9: List[Int] = List(1, 2, 3, 4, 7, 9, 12)

// 配列に変換します
scala> treeSet.toArray
res10: Array[Int] = Array(1, 2, 3, 4, 7, 9, 12)

変換済みのリストや配列でも、元のコレクションがTreeSetだったのできちんとソートされておりますなー。ちなみにコレクションのリスト・配列への変換は全要素のコピーが発生するのでコレクションのサイズに応じて時間がかかるので要注意や!とのことです。

集合・マップのミュータブル版といミュータブル版の相互変換

ミュータブル版・イミュータブル版集合の相互変換は上のほうでやったList→TreeSetの変換と同様に、一度空の集合を生成して要素をついかすることで行えマス。実際の操作はこんな感じです。

// ミュータブル版の呼び出し
scala> import scala.collection.mutable
import scala.collection.mutable

// ミュータブル版の集合を定義します
scala> val mutableSet = mutable.Set(1,2,3,4,5)
mutableSet: scala.collection.mutable.Set[Int] = Set(5, 3, 1, 4, 2)

// イミュータブル版への変換を行ないます
// emptyメソッドで空の集合を生成して要素の追加を行ないます
scala> val immutableSet = Set.empty ++ mutableSet
immutableSet: scala.collection.immutable.Set[Int] = Set(5, 3, 1, 4, 2)

// 今度はミュータブル版への変換をやってみますよ
scala> val reMutableSet = mutable.Set.empty ++ immutableSet
reMutableSet: scala.collection.mutable.Set[Int] = Set(5, 3, 1, 4, 2)

せっかくなんでマップの相互変換もやってみますかねー、手順自体は一緒です

// ミュータブル版の呼出です
scala> import scala.collection.mutable
import scala.collection.mutable

// ミュータブルマップを生成しますよ
scala> val mutableMap = mutable.Map('a'->1, 'b'->2, 'c'->3)
mutableMap: scala.collection.mutable.Map[Char,Int] = Map(a -> 1, c -> 3, b -> 2)

// イミュータブルマップに変換します
scala> val immutableMap = Map.empty ++ mutableMap
immutableMap: scala.collection.immutable.Map[Char,Int] = Map(a -> 1, c -> 3, b -> 2)

// ミュータブルマップに再変換します
scala> val reMutableMap = mutable.Map.empty ++ immutableMap
reMutableMap: scala.collection.mutable.Map[Char,Int] = Map(a -> 1, c -> 3, b -> 2)

タプル

異なる型の要素を作るタプルについてやりますよー、タプルの例としてはこんな感じですかね

// 数字、文字列、コンソールを保持しております
// コンソールは対話型コンソールのオブジェクトかしら?(´・ω・`)
scala> (1, "Hello", Console)
res12: (Int, java.lang.String, object Console) = (1,Hello,scala.Console$@8b7250)

タプルは異なる型の要素を保持するのでIterableは継承しないみたいです。

タプルの使用法

タプルがよく使われるのはメソッドから複数の値を返す時みたいです。例えばこんなふうに使われますねー

// 渡された文字列コレクション内の最も長い文字列を求めますよ
// 戻りの値は該当文字列とその値です
def longestWord(words:Array[String]) = {
  // 初期設定で先頭文字を最長文字として登録
  var word = words(0)
  var idx = 0
  // コレクション要素ごとに比較
  for (i <- 1 until words.length){
    // 最長文字列よりも長いものがあれば再代入
    if(words(i).length > word.length){
      word = words(i)
      idx = i 
    }
  }
  // 戻りは(最長文字列, その添字)
  (word, idx)
}

// 実行しますよー、文字列を分割して配列として渡します
scala> val longest = longestWord("Hello world worldwide".split(" "))
// 結果がタプルで帰ってきましたね
longest: (String, Int) = (worldwide,2)

タプルの要素にアクセスするときは" _<要素番号> " という形式でアクセスするみたいです

// タプル定義です
scala> val taple = ('a', 1, "Hello")
taple: (Char, Int, java.lang.String) = (a,1,Hello)

// 第1要素にアクセスしますよ 
scala> taple._1
res13: Char = a
// 第2要素にアクセスしますよ
scala> taple._2
res14: Int = 1
// 第3要素にアクセスしますよ
scala> taple._3
res15: java.lang.String = Hello

// 要素がないと怒られますね(´・ω・`)
scala> taple._4
<console>:6: error: value _4 is not a member of (Char, Int, java.lang.String)
       taple._4
             ^

また次のような方法を使えばタプルの要素をダイレクトに変数に突っ込めますです

scala> val taple = ('a', 1, "Hello")
taple: (Char, Int, java.lang.String) = (a,1,Hello)

scala> val (charA, num1, greeting) = taple
charA: Char = a
num1: Int = 1
greeting: java.lang.String = Hello

ちなみに()を省略すると同時定義(複数の変数に右辺の結果を同時に代入)になってしまうので気をつけないといけないですな(´・ω・`)

scala> val taple = ('a', 1, "Hello")
taple: (Char, Int, java.lang.String) = (a,1,Hello)

// 同時定義になってしまいます
scala>  val charA, num1, greeting = taple
charA: (Char, Int, java.lang.String) = (a,1,Hello)
num1: (Char, Int, java.lang.String) = (a,1,Hello)
greeting: (Char, Int, java.lang.String) = (a,1,Hello)

ちなみにタプルは複数の型要素を組み合わせられるけど、組み合わせに何らかの意味(日付の年月日とか)がある場合はクラスを作ったほうが良いです!とコップ本がおっしゃっておりました。そのほうが他のプログラマーに意図が伝わりやすいとのこと…たしかになぁ(´・ω・`)

以上ー

とりあえず17章のコレクションは終わりです。次は18章のステートフルオブジェクトをやります(´・ω・`)いつになったら終わるかなぁ、が、頑張ります