M.Hiroi's Home Page

Scala Programming

お気楽 Scala プログラミング入門

[ PrevPage | Scala | NextPage ]

パッケージ

プログラムを作っていると、以前作った関数と同じ処理が必要になる場合があります。いちばんてっとり早い方法はソースファイルからその関数をコピーすることですが、賢明な方法とはいえません。このような場合、自分で作成した関数をライブラリとしてまとめておくと便利です。

ライブラリの作成で問題になるのが「名前の衝突」です。複数のライブラリを使うときに、同じ名前の関数や変数が存在すると、そのライブラリは正常に動作しないでしょう。この問題は「パッケージ (package)」を使うと解決することができます。

●パッケージの宣言

Scala は Java と同様に、パッケージの中に複数のクラス (class) やオブジェクト (object) を定義することができます。パッケージは package 文で宣言します。package 文がない場合は無名のパッケージとして扱われます。package 文の構文を示します。

package 名前 {

  ...

}

Java と違い、Scala は一つのファイルに複数のパッケージを記述することができます。

簡単な例を示しましょう。

リスト : パッケージの使用例 (1)

package foo {
  class Foo {
    val x = 1
  }
}

ファイル foo.scala に package foo を宣言し、その中でクラス Foo を定義します。このプログラムは package の { } を省略して、次のように記述することができます。

リスト : パッケージの使用例 (2)

package foo

class Foo {
  val x = 1
}

scalac で foo.scala をコンパイルすると、サブディレクトリ foo が作成され、その中にコンパイルされた Foo.class が格納されます。クラス Foo を使用するときは、パッケージ名を前に付けて foo.Foo とします。Java と同様に、区切り記号にはドット ( . ) を使います。これを完全修飾名といいます。import 文を使うと、Foo だけで使用することができます。これはあとで説明します。

●パッケージの入れ子

パッケージは入れ子にすることができます。

リスト : パッケージの入れ子 (1)

package foo {
  class Foo {
    val x = 1
  }

  package bar {
    class Bar {
      val y = 2
    }
  }
}

このプログラムも package の { } を省略して、次のように記述してもかまいません。

リスト : パッケージの入れ子 (2)

package foo 

class Foo {
  val x = 1
}

package bar

class Bar {
  val y = 2
}

このプログラムをコンパイルすると、サブディレクトリ foo の中にサブディレクトリ bar が作成され、その中に Bar.class が格納されます。クラス Foo のアクセスは foo.Foo で、クラス Bar のアクセスは foo.bar.Bar となります。

●一つのファイルに複数のパッケージを定義する

Scala は一つのファイルに複数のパッケージを記述することができます。また、次のようにパッケージ名をドットで区切ることもできます。

リスト : 複数のパッケージ

package foo {
  class Foo {
    val x = 1
  }

  package bar {
    class Bar {
      val y = 2
    }
  }
}

package baz {
  class Baz {
    val z = 3
  }
}

package baz.oops {
  class Oops {
    val mes = "oops"
  }
}

Baz.class はサブディレクトリ baz の中に、Oops.class は baz の中のサブディレクトリ oops の中に格納されます。

●import 文

パッケージ内のクラスは import 文を使うと簡単に使用できるようになります。次の例を見てください。

scala> new foo.Foo
val res0: foo.Foo = foo.Foo@6f70a21b

scala> import foo.Foo
import foo.Foo

scala> new Foo
val res1: foo.Foo = foo.Foo@202d9236

scala> import foo.bar._
import foo.bar._

scala> new Bar
val res2: foo.bar.Bar = foo.bar.Bar@58164e9a

scala> new foo.bar.Bar
val res3: foo.bar.Bar = foo.bar.Bar@1edf71d9

クラス Foo は foo.Foo で使用することができます。import foo.Foo とすると、REPL の名前空間に Foo がインポートされて、完全修飾名ではなく Foo だけで使用することができます。アンダーバー ( _ ) を使うと、パッケージに定義されているクラスをすべてインポートすることができます。これは Java の * と同じ機能です。import foo.bar._ でパッケージ foo.bar で定義されているクラスやオブジェクトをすべてインポートすることができます。

●import 文の便利な機能

パッケージの中で特定のクラスだけをインポートすることもできます。

import パッケージ名.{クラス1, クラス2, ... }

{ } の中でインポートするクラスを指定します。簡単な例を示しましょう。

リスト : パッケージ foo1

package foo1

class Foo {
  val x = 1
}

class Bar {
  val y = 2
}

class Baz {
  val z = 3
}
scala> import foo1.{Foo, Bar}
import foo1.{Foo, Bar}

