1. 概要

このチュートリアルでは、Scalaのマップについて学習します。 キーと値のペアを保存する方法、特定のキーの下の値を取得、更新、および削除する方法を説明します。 次に、マップを変換するいくつかの方法について説明します。

2. マップ

ScalaのMapは、キーと値のペアのコレクションであり、各キーは一意である必要があります。 そのおかげで、特定のキーの下の値に直接アクセスできます。

Scalaは、デフォルトで使用される immutable と、インポートscala.collection.mutable.Mapが必要なmutableの2種類のマップを定義します。

3. マップの作成

このセクションでは、マップを作成するためのさまざまなオプションのいくつかを見ていきます。

3.1. 空の

空のMapを作成することから始めましょう。これは、デフォルト値として使用できます。 Mapコンパニオンオブジェクトでemptyメソッドを使用できます。

val emptyMap: Map[Int, String] = Map.empty[Int, String]

空のマップを作成する別の方法は、applyメソッドを使用することです。

val emptyMap: Map[Int, String] = Map[Int, String].apply()

Scalaには、このための特別な構文糖衣があり、括弧のみを使用してそれを呼び出すことができます。

val emptyMap: Map[Int, String] = Map[Int, String]()

3.2. 空でない

空でないMapを作成する場合は、 apply メソッドを使用して、引数としてkey-value tuplesを渡すことができます。

val map: Map[Int, String] = Map.apply(1 -> "first", 2 -> "second")

applyメソッドにシンタックスシュガーを使用することもできます。

val map: Map[Int, String] = Map(1 -> "first", 2 -> "second")

マップを作成するもう1つの非常に一般的な方法は、リストマップ:に変換することです。

val map: Map[Int, String] = List(1 -> "first", 2 -> "second").toMap
map shouldBe Map(1 -> "first", 2 -> "second")

Map を作成するこの方法は非常に人気があるため、専用のtoMapメソッドもあります。 リストにタイプTuple2の要素が含まれている場合にのみ機能します。

val map: Map[Int, String] = List(1, 2).toMap

そうしないと、コンパイラは次のメッセージで失敗します。

[error] Cannot prove that Int <:< (T, U). 
[error] val map: Map[Int, String] = List(1, 2).toMap

ご覧のとおり、Listの要素がTuple2のサブタイプである場合にのみ、 toMap メソッドを呼び出すことができます。これは、Intには当てはまりません。

3.3. 推論

ほとんどの場合、タイプを明示的に指定する必要はありません。 残念ながら、コンパイラがそれらを正しく推測できない状況がいくつかあります。 これは主に、 List:foldLeftまたはfoldRightのように、2つの引数リストを持つ汎用メソッドで発生します。

List(1 -> "first", 2 -> "second")
  .foldLeft(Map.empty) {
    case (map, (key, value)) =>
      map + (key -> value)
  }

上記のコードは、Tuples2Listを空のマップで始まるマップにフォールドしようとします。残念ながらコンパイルされません、 Map.empty を使用するときに型を明示的に指定せず、コンパイラがそれらを Nothing:と推測したためです。

[error] type mismatch;
[error]  found   : (Int, String)
[error]  required: (Nothing, Nothing)
[error]             map + (key -> value)

それらを指定すると、コードはエラーなしでコンパイルされます。

List(1 -> "first", 2 -> "second")
  .foldLeft(Map.empty[Int, String]) {
    case (map, (key, value)) =>
      map + (key -> value)
  }

4. 要素の追加

これで、マップの作成方法がわかったので、マップ:に新しい要素を追加する方法を見てみましょう。

val initialMap: Map[Int, String] = Map(1 -> "first")
val newMap: Map[Int, String] = initialMap + (2 -> "second")

上記のコードは、 initialMap plusのすべての要素を含む+ updated の別名)メソッドを使用して、新しいMapを作成します。 新しいキーと値のペア。 最初のマップは変更されないままであることを覚えておく必要があります。

ここで、newMapのコンテンツが期待どおりであることを確認しましょう。

initialMap shouldBe Map(1 -> "first")
newMap shouldBe Map(1 -> "first", 2 -> "second")

同じ手法を使用して、複数のキーと値のペアを追加することもできます。

val initialMap: Map[Int, String] = Map(1 -> "first")
val newMap: Map[Int, String] = initialMap + (2 -> "second", 3 -> "third")

initialMap shouldBe Map(1 -> "first")
newMap shouldBe Map(1 -> "first", 2 -> "second", 3 -> "third")

