👋

Kotlin: Collectionを理解したい!IteratorについてとCollectionをわかりやすく

2023/10/21に公開

IteratorについてとCollection操作方法


画像元

今回は、Collectionの基本的な概念を、Iteratorまで含めて
まずはImageを持てるようにした上で、
そこの部分を今度はコードで見ていく形で書いていきたいと思います。

理論的な部分も、初学者でも理解できるように、できるだけわかりやすく書いていますが、
もしわかりずらい、や 少し違いそう?とかあったらコメントお願いします!

今回は基本概要とし、次回は活用などについてで、分けて書いていきたいと思います。

本日の大枠目次

  1. 概要ついて
  2. Iterableを継承?!
  3. Iteratorについて
  4. 本題:Collection(List,Map,Set)
  5. Collectionの操作について
    (filter, map, forEach, )
    ▶️ mapとforEachの違いは?

コレクションの基本的な考えとItrator

■ Collection基礎: 概要

  • コレクションとは複数の値をまとめて扱うことができるもの.
  • Collection<T>インターフェイスは、
  • コレクションには大きな区分が2つ。
    読み取り専用コレクション(Read-only Collection)と、
    ミュータブルコレクション(Mutable Collection)の2種類のコレクションがある。
  • Kotlin のコレクションには、ListSetMap が用意されていて、
    これらは,読み取り専用コレクションだ.
    それぞれ宣言した後に要素の変更(追加、削除、更新)ができない
    後で変更をしたい場合はMutableListMutableSetMutableMap 型で
    宣言する必要がある。
    (のちに、詳しく記載。)

最初に簡単にテーブルを作成します。

メソッド 概要
List 順序を持つコレクション。重複可。
Set 順序を持たないコレクション。重複不可。
Map Key Valueで値を保持。

Collection types

Collection インターフェース自体(単体、何も継承しない場合)は、
読み取り専用コレクション(Read-only Collection)を表すものだ。
Iterable<T> インターフェースを継承しているため、
Collection インターフェースは、要素の追加、削除、更新などの変更操作をサポートできていて、

[読み取り専用コレクション(Read-only Collection)]
  • Kotlin のコレクションには、ListSetMap が用意されていて、
    これらは,読み取り専用コレクションだ
  • この読み取り専用コレクションはそれぞれ宣言した後に要素の変更(追加、削除、更新)ができない
    • add() などの更新系メソッドは持っていない!!!
[ミュータブルコレクション(Mutable Collection)]
  • 更新などのメソッドは、 Mutable が頭についたMutableListMutableSetMutableMap
    がこのインターフェースに定義されている

Collectionは、Iterable<T> インターフェイス を継承する

Collectionは、Iterable<T>インターフェースを継承している。

inline fun <T> Iterable(
    crossinline iterator: () -> Iterator<T>
): Iterable<T>	

このインターフェースは、繰り返し処理を行うための基本的なメソッドを提供している。
T は反復要素の型を表す)

具体的には、Iterable インターフェースは Iteratorメソッド を提供し、
これを使用してコレクション内の要素を反復処理できる!!!
Iterator はコレクション内の要素に逐次アクセスするのに役立つ。

■ "Iterator はコレクション内の要素に逐次アクセスするのに役立つ"とはどういうことか。

これを読み解くために一旦Iteratorを読み解いていく。
Collectionだけ見たいかたは、飛ばして読み進めてください〜!

Iterable インターフェース

まずは簡単にテーブルで記載します。

メソッド 概要
iterator() コレクション内の要素に順次アクセスするための Iterator オブジェクトを生成する。 forループが動作するために必要な概念
hasNext() 次の要素が存在するかどうかを確認し、true または false を返します。
next() 次の要素を取得し、その要素を返します。内部の位置ポインタを次の要素に進めます。

iterator()

  • コレクション内の要素に順次アクセスするための Iterator オブジェクトを生成する
    forループが動作するために必要な概念.
  • Iterator インターフェースには、 hasNext() メソッドと next() メソッドが含まれる。
    別の言い方にすると、IteratorhasNext()next() に分解できる。
    これらのメソッドを使用してコレクション内の要素にアクセスする。

hasNext() メソッド:

  • hasNext() メソッドは、次の要素が存在するかどうかを(真偽)確認する
    具体的には、このメソッドは次に next() メソッドを呼び出して要素を取得できるかどうかを判定している。
    • もし hasNext() メソッドが true を返す場合、次に呼び出すべきは next() メソッド。
      これは、要素がまだ残っていることを示し、次の要素にアクセスするための許可を与える。
    • もし hasNext() メソッドが false を返す場合
      それは反復処理が終了したことを示し、もう要素が残っていないことを示す。
      この場合、next() メソッドを呼び出すと例外がスローされることがあるため、
      通常は hasNext() の結果を確認してから next() を呼び出すことが推奨される。