scala> new Foo
val res0: foo1.Foo = foo1.Foo@1835dc92

scala> new Bar
val res1: foo1.Bar = foo1.Bar@7ecda95b

scala> new Baz
           ^
       error: not found: type Baz

scala> new foo1.Baz
val res3: foo1.Baz = foo1.Baz@3002e397

インポートするとき、クラスに別名を付けることもできます。

import パッケージ名.{クラス名1 => 別名, ...}

簡単な例を示しましょう。

scala> import foo1.{Foo => Oops}
import foo1.{Foo=>Oops}

scala> new Oops
val res0: foo1.Foo = foo1.Foo@3aaa3c39

scala> new foo1.Foo
val res1: foo1.Foo = foo1.Foo@519c6fcc

scala> new Foo
           ^
       error: not found: type Foo

また、次のようにアンダーバーと組み合わせて、残りのクラスをすべてインポートすることもできます。

scala> import foo1.{Foo => Oops, _}
import foo1.{Foo=>Oops, _}

scala> new Oops
val res0: foo1.Foo = foo1.Foo@30c3ae63

scala> new Bar
val res1: foo1.Bar = foo1.Bar@35bfa1bb

scala> new Baz
val res2: foo1.Baz = foo1.Baz@6d294ddc

●オブジェクトのインポート

シングルトンオブジェクトをインポートすることもできます。簡単な例を示しましょう。

scala> :paste
// Entering paste mode (ctrl-D to finish)

object Foo {
var x = 1
def getX: Int = x
def putX(a: Int): Unit = x = a
}

// Exiting paste mode, now interpreting.

object Foo

scala> Foo.x
val res0: Int = 1

scala> Foo.getX
val res1: Int = 1

scala> Foo.putX(10)

scala> import Foo._
import Foo._

scala> x
val res3: Int = 10

scala> getX
val res4: Int = 10

scala> putX(100)

scala> x
val res6: Int = 100

アンダーバーを使って、シングルトンオブジェクトに定義されている変数や関数をすべてインポートすることができます。また、インポートする関数や変数を指定することもできます。

scala> :paste
// Entering paste mode (ctrl-D to finish)

object Bar {
val x = 10
val y = 20
def getX: Int = x
def getY: Int = y
}

// Exiting paste mode, now interpreting.

object Bar

scala> import Bar.{getX => getBX, getY}
import Bar.{getX=>getBX, getY}

scala> getBX
val res7: Int = 10

scala> getY
val res8: Int = 20

Bar のメソッド getX は名前を getBX に変えてインポートし、メソッド getY はそのままインポートしています。

ちなみに、print や prntln はシングルトンオブジェクト Predef に定義されています。Scala は Predef を含め 3 つのパッケージ (やオブジェクト) を暗黙のうちにインポートしています。

import java.lang._  // Java の基本クラス
import scala._      // scala の基本クラス
import Predef._     // Predef オブジェクト

●パッケージオブジェクト

パッケージオブジェクトは、パッケージをシングルトンオブジェクトのように扱うための機能です。

package object 名前 {

  ...

}

package object の後ろに名前を指定し、{ ... } の中にフィールド変数やメソッド、クラスを定義します。同じパッケージ内でクラスを定義する場合、パッケージオブジェクトの変数やメソッドは、パッケージ名をつけなくてもアクセスすることができます。

簡単な例を示しましょう。

リスト : パッケージオブジェクト

package pack

package object foo {
  val x = 1
  def getX: Int = x

  class Foo {
    val y = 2
    def foo: Int = x + y
  }
}

pack.foo はパッケージオブジェクトです。クラス Foo は pack.foo に定義されているので、フィールド変数 x は修飾名を付けなくてもアクセスすることができます。上記プログラムは、次のように 2 つのファイルに分割することもできます。

リスト : パッケージオブジェクト (2)

//
// pack.scala
//
package pack

package object foo {
  val x = 1
  def getX: Int = x
}

//
// packFoo.scala
//
package pack.foo {
  class Foo {
    val y = 2
    def foo: Int = x + y
  }
}

クラス Foo はパッケージオブジェクト foo.bar の中で定義されているので、異なるファイルに分割されていても、完全修飾名なしでフィールド変数 x を参照することができます。

パッケージオブジェクトはパッケージと同様に import 文でインポートすることができます。

$ scalac pack.scala
$ scalac packFoo.scala
$ scala
Welcome to Scala 2.13.5 (OpenJDK 64-Bit Server VM, Java 11.0.10).
Type in expressions for evaluation. Or try :help.

scala> import pack.foo._
import pack.foo._

