サイトロゴ

Enjoy Creating
Web & Mobile Apps

MENU BOX
WEB
MOBILE
OPEN

ホーム

 > 

 > 

【Kotlin】昇順と降順が混在した並び替え【sortedWith】

【Kotlin】昇順と降順が混在した並び替え【sortedWith】

この記事にはプロモーションが含まれています。

リスト(配列)を並べ替える際、単に昇順または降順だけで並べ替えるのではなく、『文字列の先頭文字は昇順、先頭文字以降は降順にして並べ替えたい』といった場合があります。

たとえば、IDや製品番号を『A-123』というふうに、英字と数字から成る文字列型(String)で管理しているケースでは、英字部分と数字部分で昇順と降順を逆にして並べ替えたい状況があり得るでしょう。

このような場合は、どうすれば良いのでしょうか?また、反対にどのようなアプローチではうまくいかないのでしょうか?

今回は、Kotlinにおける昇順と降順が混在した並べ替えについて、ポイントを解説していきます!

この記事を読むことで分かること
  • ・昇順と降順を同時に適用させる方法
  • sortedWith() の基本的な使い方
  • compareBy()thenBy() の基本的な使い方

– 目次 –

うまくいかないアプローチ

まず最初に、期待する結果が得られないアプローチから確認しておきましょう。

次のようなリスト(List)があったとします。

Kotlin

val itemList = listOf(
    "A-123",
    "B-032",
    "A-025",
    "C-112",
    "C-054",
    "A-372",
    "B-004",
    "C-010",
    "B-538"
)

これを、英字部分は昇順(A→C)、数字部分は降順(9→0)に並べ替えたいとしましょう。

つまり、期待する結果は下のようになります。

Kotlin

val expectedList = listOf(
    "A-372",
    "A-123",
    "A-025",
    "B-538",
    "B-032",
    "B-004",
    "C-112",
    "C-054",
    "C-010"
)

各リストアイテムの文字列の英字もしくは数字部分を抽出し、それを元に単に昇順あるいは降順にするだけならば簡単です。

下のコードは、先頭文字(英字部分)を条件に、昇順に並び替えたものです。(コードが縦に長いので、スクロールするか、▼ボタンをクリックして展開してください)

Kotlin

val itemList = listOf(
    "A-123",
    "B-032",
    "A-025",
    "C-112",
    "C-054",
    "A-372",
    "B-004",
    "C-010",
    "B-538"
)

fun main() {
    val sortedList = itemList.sortedBy {
        it.first()
    }
    
    for (item in sortedList) {
        println(item)
    }
    /**
     * Outputs
     * 
     * A-123
     * A-025
     * A-372
     * B-032
     * B-004
     * B-538
     * C-112
     * C-054
     * C-010
     * 
     */
}

とりあえず、これで英字部分については期待通りの順番になりました。

しかし、この結果を利用して、続けて数字部分について次のように並べ替え(降順)を行っても、期待する結果にはなりません。

Kotlin

fun main() {
    val sortedList = itemList.sortedBy {
        it.first()
    }.sortedByDescending {
        it.slice(2..4)
    }
    
    for (item in sortedList) {
        println(item)
    }
    /**
     * Outputs
     * 
     * B-538
     * A-372
     * A-123
     * C-112
     * C-054
     * B-032
     * A-025
     * C-010
     * B-004
     * 
     */
}

これは、英字部分について昇順で並べ替えが行われた後、その結果を全て上書きする形で数字部分について降順で並べ替えられてしまうからです。

当然、昇順→降順ではなく、降順→昇順のように処理を逆にしたところで、どのみち期待する結果は得られません。

並べ替えの条件として、昇順と降順を同時に設定したい場合は、次の章で紹介するアプローチが必要となります。

    POINT!
  1. ・単純に昇順あるいは降順だけを条件に並び替えるのは簡単だが…!?
  2. ・昇順(降順)で並び替えた後、別の条件で並び替えを行うと全て上書きされてしまう!
  3. ・昇順と降順を同時に適用したい場合は、sortedBysortedByDescending では難しい!

sortedWith()を使う

昇順と降順を並べ替えの条件として同時に適用させるには、sortedWith() を使います。

そして、sortedWith()comparator に渡す値として、compareBy()compareByDescending() で昇順・降順の並べ替え条件を指定し、さらに thenBy()thenByDescending() で、もう一つの昇順・降順の並べ替え条件を指定すれば、昇順と降順を同時に適用した結果を得ることができます。

Descendingという単語がつく方が降順で、つかない方が昇順です。

下は、itemList に対し、sortedWith() を使って昇順・降順を同時に適用し、期待する結果が得られるように並び替えた例です。(※縦に長いので以下略)

Kotlin

val itemList = listOf(
    "A-123",
    "B-032",
    "A-025",
    "C-112",
    "C-054",
    "A-372",
    "B-004",
    "C-010",
    "B-538"
)