next() メソッド:

  • next() メソッドは、次の要素を取得し、その要素を返す
    また、内部の位置ポインタを次の要素に進める。

  • 反復処理が進行中の場合、next() メソッドは次の要素に進み、その要素を取得する。

  • 反復処理が終了した場合、(hasNext() メソッドが false を返す場合),
    next() メソッドを呼び出すと例外(通常は NoSuchElementException など)がスローされることがある。

本題: Collectionについて

では最初の本題のCollectionに戻ります。
読み取り専用も、ミュータブルも大きな差はないので一緒のスペースで書いていきます!

最初に概要を書いた上で、操作方法(map, filterとか!)を書いていきます~!!

▶️ Kotlin Koans: Collections Introductionこちらに問題があります。
先にどれくらいできるか、やってみても面白いと思います!
ここで出てくる問題は、基本的なことがわからないと難しいので、ここで基本を押さえてから、
やってみても面白いと思います!

List

List is an ordered collection with access to elements by indices – integer numbers that reflect their position. Elements can occur more than once in a list. An example of a list is a telephone number: it's a group of digits, their order is important, and they can repeat.
リストは順番に並んだコレクションであり、要素へのアクセスはインデックス(位置を表す整数値)によって行われる。要素はリスト内で複数回出現することがある。リストの例は電話番号である。電話番号は数字の集まりであり、その順番は重要で、繰り返すことができる。
▶️ Native souseはこちら

  • 順序を持つ要素のコレクション
  • 要素はリスト内で順番に配置され、インデックスを使用してアクセスできる。
    List内の要素は重複して存在でき、
    上記でも記載したがKotlinでは、
    Listは読み取り専用(immutable)と変更可能(mutable)の2つがある。

■ 宣言方法
listOf()で宣言。(MutableListOf()で行うと可変に作成が可能。)

補足: Array(配列)との違いは説明できるか?

ListとArray(配列)の違い

Array

  • 複数の要素を保持するデータ構造で、これらの要素はインデックスを使用して
    アクセスできる順序付けられたコレクション。
    特定の型の要素を保持するために使用される。

違いをTableにしてみました!

特性 List (リスト) 配列 (Array)
可変性 (Mutability) 通常は不変 (Immutable) 通常はミュータブル (Mutable)
ジェネリック (Generics) サポート (Generics) 一般的に型制約 (Type Constraint)
サイズの可変性 (Size Mutability) 可変 (Mutable) 通常は固定 (Fixed)
拡張関数と操作 (Extension Functions) サポート (Supports Ext. Functions) サポートなし (No Support)

Set

Set is a collection of unique elements. It reflects the mathematical abstraction of set: a group of objects without repetitions. Generally, the order of set elements has no significance. For example, the numbers on lottery tickets form a set: they are unique, and their order is not important.
集合とはユニークな要素の集まりである。集合の数学的抽象化である「繰り返しのないオブジェクトのグループ」を反映している。一般に、集合の要素の順序には意味がない。例えば、宝くじに書かれている数字は集合を形成している。それらは一意であり、その順序は重要ではない。

▶️ Native souseはこちら

  • Setは、特定の順序を持たず、値が重複しないコレクション
  • 集合を表すコレクションで下記のような特徴がある。
  • リストとは異なり,,,
    • 集合のため要素に重複がなくなる
    • 順序が保証されていない

■ 宣言方法
セットはsetOf()を使用して宣言。
変更可能なセットを作成する場合は、mutableSetOf()を使用!

// 不変セット
val set: Set<Int> = setOf(1, 2, 3, 1, 2, 3) // 重複要素は削除される

// 変更可能セット
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3)
mutableSet.add(4) // 要素の追加
mutableSet.remove(2) // 要素の削除

println("set: $set") // set: [1, 2, 3]
println("mutableSet: $mutableSet") // mutableSet: [1, 3, 4]

Map

Map (or dictionary) is a set of key-value pairs. Keys are unique, and each of them maps to exactly one value. The values can be duplicates. Maps are useful for storing logical connections between objects, for example, an employee's ID and their position.
マップ(または辞書)は、キーと値のペアの集合である。キーは一意であり、それぞれが正確に1つの値に対応する。値は重複することもあります。マップは、例えば従業員のIDとそのポジションのように、オブジェクト間の論理的なつながりを格納するのに便利です。

  • Mapは、ListやSetと違って、Collectionインターフェイスを直接には継承せずに、
    独自でMapインターフェイスを持つ。
  • キーと値のペアのコレクションで、各キーは一意であり、それに対応する値を持つ。
  • KotlinのMapは、キーと値の型を指定できる柔軟なデータ構造です。

▶️ Native souseはこちら