scala> x
val res0: Int = 1

scala> getX
val res1: Int = 1

scala> val a = new Foo
val a: pack.foo.Foo = pack.foo.Foo@6459f4ea

scala> a.foo
val res2: Int = 3

●スコープ限定子

Scala のアクセス制御は、private, protected, 指定無し (public) の 3 通りの方法がありますが、このほかにも「スコープ限定子」という機能があります。これは private, protected のあとに角カッコで公開するクラス (オブジェクト) やパッケージを指定する機能です。

簡単な例を示しましょう。

リスト : スコープ限定子

package foo {
  class Foo {
    private[foo] var x = 1
  }

  class Bar {
    var y = 2
    def getX(a: Foo): Int = a.x
  }
}

object sample1801 {
  def main(args: Array[String]): Unit = {
    val a = new foo.Foo
    val b = new foo.Bar
    println(b.getX(a))
  }
}
$ scalac sample1801.scala
$ scala sample1801
1

private で指定されたクラス Foo のフィールド変数 x は、他のクラスからアクセスすることはできませんが、private[foo] と指定すると、パッケージ foo に定義されているクラスであればアクセスすることができます。したがって、Bar のメソッド getX は Foo のフィールド変数 x を参照することができます。

private[this] というように this を指定をすると、同じインスタンスしかアクセスすることができなくなります。つまり、クラスが同じでも異なるインスタンスであれば、アクセスすることができなくなります。

簡単な例を示しましょう。

リスト : スコープ限定子 (2)

class Foo {
  private[this] var x = 1
  def getX: Int = x
  def putX(a: Int): Unit = x = a

  def foo(): Int = (new Foo).x
}

object sample1802 {
  def main(args: Array[String]): Unit = {
    val a = new Foo
    println(a.getX)
    println(a.foo)
  }
}
$ scalac sample1802.scala
sample1802.scala:6: error: value x is not a member of Foo
  def foo(): Int = (new Foo).x
                             ^
1 error

Foo のメソッド foo は、Foo のインスタンスを生成して、そのフィールド変数 x にアクセスしています。ところが、変数 x にはスコープ限定子 this が指定されているので、メソッド foo は異なるインスタンス (new Foo) の変数 x を参照することはできません。コンパイルでエラーになります。

protected にもスコープ限定子を指定することができます。protected[scope] は、自クラスとそのサブクラス、scope で指定したものにアクセス権限が与えられます。protected[this] は、自クラスについては private[this] と同じですが、サブクラスからアクセスすることは可能です。

●シールドクラス

パッケージと直接関係はありませんが、もう一つアクセス制御の機能を紹介しましょう。class の前に sealed を付けると、そのクラスは「シールドクラス」に設定されます。シールドクラスと同一ファイルにあるクラスは、シールドクラスを継承することができますが、別ファイルのクラスはシールドクラスを継承することができません。

簡単な例を示しましょう。

リスト : 図形のクラス (figure.scala)

package figure

sealed abstract class Figure {
  def kindOf: String
}

case class Triangle() extends Figure {
  def kindOf: String = "Triangle"
}

case class Rectangle() extends Figure {
  def kindOf: String = "Rectangle"
}

case class Circle() extends Figure {
  def kindOf: String = "Circle"
}

抽象クラス Figure はシールドクラスなので、ファイル figure.scala 内では Figure を継承した case クラスを作ることができます。次のように、パターンマッチングを使って図形の種類ごとに個数を数えるプログラムを作ることができます。

リスト : 図形をカウントする (sample1804.scala)

import figure._

object sample1803 {
  def countFigure(xs: List[Figure]): List[Int] = {
    var a = 0
    var b = 0
    var c = 0
    for (x <- xs) {
      x match {
        case Triangle() => a += 1
        case Rectangle() => b += 1
        // case Circle() => c += 1  warning!!
      }
    }
    List(a, b, c)
  }

  def main(args: Array[String]): Unit = {
    println(countFigure(List(new Circle(), new Rectangle(), new Triangle(),
                            new Rectangle(), new Triangle(), new Rectangle())))
  }
}

ここで、パターンマッチングに漏れがあることに注意してください。シールドクラスの場合、パターンマッチングに漏れがあると警告が表示されます。