val expectedList = listOf(
    "A-372",
    "A-123",
    "A-025",
    "B-538",
    "B-032",
    "B-004",
    "C-112",
    "C-054",
    "C-010"
)

fun main() {
    val sortedList = itemList.sortedWith(
        compareBy<String> {
            it.first()
        }.thenByDescending {
            it.slice(2..4)
        }
    )
    
    for (item in sortedList) {
        println(item)
    }
    /**
     * Outputs
     * 
     * A-372
     * A-123
     * A-025
     * B-538
     * B-032
     * B-004
     * C-112
     * C-054
     * C-010
     * 
     */
     
     println(expectedList == sortedList) // Outputs: true
}

ということで、無事に期待通りの結果が得られていることが確認できました!

thenBy()thenByDescending() で処理を繋げる場合、compareBy()compareByDescending() において、データの型を明示的に指定しておく必要があります。

よって、次のコードはエラーになります。

Kotlin

val sortedList = itemList.sortedWith(
    compareBy { // This code will cause an error
        it.first()
    }.thenByDescending {
        it.slice(2..4)
    }
)

今回はリストアイテムを文字列型(String)で管理しているため、<String> と指定しておけばOKです。

ただし、これまで例として使用してきたリストのように、単に文字列が配列になっているだけのリストを扱うケースは、実際には稀であると思います。

実際のアプリ開発では、data classなどによって定義された文字列、整数、真偽値などを利用し、複数の要素を含んだリストを管理することが多いです。

たとえば、ユーザー情報(IDとパスワード)を管理する場合、IDを整数、パスワードを文字列として data class で設定し、それらをまとめてユーザー情報のリストとして管理する…といった感じですね。

ということで、次の章では data class を利用してリストアイテムが管理されている場合を例に、同じように昇順と降順を同時に適用させて並び替えを行ってみたいと思います。

    POINT!
  1. ・昇順と降順を同時に適用させるには、sortedWith()を使う!
  2. ・compareBy()やcompareByDescending()で最初の並び替えを行う!
  3. ・thenBy()やthenByDescending()で次の並び替えを行う!

data classでリストが管理されている場合

data classを利用してリストが管理されている場合も、前章で紹介した方法とほぼ同様の手順で昇順・降順が混在した並べ替えを行うことが可能です。

例として、次のような data class とリストがあったとしましょう。

Kotlin

data class Item (
    val group: String,
    val num: Int
)

val itemList = listOf(
    Item("A", 123),
    Item("B", 32),
    Item("A", 25),
    Item("C", 112),
    Item("C", 54),
    Item("A", 372),
    Item("B", 4),
    Item("C", 10),
    Item("B", 538),
)

そして今度は、英字(group)を降順にした上で、数字(num)を昇順にして並び替えを行ってみたいと思います。

つまり、compareByDescending()group 部分について降順で並び替えを行い、続けて thenBy()num 部分について昇順で並び替えを行うということですね。

コードと実行結果は次のようになります。

Kotlin

fun main() {
    val sortedList = itemList.sortedWith(
        compareByDescending<Item> {
            it.group
        }.thenBy {
            it.num
        }
    )
    
    for (item in sortedList) {
        println("${item.group}: ${item.num}")
    }
    
    /**
     * Outputs
     * 
     * C: 10
     * C: 54
     * C: 112
     * B: 4
     * B: 32
     * B: 538
     * A: 25
     * A: 123
     * A: 372
     */
     
}

ということで、data classを利用して管理されているリストの並べ替えについても、期待通りの結果を得ることができました!

なお、今回のコードの肝である sortedWith() について詳しい情報を得たい方は、公式ドキュメントをご参考ください。

Kotlinの公式サイトは英語表記ですが、Kotlinに限らずプログラミングの世界では公式ドキュメントの表記言語が英語であることが多いので、英語で書かれたページから情報を得ることに慣れておいた方が良いです。

もちろん翻訳ツールを頼っても良いので、興味を持って色々読んでみることをオススメします!

    POINT!
  1. ・data classでリストが管理されている場合も、ほぼ同じ手順で昇順・降順が混在した並び替えができる!
  2. ・より詳しい仕様や情報が必要な場合は、公式ドキュメントを読み込もう!
  3. ・公式ドキュメントは英語表記の場合が多いので、英文を読むことに抵抗をなくしておこう!

« »

カテゴリーリンク

著者について- author profile -

ROYDOプロフィール写真
Michihiro

モバイルアプリ(iOS・Android)ディベロッパー&デザイナー

これまでに、可読性の高いカラーパターンを自動で生成するアプリや、『第3火曜日』といった形式で通知をスケジュールできるアプリなどを制作。

サブでWebデザイン・フロントエンドエンジニアとしても活動しています。

📝ツール・言語:JavaScript/React Native/Kotlin/Android Studio/Swift/SwiftUI

🎓資格:基本情報技術者/ウェブデザイン技能検定3級

Twitterアイコン Instagramアイコン