■ 宣言方法
MapOf()で宣言。(MutableMapOf()で行うと可変に作成が可能。)

// 不変マップ
val map: Map<String, String> = mapOf("John" to "Manager", "Alice" to "Developer")

// 変更可能マップ
val mutableMap: MutableMap<String, String> = mutableMapOf()
mutableMap["Bob"] = "Designer" // キーと値の追加
mutableMap["Alice"] = "Team Lead" // キーに対する値の変更
mutableMap.remove("John") // キーと値の削除

println("map: $map") // map: {John=Manager, Alice=Developer}
println("mutableMap: $mutableMap") 
// mutableMap: {Bob=Designer, Alice=Team Lead}
	

■ Collectionの操作

ここからは、Collectionの操作について記述していきます。
大まかにジャンルでわけて代表的なものや大事なところの解説を書いていきます。

操作・変換 操作の名前 説明
フィルタリング filter() 条件に合致する要素を抽出。
filterNot() 条件に合致しない要素を抽出。
distinct() 重複要素を削除。
マッピング map() 各要素を変換して新しいコレクションを作成。
ソート sorted() 要素を昇順にソート。
sortedBy() 指定の条件に基づいて要素をソート。
グループ化 groupBy() 指定の条件に基づいて要素をグループ化。

filter()

  • コレクション内の要素を特定の条件に基づいてフィルタリングするための操作。
    引数部分で指定した条件がtrueの要素だけを返す。
  • これは、条件に合致する要素を新しいコレクションとして取得する!!

■ 構文

val filteredCollection = originalCollection.filter { element -> /* 条件式 */ }

・ originalCollection: フィルタリング対象とするコレクション名。
・ filteredCollection: 条件に合致する要素で構成された新しいコレクション。
・ element: 各要素を表す変数。
・ 条件式: フィルタリング条件を表す式。(ここの条件に合致する要素が選択されるよ!)

Ex.

例として、奇数の要素を抽出するとしたら...

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val oddNumbers = numbers.filter { it % 2 != 0 }

println(oddNumbers)

// oddNumbers は [1, 3, 5, 7, 9] となる

groupBy() メソッド

  • groupBy() メソッドは、
    コレクション内の要素を指定の条件に基づいてグループ化するために使用される。
  • 特定のキーに関連する要素をまとめるのに役立つ

■ 基本構文

val groupedMap = collection.groupBy { element ->
    /* キーを抽出する処理 */
}

・ collection: グループ化対象のコレクション。
・ element: 各要素を表す変数。
・ キーを抽出する処理: ラムダ式 { element -> /* キーを抽出する処理 */ } は,
各要素からキーを抽出するための関数。この関数内で要素を処理し、キーを返す処理を記述。

整数のリストを奇数と偶数にグループ化してみる...!

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
val groupedByOddEven = numbers.groupBy { if (it % 2 == 0) "even" else "odd" }
// 結果を出力
println(groupedByOddEven)

//{odd=[1, 3, 5, 7, 9], even=[2, 4, 6, 8]}

このように、コレクション内の要素を指定のキーに基づいて効果的にグループ化することができる!
これはデータの集計やカテゴリ別に要素を整理する際に役立ちます!

forEach

  • .forEach メソッドは、Kotlinのコレクション(List、Set、Map)内の各要素に対して,
    指定されたアクション(処理)を適用するためのメソッド
  • このメソッドは要素の反復処理に使用され、各要素を順番に処理します。

■ 構文:

collection.forEach { element -> /* アクション */ }

・ collection: 要素を繰り返し処理する対象のコレクション。
・ element: 各要素を表す変数。
・ アクション: 各要素に対して実行される処理。

今回は例として、各要素を合計値に加えるを実行します。

val numbers = listOf(1, 2, 3, 4, 5)
var sum = 0 // 合計値を保持する変数

numbers.forEach { number ->
    sum += number // 各要素を合計に加える
}
println("合計値は $sum です。")
// 合計値は 15 です。

■ ⚠️ .map().forEach()の違いは?!?!

個人的にはこの問題が、勉強し初めた時には一番よくわからなかったところです。
先に、結論を書くと、

  • 要素を変換して新しいコレクションを作成したい場合は .map を使用し、
  • 単に要素の反復処理を行いたい場合は .forEach を使用する!

では、それぞれのメソッドの中のコードまで確認すると,,,

< forEach >

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

返り値を見てみると、Unitと確認できる。値が不要であることを示す型だ。
他の言語では"void"型として知られていますが、KotlinではUnit型を使用する。
Unit型は、主に関数やメソッドが値を返さない場合に使用されるもので、
関数が値を返さない場合、その関数の戻り値型としてUnitを明示的に指定することがある。

< map >

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}	

ここでは、返り値を確認すると、List<R>と新しいListになっていることが確認できる。

Discussion