$ scalac figure.scala
$ scalac sample1803.scala
sample1803.scala:9: warning: match may not be exhaustive.
It would fail on the following input: Circle()
      x match {
      ^
1 warning
$ scala sample1803
scala.MatchError: Circle() (of class figure.Circle)
    ... 省略 ...

警告を無視して実行すると、実行時に MatchError が送出されます。コメントを有効にして、パターンマッチングに漏れをなくすと正常に実行することができます。

$ scalac sample1803.scala
$ scala sample1803
List(2, 3, 1)

また、次のように別ファイルで Figure を継承した case class を作るとコンパイルエラーになります。

リスト : 図形をカウントする (sample1804.scala)

import figure._

case class Pentagon() extends Figure {
  def kindOf: String = "Pentagon"
}

object sample1804 {
  def countFigure(xs: List[Figure]): List[Int] = {
    var a = 0
    var b = 0
    var c = 0
    var d = 0
    for (x <- xs) {
      x match {
        case Triangle() => a += 1
        case Rectangle() => b += 1
        case Circle() => c += 1
        case Pentagon() => d += 1
      }
    }
    List(a, b, c, d)
  }

  def main(args: Array[String]): Unit = {
    println(countFigure(List(new Circle(), new Rectangle(), new Triangle(),
                            new Rectangle(), new Triangle(), new Rectangle())))
  }
}
$ scalac sample1804.scala
sample1804.scala:3: error: illegal inheritance from sealed class Figure
case class Pentagon() extends Figure {
                              ^
1 error

初版 2014 年 9 月 20 日
改訂 2021 年 3 月 28 日

列 (Seq)

今回は「列 (Seq)」について説明します。Seq はシーケンス (sequence) の略で、要素を順番に並べたコレクションのことを「列」といいます。Scala の場合、列は Seq トレイトとして定義されていて、Seq を継承 (Mix-in) しているデータ型であれば、列として統一的に扱うことができます。

●リストと列の違い

列の代表的なデータがリスト (List) ですが、リストのコンス演算子 (::) と連結演算子 (:::) は Seq トレイトにはありません。列の先頭に要素を追加するには演算子 +: を使い、列の末尾に要素を追加するには演算子 :+ を使います。列を連結する場合は演算子 ++ を使います。もちろん、これらの演算子はリストにも適用することができます。

簡単な例を示しましょう。

scala> val a = List(1, 2, 3, 4, 5)
val a: List[Int] = List(1, 2, 3, 4, 5)

scala> 0 +: a
val res0: List[Int] = List(0, 1, 2, 3, 4, 5)

scala> a :+ 6
val res1: List[Int] = List(1, 2, 3, 4, 5, 6)

scala> a ++ List(6, 7, 8, 9)
val res2: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)

●不変 (immutable) な列

不変な列は、リスト以外ではおもに String, Range, Vector などがあります。String と Range は今までもよく使ってきましたが、Vector (ベクタ、ベクトル) は初めでなので簡単に説明しましょう。

リストの要素を参照する場合、セルを順番にたどっていく必要があるので、後ろの要素ほど時間がかかるようになります。Vector はこれを改良したデータ構造で、要素の参照はほぼ一定時間で行うことができるようになっています。他のプログラミング言語、たとえば Scheme では一次元配列のことをベクタと呼びますが、Scala のベクタは配列ではありません。したがって、要素の参照は配列よりも時間がかかります。ご注意ください。

簡単な例を示しましょう。

scala> val b = Vector(1, 2, 3, 4, 5)
val b: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5)

scala> b(0)
val res3: Int = 1

scala> b(4)
val res4: Int = 5

scala> 0 +: b
val res5: scala.collection.immutable.Vector[Int] = Vector(0, 1, 2, 3, 4, 5)

scala> b :+ 6
val res6: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6)

scala> b ++ List(6, 7, 8, 9)
val res7: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> a ++ b
val res8: List[Int] = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)

もちろん、String にも Seq トレイトのメソッドを適用することができます。

scala> val c = "abcdefg"
val c: String = abcdefg

scala> c(0)
val res9: Char = a

scala> c(6)
val res10: Char = g

scala> 'A' +: c
val res11: String = Aabcdefg

scala> c :+ 'Z'
val res12: String = abcdefgZ

●列の生成

列の生成はコンパニオンオブジェクトの apply メソッドだけではなく、次に示すメソッドもコンパニオンオブジェクトに用意されています。

empty                         : 空の列を生成する。
concat(col1, col2, ...)       : 他のコレクションから列を生成する。
fill(大きさ)(値)              : 列を指定した値で満たす。
tabulate(大きさ)(関数)        : 添字に関数を適用して、返り値を列に格納して返す。
iterate(初期値, 大きさ)(関数) : 前の要素に関数を適用して、その返り値を列に格納して返す。
range(開始, 終了, [増分])     : 指定した範囲の数列を生成する。

簡単な例を示しましょう。

