Skip to content

順序付け

要素の順序は、特定のコレクションタイプにとって重要な側面です。例えば、同じ要素を持つ2つのリストは、それらの要素の順序が異なる場合、等しくありません。

Kotlinでは、オブジェクトの順序をいくつかの方法で定義できます。

まず、_自然順序_があります。これは、Comparableインターフェースの実装に対して定義されます。自然順序は、他の順序が指定されていない場合にそれらをソートするために使用されます。

ほとんどの組み込み型は比較可能です。

  • 数値型は伝統的な数値順序を使用します: 10より大きく、-3.4f-5fより大きい、などです。
  • CharString辞書順を使用します: baより大きく、worldhelloより大きい、などです。

ユーザー定義型に自然順序を定義するには、その型をComparableの実装者にします。これには、compareTo()関数の実装が必要です。compareTo()は、同じ型の別のオブジェクトを引数として受け取り、どちらのオブジェクトが大きいかを示す整数値を返さなければなりません。

  • 正の値は、レシーバオブジェクトが大きいことを示します。
  • 負の値は、引数より小さいことを示します。
  • ゼロは、オブジェクトが等しいことを示します。

以下は、メジャーバージョンとマイナーバージョンから成るバージョンの順序付けのためのクラスです。

kotlin
class Version(val major: Int, val minor: Int): Comparable<Version> {
    override fun compareTo(other: Version): Int = when {
        this.major != other.major -> this.major compareTo other.major // compareTo() in the infix form 
        this.minor != other.minor -> this.minor compareTo other.minor
        else -> 0
    }
}

fun main() {    
    println(Version(1, 2) > Version(1, 3))
    println(Version(2, 0) > Version(1, 5))
}

_カスタム順序_を使用すると、任意の型のインスタンスを好きなようにソートできます。特に、比較不可能なオブジェクトの順序を定義したり、比較可能な型に対して自然順序以外の順序を定義したりできます。ある型にカスタム順序を定義するには、その型に対してComparatorを作成します。Comparatorにはcompare()関数が含まれています。これはクラスの2つのインスタンスを受け取り、それらの比較の整数結果を返します。結果は、上記で説明したcompareTo()の結果と同じように解釈されます。

kotlin
fun main() {
    val lengthComparator = Comparator { str1: String, str2: String -> str1.length - str2.length }
    println(listOf("aaa", "bb", "c").sortedWith(lengthComparator))
}

lengthComparatorがあれば、デフォルトの辞書順ではなく、文字列をその長さで並べ替えることができます。

Comparatorを定義するより短い方法は、標準ライブラリのcompareBy()関数です。compareBy()は、インスタンスからComparable値を生成するラムダ関数を受け取り、生成された値の自然順序としてカスタム順序を定義します。

compareBy()を使用すると、上記の例の長さコンパレータは次のようになります。

kotlin
fun main() {
    println(listOf("aaa", "bb", "c").sortedWith(compareBy { it.length }))
}

複数の基準に基づいて順序を定義することもできます。例えば、文字列を長さでソートし、長さが同じ場合はアルファベット順にソートするには、次のように記述します。

kotlin
fun main() {
    val sortedStrings = listOf("aaa", "bb", "c", "b", "a", "aa", "ccc")
        .sortedWith { a, b -> 
           when (val compareLengths = a.length.compareTo(b.length)) {
             0 -> a.compareTo(b)
             else -> compareLengths
           }
         }

    println(sortedStrings)
    // [a, b, c, aa, bb, aaa, ccc]
}

複数の基準によるソートはよくあるシナリオであるため、Kotlin標準ライブラリは、二次的なソートルールを追加するために使用できるthenBy()関数を提供しています。

例えば、compareBy()thenBy()を組み合わせて、前の例と同じように、最初に文字列を長さでソートし、次にアルファベット順でソートできます。

kotlin
fun main() {
    val sortedStrings = listOf("aaa", "bb", "c", "b", "a", "aa", "ccc")
        .sortedWith(compareBy<String> { it.length }.thenBy { it })

    println(sortedStrings)
    // [a, b, c, aa, bb, aaa, ccc]
}

Kotlinコレクションパッケージは、自然順序、カスタム順序、さらにはランダム順序でコレクションをソートするための関数を提供しています。このページでは、読み取り専用コレクションに適用されるソート関数について説明します。これらの関数は、元のコレクションの要素を要求された順序で含む新しいコレクションとして結果を返します。可変コレクションをインプレースでソートする関数については、List固有の操作を参照してください。

自然順序

基本的な関数であるsorted()sortedDescending()は、コレクションの要素を自然順序に従って昇順および降順にソートして返します。これらの関数は、Comparable要素のコレクションに適用されます。

kotlin
fun main() {
    val numbers = listOf("one", "two", "three", "four")

    println("Sorted ascending: ${numbers.sorted()}")
    println("Sorted descending: ${numbers.sortedDescending()}")
}

カスタム順序

カスタム順序でソートする場合や、比較不可能なオブジェクトをソートする場合は、sortedBy()sortedByDescending()関数があります。これらは、コレクションの要素をComparable値にマッピングするセレクタ関数を受け取り、その値の自然順序でコレクションをソートします。

kotlin
fun main() {
    val numbers = listOf("one", "two", "three", "four")

    val sortedNumbers = numbers.sortedBy { it.length }
    println("Sorted by length ascending: $sortedNumbers")
    val sortedByLast = numbers.sortedByDescending { it.last() }
    println("Sorted by the last letter descending: $sortedByLast")
}

コレクションのソートにカスタム順序を定義するには、独自のComparatorを提供できます。これを行うには、Comparatorを渡してsortedWith()関数を呼び出します。この関数を使用すると、文字列をその長さでソートするのは次のようになります。

kotlin
fun main() {
    val numbers = listOf("one", "two", "three", "four")
    println("Sorted by length ascending: ${numbers.sortedWith(compareBy { it.length })}")
}

逆順

reversed()関数を使用して、コレクションを逆順で取得できます。

kotlin

fun main() {
    val numbers = listOf("one", "two", "three", "four")
    println(numbers.reversed())
}

reversed()は、要素のコピーを含む新しいコレクションを返します。したがって、後で元のコレクションを変更しても、以前にreversed()から取得した結果には影響しません。

もう1つの逆順にする関数であるasReversed()は、同じコレクションインスタンスの逆順ビューを返します。そのため、元のリストが変更されない場合は、reversed()よりも軽量で望ましい場合があります。

kotlin
fun main() {
    val numbers = listOf("one", "two", "three", "four")
    val reversedNumbers = numbers.asReversed()
    println(reversedNumbers)
}

元のリストが可変である場合、そのすべての変更は逆順ビューに反映され、逆もまた同様です。

kotlin
fun main() {
    val numbers = mutableListOf("one", "two", "three", "four")
    val reversedNumbers = numbers.asReversed()
    println(reversedNumbers)
    numbers.add("five")
    println(reversedNumbers)
}

しかし、リストの可変性が不明な場合や、ソースがそもそもリストではない場合は、reversed()の方が推奨されます。その結果は将来変更されないコピーであるためです。

ランダム順序

最後に、コレクション要素をランダムな順序で含む新しいListを返す関数であるshuffled()があります。引数なしで呼び出すことも、Randomオブジェクトを渡して呼び出すこともできます。

kotlin
fun main() {
     val numbers = listOf("one", "two", "three", "four")
     println(numbers.shuffled())
}