5. マップのマージ

++ concat のエイリアス)メソッドを使用して、2つのマップをマージできます。

val leftMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val rightMap: Map[Int, String] = Map(2 -> "2nd", 3 -> "third")

val map = leftMap ++ rightMap

map shouldBe Map(1 -> "first", 2 -> "2nd", 3 -> "third")

++の右側にあるrightMapが、同じキーのleftMap値を上書きしていることがわかります。

ペアのリストを使用してマップをマージすることも可能です

val leftMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val list: List[(Int, String)] = List(2 -> "2nd", 3 -> "third")

val map = leftMap ++ list
map shouldBe Map(1 -> "first", 2 -> "2nd", 3 -> "third")

6. 値のオーバーライド

マップのキーは一意である必要があることはすでに説明しました。 既存のキーの下でMapに新しい値を追加するとどうなるかを確認するときが来ました。

val initialMap: Map[Int, String] = Map(1 -> "first")
val newMap = initialMap + (1 -> "1st")

newMap shouldBe Map(1 -> "1st")

既存のキーの下に新しい値を置くと、以前の値が上書きされることがわかります。

7. 値の取得

このセクションでは、マップから値を取得するためのオプションのいくつかを見ていきます。

7.1. get

これで、要素をマップに配置する方法がわかりました。 次に、特定のキーの値を取得する方法を確認します。 この目的のために、getメソッドを使用できます。

val map: Map[Int, String] = Map(1 -> "first", 2 -> "second")

map.get(1) shouldBe Some("first")
map.get(3) shouldBe None

指定されたキーが存在しない可能性があるため、このメソッドはOption内の値を返すことに注意してください。

7.2. 適用

値を取得する別の方法は、applyメソッドを使用することです。

val map: Map[Int, String] = Map(1 -> "first", 2 -> "second")

map.apply(1) shouldBe "first"

残念ながら、このメソッドは結果を Option にラップしませんが、指定されたキーが存在しない場合は例外をスローします。

the[NoSuchElementException] thrownBy map.apply(3)

applyシンタックスシュガーを使用することもできます。

map(1) shouldBe "first"

7.3. withDefaultValue

特定のキーに値が存在しない場合の適切なデフォルトがある場合は、withDefaultValueメソッドを使用できます。

val map: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val mapWithDefault: Map[Int, String] = map.withDefaultValue("unknown")

この例では、新しい Map を作成しました。これは、欠落しているキーに対して「unknown」を返します。 いくつかの値を取得することで、この動作を確認できます。

mapWithDefault(1) shouldBe "first"
mapWithDefault(3) shouldBe "unknown"

7.4. withDefault

withDefault メソッドを使用して、欠落しているキーに基づいて値を計算するオプションもあります。

val map: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val mapWithDefault: Map[Int, String] = map.withDefault(i => i + "th")

今回は、欠落しているキーの値を計算する(「th」サフィックスを追加する)新しいマップを作成しました。

mapWithDefault(1) shouldBe "first"
mapWithDefault(5) shouldBe "5th"

8. キーの削除

次に、キーを削除する方法を見てみましょう。

8.1. シングルキー

値を追加、更新、および取得する方法はすでに知っています。 地図。 からキーを削除するには地図、 使用できます (エイリアス削除されました ) 方法:

val initialMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val newMap: Map[Int, String] = initialMap - 1

削除したばかりのキーを含まない新しいマップを返します。 指定されたキーがマップに存在しない場合、最初のマップ:を返します。

newMap shouldBe Map(2 -> "second")

initialMapは変更されません。

initialMap shouldBe Map(1 -> "first", 2 -> "second")

8.2. 複数のキー

同じ方法を使用して複数のキーを削除することもできます。

val initialMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val newMap: Map[Int, String] = initialMap - (1, 2, 3)

initialMap shouldBe Map(1 -> "first", 2 -> "second")
newMap shouldBe empty

8.3. キーのリスト

キーを削除する別のオプションは、キーの List を使用して removedAll のエイリアス)メソッドを呼び出すことです。

val initialMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val newMap: Map[Int, String] = map -- List(1, 2)

initialMap shouldBe Map(1 -> "first", 2 -> "second")
newMap shouldBe empty

9. マップの変換

この最後のセクションでは、マップを変換する方法を説明します。

9.1. マップ

Map、は、他のScalaコレクションと同様に、関数mapメソッドに渡すことで変換できます。

val initialMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")

val abbreviate: ((Int, String)) => (Int, String) = {
  case (key, value) =>
    val newValue = key + value.takeRight(2)
    key -> newValue
}

この例では、値の最後の2文字を取得し、それらをキーと連結するabbreviate関数を作成しました。 これをmapメソッドに渡して、 initialMap:のすべての値を省略できます。

val abbreviatedMap = initialMap.map(abbreviate)

initialMap shouldBe Map(1 -> "first", 2 -> "second")
abbreviatedMap shouldBe Map(1 -> "1st", 2 -> "2nd")

省略形関数をinitialMapの各キーと値のペアに適用し、結果を含む新しいMapを返します。 initialMapは変更されません。

9.2. mapValues

値のみを変換する場合は、 mapValues メソッドを使用して、マッピング関数を渡すことができます。

val initialMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val reverse: String => String = value => value.reverse

val reversed: Map[Int, String] = initialMap.mapValues(reverse)

reversed.get(1) shouldBe Some("tsrif")
reversed.get(2) shouldBe Some("dnoces")

残念ながら、この方法には注意が必要です。 マップを呼び出すときに、マップ内のすべての値にマッピング関数を適用するのではなく、値を取得するたびに評価します。

理解しやすくするために、マッピング関数にカウンターを追加しましょう。

val initialMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")

val counter = new AtomicInteger(0)
val reverse: String => String = { value =>
  counter.incrementAndGet()
  value.reverse
}

val reversed: Map[Int, String] = initialMap.mapValues(reverse)

次に、カウンターの動作を確認しましょう。

counter.get() shouldBe 0

reversed.get(1) shouldBe Some("tsrif")
counter.get() shouldBe 1

reversed.get(2) shouldBe Some("dnoces")
counter.get() shouldBe 2

reversed.get(1) shouldBe Some("tsrif")
counter.get() shouldBe 3

ご覧のとおり、mapValuesを呼び出してもカウンターは変更されません。 カウンターは、値を取得する場合にのみ変更されます。

マッピングを厳密にしたい場合は、 mapValues メソッドを呼び出した後、viewおよびforceメソッドを呼び出す必要があります。

val initialMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")

val counter = new AtomicInteger(0)
val reverse: String => String = { value =>
  counter.incrementAndGet()
  value.reverse
}

val reversed: Map[Int, String] = initialMap
  .mapValues(reverse)
  .view
  .force

counter.get() shouldBe map.size

reversed.get(1) shouldBe Some("tsrif")
counter.get() shouldBe map.size

reversed.get(2) shouldBe Some("dnoces")
counter.get() shouldBe map.size

また、Scala 2.13以降、mapValuesメソッドは非推奨になっていることにも注意してください。

9.3. フィルター

filter メソッドに述語を渡すことで、マップの要素をフィルタリングすることもできます。

key が1より大きく、 value が5より大きい場合にのみ、trueを返すpredicate関数を定義しましょう。

val predicate: ((Int, String)) => Boolean = {
  case (key, value) => key > 1 && value.length > 5
}

今、述語関数をフィルターメソッドに渡すことができます。

val initialMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val filtered: Map[Int, String] = initialMap.filter(predicate)

述語initialMapの各キーと値のペアに適用し、述語のペアのみを含む新しいマップを返します。 trueを返します:

filtered shouldBe Map(2 -> "second")

9.4. filterKeys

Map 要素をキーのみでフィルタリングする場合は、filterKeysメソッドを使用する必要があります。

ここでも、指定されたキーが1より大きい場合にのみtrueを返す単純な述語関数を作成できます。

val predicate: Int => Boolean = key => key > 1

それをfilterKeysメソッドに渡しましょう。

val initialMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val filtered: Map[Int, String] = initialMap.filterKeys(predicate)

前と同じように、 predicate関数をinitialMap の各キーに適用し、述語がtrueを返す要素のみを含む新しいMapを返します。

filtered.get(1) shouldBe None
filtered.get(2) shouldBe Some("second")

残念ながら、 mapValues メソッドと同じように動作し、Scala2.13以降も非推奨になっています。

 10. 結論

この記事では、Scalaの基本を探りました地図 API 。 

空および空でないマップを作成する方法と、値を取得、更新、および削除する方法を学習しました。 次に、さまざまな方法を使用してマップを変換する方法を確認し、いくつかの注意点を指摘しました。

いつものように、記事の完全なソースコードは、GitHubから入手できます。