scala> List.empty[Int]
val res13: List[Int] = List()

scala> Vector.empty[Int]
val res14: scala.collection.immutable.Vector[Int] = Vector()

scala> List.concat(Vector(1, 2, 3, 4), List(5, 6, 7, 8))
val res15: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8)

scala> Vector.concat(Vector(1, 2, 3, 4), List(5, 6, 7, 8))
val res16: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8)

scala> List.fill(5)(0)
val res17: List[Int] = List(0, 0, 0, 0, 0)

scala> Vector.fill(5)(0)
val res18: scala.collection.immutable.Vector[Int] = Vector(0, 0, 0, 0, 0)

scala> List.tabulate(5)(x => x * x)
val res19: List[Int] = List(0, 1, 4, 9, 16)

scala> Vector.tabulate(5)(x => x * x)
val res20: scala.collection.immutable.Vector[Int] = Vector(0, 1, 4, 9, 16)

scala> List.iterate(1, 5)(x => x * 2)
val res21: List[Int] = List(1, 2, 4, 8, 16)

scala> Vector.iterate(1, 5)(x => x * 2)
val res22: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4, 8, 16)

scala> List.range(1, 8)
val res23: List[Int] = List(1, 2, 3, 4, 5, 6, 7)

scala> Vector.range(1, 8)
val res24: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7)

なお、これらのメソッドは配列 (Array) にも用意されています。

scala> Array.empty[Int]
val res25: Array[Int] = Array()

scala> Array.concat(Array(1, 2, 3), Array(4, 5, 6))
val res26: Array[Int] = Array(1, 2, 3, 4, 5, 6)

scala> Array.fill(10)(0)
val res27: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

scala> Array.tabulate(5)(x => x * x)
val res28: Array[Int] = Array(0, 1, 4, 9, 16)

scala> Array.iterate(1, 5)(x => x * 2)
val res29: Array[Int] = Array(1, 2, 4, 8, 16)

scala> Array.range(1, 10)
val res30: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)

●可変 (mutable) な列

可変な列はおもに ArrayBuffer, ListBuffer, StringBuilder があります。ArrayBuffer は要素を格納するバッファに配列を使っていて、いわゆる「可変長配列」として使用することができます。ArrayBuffer はバッファの末尾に対する操作が得意です。ListBuffer はバッファに連結リストを使っていて、バッファの先頭に対する操作が得意です。StringBuilder は文字列を組み立てるために使います。

可変な列 seq は seq(n) = x で要素を更新できますが、このほかにも要素の追加や削除を行うメソッドが用意されています。

seq += x              : x を末尾に追加する
seq ++= xs            : xs の要素をすべて末尾に追加する
x +=: seq             : x を先頭に追加する
xs +=: seq            : xs の要素をすべて先頭に追加する
seq insert (i, x)     : x を i 番目に挿入する
seq insertAll (i, xs) : xs の要素をすべて i 番目に挿入する
seq -= x              : 要素 x を削除する
seq remove i          : i 番目の要素を削除する
seq remove (i, n)     : i 番目から n 個の要素を削除する
seq trimStart n       : 先頭から n  個の要素を削除する
seq trimEnd n         : 末尾から n  個の要素を削除する

簡単な例を示しましょう。

scala> import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import scala.collection.mutable.{ArrayBuffer, ListBuffer}

scala> val a = new ArrayBuffer[Int]
val a: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()

scala> for (i <- 1 to 5) a += i

scala> a
val res1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5)

scala> a(0)
val res2: Int = 1

scala> a(4)
val res3: Int = 5

scala> a(0) = 100

scala> a
val res5: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(100, 2, 3, 4, 5)

scala> val b = new ListBuffer[Int]
val b: scala.collection.mutable.ListBuffer[Int] = ListBuffer()

scala> for (i <- 1 to 5) i +=: b

scala> b
val res10: scala.collection.mutable.ListBuffer[Int] = ListBuffer(5, 4, 3, 2, 1)

scala> b(0)
val res11: Int = 5

scala> b(4)
val res12: Int = 1

scala> b(4) = 100

scala> b
val res14: scala.collection.mutable.ListBuffer[Int] = ListBuffer(5, 4, 3, 2, 100)

scala> a ++= b
val res15: a.type = ArrayBuffer(100, 2, 3, 4, 5, 4, 3, 2, 100)

scala> a ++=: b
val res16: b.type = ListBuffer(100, 2, 3, 4, 5, 4, 3, 2, 100, 5, 4, 3, 2, 100)

scala> a -= 100
val res17: a.type = ArrayBuffer(2, 3, 4, 5, 4, 3, 2, 100)

