yu/logs/*

技術メモ など

AtCoder ABC128 B 解法メモ (Kotlinで)複数項目をキーにしてリストをソートしたい

タイトルの通りです。

普段はDBからselectする時にorder byすることが多い気がしますが、AtCoderをやっていてプログラミング言語側でちょっと複雑なソートをしたくなったので調べました。

解いていた問題

atcoder.jp

解法メモ

  • 問題文の通りにソートする
  • List<String> とか List<Int> とかであれば、 list.sorted() とかでソートが出来るけど、今回は複数項目をキーにしたソートをしたい
    • list.sortWith(compareBy(~~~))list.sortWith(compareByDescending(~~~)) を使ってあげると良さそう

確認(の事前準備)

前提となるデータを以下の感じで準備しました。

    class Person(val no: Int, val group: Int, val firstName: String, val lastName: String)

    val list = mutableListOf<Person>()
    list.add(Person(1, 1, "たろう", "さとう"))
    list.add(Person(2, 1, "じろう", "すずき"))
    list.add(Person(3, 1, "さぶろう", "たかはし"))
    list.add(Person(4, 2, "しろう", "たなか"))
    list.add(Person(5, 2, "ごろう", "いとう"))
    list.add(Person(6, 2, "ろくろう", "いとう"))

確認1 数値項目 昇順

以下のようにorder byの感覚で指定できました。

    list.sortWith(compareBy({ it.group }, { it.no }))
    println("list.sortWith(compareBy({ it.group }, {it.no}))")
    for (item in list) {
        println("${item.no} ${item.group} ${item.firstName} ${item.lastName}")
    }
    println()

↓出力

list.sortWith(compareBy({ it.group }, {it.no}))
1 1 たろう さとう
2 1 じろう すずき
3 1 さぶろう たかはし
4 2 しろう たなか
5 2 ごろう いとう
6 2 ろくろう いとう

確認2 数値項目 降順

以下のように、確認1で指定した項目にマイナスをつけてあげると逆順にソートすることが出来るようでした。

    // 数値項目ならマイナスすれば降順になる
    list.sortWith(compareBy({ -it.group }, { it.no }))
    println("list.sortWith(compareBy({ -it.group }, { it.no }))")
    for (item in list) {
        println("${item.no} ${item.group} ${item.firstName} ${item.lastName}")
    }
    println()
    list.sortWith(compareBy({ -it.group }, { -it.no }))
    println("list.sortWith(compareBy({ -it.group }, { -it.no }))")
    for (item in list) {
        println("${item.no} ${item.group} ${item.firstName} ${item.lastName}")
    }
    println()

↓出力

list.sortWith(compareBy({ -it.group }, { it.no }))
4 2 しろう たなか
5 2 ごろう いとう
6 2 ろくろう いとう
1 1 たろう さとう
2 1 じろう すずき
3 1 さぶろう たかはし

list.sortWith(compareBy({ -it.group }, { -it.no }))
6 2 ろくろう いとう
5 2 ごろう いとう
4 2 しろう たなか
3 1 さぶろう たかはし
2 1 じろう すずき
1 1 たろう さとう

確認3 文字列項目 降順

昇順は数値項目同様にcompareByでソートできますが、文字列の降順はマイナスを付けてもビルドが通りません。

文字列の場合はcompareByDescendingを使用することで降順ソートができました。

    // 文字列項目の降順はcompareByDescending
    list.sortWith(compareByDescending({ it.firstName }))
    println("list.sortWith(compareByDescending({ it.firstName }))")
    for (item in list) {
        println("${item.no} ${item.group} ${item.firstName} ${item.lastName}")
    }
    println()

↓出力

list.sortWith(compareByDescending({ it.firstName }))
6 2 ろくろう いとう
1 1 たろう さとう
2 1 じろう すずき
4 2 しろう たなか
3 1 さぶろう たかはし
5 2 ごろう いとう

確認4 文字列項目 降順含む複数

例えば「lastNameを降順にしたくて、lastName同一のものはfirstName昇順で並べたい」というケースを考えた時、確認1~3だけでは上手くできなさそうな気がして困りました。

試しに二度sortWithをかませてみたらそれっぽくなりましたが、並び順が保証されているかは未確認です。*1

    // 文字列項目を複数指定したい場合はcompareByDescending * 2で一応できるけど・・・
    list.sortWith(compareByDescending({ it.firstName }))
    list.sortWith(compareByDescending({ it.lastName }))
    println("list.sortWith(compareByDescending({ it.firstName }))")
    println("list.sortWith(compareByDescending({ it.lastName }))")
    for (item in list) {
        println("${item.no} ${item.group} ${item.firstName} ${item.lastName}")
    }
    println()
    
    list.sortWith(compareBy({ it.firstName }))
    list.sortWith(compareByDescending({ it.lastName }))
    println("list.sortWith(compareBy({ it.firstName }))")
    println("list.sortWith(compareByDescending({ it.lastName }))")
    for (item in list) {
        println("${item.no} ${item.group} ${item.firstName} ${item.lastName}")
    }
    println()

↓出力

list.sortWith(compareByDescending({ it.firstName }))
list.sortWith(compareByDescending({ it.lastName }))
4 2 しろう たなか
3 1 さぶろう たかはし
2 1 じろう すずき
1 1 たろう さとう
6 2 ろくろう いとう
5 2 ごろう いとう

list.sortWith(compareBy({ it.firstName }))
list.sortWith(compareByDescending({ it.lastName }))
4 2 しろう たなか
3 1 さぶろう たかはし
2 1 じろう すずき
1 1 たろう さとう
5 2 ごろう いとう
6 2 ろくろう いとう

↓確認してみたコードは以下で作ってあるので、少しいじって試したりなどできると思います。 https://pl.kotl.in/A__PBXw3k?theme=darcula

提出した回答

*1:もっと確実&スマートな方法があれば教えてください