1. 概要

このチュートリアルでは、Kotlinコレクション内の重複する要素を削除する方法を学習します。

2. distinct()関数

コレクションから重複する要素を削除するために(より具体的にはIterable)、distinct()拡張関数を使用できます

val protocols = listOf("tcp", "http", "tcp", "udp", "udp")
val distinct = protocols.distinct()
assertThat(distinct).hasSize(3)
assertThat(distinct).containsExactlyInAnyOrder("tcp", "http", "udp")

上に示したように、この関数は、指定された文字列の余分な出現をすべて削除し、結果のリストにそれぞれ1つだけを保持します。

2.1. 実装

内部的には、 distinct()拡張関数は、 equals()メソッドを使用して要素を比較します。 この主張を証明するために、関数implementationを見てみましょう。

public fun <T> Iterable<T>.distinct(): List<T> {
    return this.toMutableSet().toList()
}

上記のように、受信側のIterableSetに変換します。 したがって、 equals()メソッドは要素の同等性を決定します

この記事の執筆時点では、基盤となるSetの実装はLinkedHashSetです。 これは、 equals()実装がhashCode()実装とも互換性がある必要があることを意味します。 そうしないと、予期しない結果が発生します。

3. distinctBy()関数

ただし、カスタム基準を使用して重複する要素を削除する必要がある場合があります。 たとえば、URLを値の組み合わせとしてカプセル化するとします。

data class Url(val protocol: String, val host: String, val port: Int, val path: String)

このため、 distinct()関数を使用して、重複するホスト名を削除することはできません。

ただし、ラムダを介してカスタム基準を受け入れる distinctBy()という名前の別の拡張関数があります。

これをよりよく理解するために、URLのコレクションを考えてみましょう。

val urls = listOf(
  Url("https", "baeldung", 443, "/authors"),
  Url("https", "baeldung", 443, "/authors"),
  Url("http", "baeldung", 80, "/authors"),
  Url("https", "baeldung", 443, "/kotlin/distinct"),
  Url("https", "google", 443, "/"),
  Url("http", "google", 80, "/search"),
  Url("tcp", "docker", 2376, "/"),
)

したがって、重複するホスト名を削除するために、 distinctBy()のように使用できます。

val uniqueHosts = urls.distinctBy { it.host }
assertThat(uniqueHosts).hasSize(3)

ここでは、各 URL インスタンスを比較するときに、ホスト名( it.host 部分)を使用するように distinctBy{}に指示しています。 明らかに、上記の例では、「baeldung」「google」、および「docker」の3つの異なるホストしかありません。

別の例として、ここでは、重複する完全なURLを削除しています。

val uniqueUrls = urls.distinctBy { "${it.protocol}://${it.host}:${it.port}/" }
assertThat(uniqueUrls).hasSize(5)

上記の例では、2つのURLが同じプロトコル、ホスト名、およびポート値を共有している場合、それらは重複しています。

3.1. 実装

distinctBy()実装を見てみましょう。

public inline fun <T, K> Iterable<T>.distinctBy(selector: (T) -> K): List<T> {
    val set = HashSet<K>()
    val list = ArrayList<T>()
    for (e in this) {
        val key = selector(e)
        if (set.add(key))
            list.add(e)
    }
    return list
}

基本的に、それは受信を繰り返します反復可能一度。 要素ごとに、指定されたラムダセレクターを使用してキーを計算します。 このキーが重複している場合、現在の要素は最後のリストに追加されません。 このチュートリアルでは、Stringキーのみを使用しました。 ただし、ラムダ内の他のタイプを返すことも可能です。

4. 結論

このチュートリアルでは、コレクションまたは配列から重複する要素を削除する2つの方法を学びました。 distinct()関数は、オブジェクトの等価性を使用して要素を比較する場合に役立ちます。 一方、よりカスタマイズされた比較のために、より柔軟な distinctBy()関数を使用できます。

いつものように、すべての例はGitHubから入手できます。