scala> a.toArray
val res18: Array[Int] = Array(2, 3, 4, 5, 4, 3, 2, 100)

scala> b -= 100
val res19: b.type = ListBuffer(2, 3, 4, 5, 4, 3, 2, 100, 5, 4, 3, 2, 100)

scala> b -= 100
val res20: b.type = ListBuffer(2, 3, 4, 5, 4, 3, 2, 5, 4, 3, 2, 100)

scala> b.toList
val res21: List[Int] = List(2, 3, 4, 5, 4, 3, 2, 5, 4, 3, 2, 100)

StringBuilder は文字列を組み立てるときに使うと便利です。簡単な例を示しましょう。

scala> val c = new StringBuilder
val c: StringBuilder =

scala> c += 'a'
val res22: c.type = a

scala> c ++= "bcdefg"
val res23: c.type = abcdefg

scala> c.toString
val res24: String = abcdefg

StringBuilder は標準でインポートされているので、簡単に使用することができます。

●ArraySeq

配列は Seq トレイトを継承していませんが、メソッド toIndexedSeq で ArraySeq というコレクションに変換することができます。ArraySeq は Seq トレイトを継承していて、配列を Seq 型のように使うことができます。ArraySeq は mutable と immutable がありますが、toIndexedSeq が返す ArraySeq は immutable のほうです。次の例を見てください。

scala>  val a = Array(1, 2, 3, 4, 5)
val a: Array[Int] = Array(1, 2, 3, 4, 5)

scala> val b = a.toIndexedSeq
val b: IndexedSeq[Int] = ArraySeq(1, 2, 3, 4, 5)

scala> b.toArray
val res0: Array[Int] = Array(1, 2, 3, 4, 5)

ArraySeq は toArray メソッドで配列に戻すことも簡単にできます。

Scala の配列は演算子 ==, != で等値の判定はできませんが、ArraySeq は演算子 ==, != で等値を判定することができます。

scala> val c = Array(1, 2, 3, 4, 5)
val c: Array[Int] = Array(1, 2, 3, 4, 5)

scala> a == c
val res1: Boolean = false

scala> val d = c.toIndexedSeq
val d: IndexedSeq[Int] = ArraySeq(1, 2, 3, 4, 5)

scala> b == d
val res2: Boolean = true

実をいうと、配列は列と同じ演算がサポートされています。次の例を見てください

scala> a
val res5: Array[Int] = Array(1, 2, 3, 4, 5)

scala> a :+ 5
val res6: Array[Int] = Array(1, 2, 3, 4, 5, 5)

scala>  0 +: a
val res7: Array[Int] = Array(0, 1, 2, 3, 4, 5)

scala> a ++ Array(6,7,8,9,10)
val res8: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

参考 URL配列 によると、内部では暗黙の型変換により処理が行われているそうです。初心者にとって、Scala の暗黙の型変換はやっぱり難しいですね。

●列のパターンマッチング

リストと同様に列でもパターンマッチングを行うことができます。簡単な例を示しましょう。

scala> val (x +: xs) = Vector(1, 2, 3, 4, 5)
x: Int = 1
xs: scala.collection.immutable.Vector[Int] = Vector(2, 3, 4, 5)

scala> val (x +: xs) = List(1, 2, 3, 4, 5)
x: Int = 1
xs: List[Int] = List(2, 3, 4, 5)

scala> val (ys :+ y) = Vector(1, 2, 3, 4, 5)
ys: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4)
y: Int = 5

scala> val (ys :+ y) = List(1, 2, 3, 4, 5)
ys: List[Int] = List(1, 2, 3, 4)
y: Int = 5

scala> val (a +: b +: c +: d) = Vector(1, 2, 3, 4, 5)
a: Int = 1
b: Int = 2
c: Int = 3
d: scala.collection.immutable.Vector[Int] = Vector(4, 5)

scala> val (a +: b +: c +: d) = List(1, 2, 3, 4, 5)
a: Int = 1
b: Int = 2
c: Int = 3
d: List[Int] = List(4, 5)

演算子 +: はコンス演算子 (::) と同じように使うことができます。演算子 :+ を使うと、最後尾の要素を簡単に取り出すことができます。

次のように、Seq(...) とマッチングさせることもできます。

scala> val Seq(a, b, c) = Vector(1, 2, 3)
a: Int = 1
b: Int = 2
c: Int = 3

scala> val Seq(a, b, c) = List(1, 2, 3)
a: Int = 1
b: Int = 2
c: Int = 3

scala> val Seq(a, b, c, _*) = Vector(1, 2, 3, 4, 5)
a: Int = 1
b: Int = 2
c: Int = 3

scala> val Seq(a, b, c, _*) = List(1, 2, 3, 4, 5)
a: Int = 1
b: Int = 2
c: Int = 3

_* は 0 個以上の任意の要素とマッチングします。

記号 @ を使うと変数とパターンを同時に設定することができます。これを「as パターン」といいます。

変数名 @ パターン

たとえば、xs @ (y :: ys) と List(1, 2, 3) をマッチングさせると、次のようになります。

xs => List(1, 2, 3)
y  => 1
ys => List(2, 3)

もうひとつ簡単な例を示しましょう。

scala> val Seq(a, b, c, d @ _*) = List(1, 2, 3, 4, 5)
a: Int = 1
b: Int = 2
c: Int = 3
d: Seq[Int] = List(4, 5)

パターン _* とマッチングした値は変数 d にセットされます。

●列の操作メソッド

列からデータを探索する場合、今まで用いてきたメソッド contains, indexOf, find, count はそのまま列に使用することができます。

scala> import scala.collection.mutable.{ArrayBuffer}
import scala.collection.mutable.ArrayBuffer

scala> val a = Vector(1,2,3,4,5,6,7,8)
val a: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8)

scala> val b = ArrayBuffer(10, 20, 30, 40, 50)
val b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(10, 20, 30, 40, 50)

scala> a contains 5
val res0: Boolean = true

scala> a contains 50
val res1: Boolean = false

scala> b contains 5
val res2: Boolean = false

scala> b contains 50
val res3: Boolean = true

scala> a indexOf 5
val res4: Int = 4

scala> a indexOf 50
val res5: Int = -1

scala> b indexOf 5
val res6: Int = -1

scala> b indexOf 50
val res7: Int = 4

scala> a find (_ % 2 == 0)
val res8: Option[Int] = Some(2)

scala> b find (_ % 2 == 0)
val res9: Option[Int] = Some(10)

scala> a count (_ % 2 == 0)
val res10: Int = 4

scala> b count (_ % 2 == 0)
val res11: Int = 5

メソッド reverse で列を反転することもできます。

scala> a.reverse
val res12: scala.collection.immutable.Vector[Int] = Vector(8, 7, 6, 5, 4, 3, 2, 1)

scala> a
val res13: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8)

scala> b.reverse
val res14: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(50, 40, 30, 20, 10)

scala> b
val res15: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(10, 20, 30, 40, 50)

元の列を反転した新しい列が生成されます。

高階関数 map, filter, foldLeft, FoldRight, foreach も使用することができます。

scala> a.map(x => x * x)
val res16: scala.collection.immutable.Vector[Int] = Vector(1, 4, 9, 16, 25, 36, 49, 64)

scala> b.map(x => x * x)
val res17: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(100, 400, 900, 1600, 2500)

scala> a.filter(_ % 2 == 0)
val res18: scala.collection.immutable.Vector[Int] = Vector(2, 4, 6, 8)

scala> b.filter(_ % 20 == 0)
val res19: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(20, 40)

scala> a.foldLeft(0)(_ + _)
val res20: Int = 36

scala> b.foldLeft(0)(_ + _)
val res21: Int = 150

scala> a foreach println
1
2
3
4
5
6
7
8

scala> b foreach println
10
20
30
40
50

リストのように、head で先頭要素を、tail で先頭要素を取り除いた列を、last で末尾の要素を取得することができます。

scala> a.head
val res22: Int = 1

scala> b.head
val res23: Int = 10

scala> a.tail
val res24: scala.collection.immutable.Vector[Int] = Vector(2, 3, 4, 5, 6, 7, 8)

scala> b.tail
val res25: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(20, 30, 40, 50)

scala> a.last
val res26: Int = 8

scala> b.last
val res27: Int = 50

リストの場合、head と tail は高速に動作しますが、他のデータ型がリストと同じように高速に動作するわけではありません。ご注意くださいませ。

●列のソート

列をソートするにはメソッド sortBy, sortWith, sorted を使います。

def sortBy[B](f: (A) => B)(implicit ord: math.Ordering[B]): Seq[A]
def sortWith(lt: (A, A) => Boolean): Seq[A]
def sorted[B >: A](implicit ord: math.Ordering[B]): Seq[A]

sortWith は引数 lt に比較関数を渡して、それを使って列をソートします。sortBy は列の要素に引数 f の関数を適用し、その結果に対してソートを行います。これらのソートはすべて新しい列を返すことに注意してください。

簡単な例を示しましょう。

scala> List(5, 6, 4, 7, 3, 8, 2, 9, 1).sorted
val res0: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> Vector(5, 6, 4, 7, 3, 8, 2, 9, 1).sorted
val res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> import scala.collection.mutable.{ArrayBuffer}
import scala.collection.mutable.ArrayBuffer

scala> val a = ArrayBuffer(5, 6, 4, 7, 3, 8, 2, 9, 1)
val a: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(5, 6, 4, 7, 3, 8, 2, 9, 1)

scala> a.sorted
val res2: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> a
val res3: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(5, 6, 4, 7, 3, 8, 2, 9, 1)

scala> val b = Array(5, 6, 4, 7, 3, 8, 2, 9, 1)
val b: Array[Int] = Array(5, 6, 4, 7, 3, 8, 2, 9, 1)

scala> b.sorted
val res4: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> b
val res5: Array[Int] = Array(5, 6, 4, 7, 3, 8, 2, 9, 1)

mutable な列や配列でも、sorted でソートすると新しい列や配列が生成されます。

要素の一部の値を基準にソートしたい場合は sortBy を使うと簡単です。たとえば、列の要素がタプルの場合、要素を基準にして次のようにソートすることができます。

scala> List(("foo", 30), ("oops", 10), ("baz", 20), ("bar", 40)).sortBy(_._1)
val res6: List[(String, Int)] = List((bar,40), (baz,20), (foo,30), (oops,10))

scala> List(("foo", 30), ("oops", 10), ("baz", 20), ("bar", 40)).sortBy(_._2)
val res7: List[(String, Int)] = List((oops,10), (baz,20), (foo,30), (bar,40))

sortWith に渡す比較関数 lt(x, y) は、x が y よりも小さい場合に真を返すと、列を昇順にソートします。

scala> List(5,6,4,7,3,8,2,9,1).sortWith(_ < _)
val res8: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> List(5,6,4,7,3,8,2,9,1).sortWith(_ > _)
val res9: List[Int] = List(9, 8, 7, 6, 5, 4, 3, 2, 1)

scala> List(("foo", 30), ("oops", 10), ("baz", 20), ("bar", 40)).sortWith(_._1 < _._1)
val res10: List[(String, Int)] = List((bar,40), (baz,20), (foo,30), (oops,10))

scala> List(("foo", 30), ("oops", 10), ("baz", 20), ("bar", 40)).sortWith(_._1 > _._1)
val res11: List[(String, Int)] = List((oops,10), (foo,30), (baz,20), (bar,40))

このほかにも便利なメソッドが多数用意されています。詳細は Scala のリファレンスをお読みください。

●Ordering

Ordering はトレイトで、仮想メソッド compare を定義すると、Ordering のメソッドを使用することができます。

abstract def compare(x: T, y: T): Int  // 大小関係を符号 (負, 0, 正) で返す
def equiv(x: T, y: T): Boolean   // x == y
def gt(x: T, y: T)   : Boolean   // x > y
def gteq(x: T, y: T) : Boolean   // x >= y
def lt(x: T, y: T)   : Boolean   // x < y
def lteq(x: T, y: T) : Boolean   // x <= y

sorted や sortBy に Ordering を渡す場合は、無名のクラスを使うと簡単です。次の例を見てください。

scala> List(5, 6, 4, 7, 3, 8, 2, 9, 1).sorted(new Ordering[Int] {
     | def compare(x: Int, y:Int): Int = (x - y).sign
     | })
val res12: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> List(5, 6, 4, 7, 3, 8, 2, 9, 1).sorted(new Ordering[Int] {
     | def compare(x: Int, y:Int): Int = (y - x).sign
     | })
val res13: List[Int] = List(9, 8, 7, 6, 5, 4, 3, 2, 1)

scala> List(5, 6, 4, 7, 3, 8, 2, 9, 1).sorted(new Ordering[Int] {
     | def compare(x: Int, y:Int): Int = y - x
     | })
val res14: List[Int] = List(9, 8, 7, 6, 5, 4, 3, 2, 1)

以前のバージョンでは、整数の符号 (-1, 0, 1) を求めるときメソッド signum を使いましたが、ver 2.13.0 から非推奨になりました。かわりにメソッド sign を使ってください。なお、compare は sign で -1, 0, 1 に変換しなくても、負, 0, 正 を返すだけで動作するようです。

●参考 URL

  1. COLLECTIONS - Scala Documentation

初版 2014 年 9 月 20 日
改訂 2021 年 3 月 28 日

Copyright (C) 2014-2021 Makoto Hiroi
All rights reserved.

[ PrevPage | Scala | NextPage ]