1. 简介

字典序排序(lexicographical order)本质上就是按字母顺序排列字符串,类似于词典中的单词排序方式。例如,给定字符串列表 ["banana", "Apple", "cherry", "Date"],排序后应为 ["Apple", "banana", "cherry", "Date"]。✅ 注意:这个过程通常是忽略大小写的。

这在处理用户姓名、标签、配置项等需要自然语言排序的场景中非常常见。如果直接按 ASCII 排序,大写字母会优先于小写字母(比如 "A" < "a"),导致结果不符合预期 ❌。

本文将介绍在 Kotlin 中实现不区分大小写的字典序排序的几种方法,并分析其适用场景和踩坑点。


2. 手动实现排序逻辑(迭代法)

最原始的方式是自己实现排序算法,比如使用双重循环进行冒泡或选择排序。核心思路是:

  • 遍历每个元素 words[i]
  • 将其与后续所有元素 words[j] 进行比较
  • 比较前先统一转为小写,避免大小写影响排序结果

⚠️ 直接使用 > 比较字符串会基于 Unicode 值,无法做到忽略大小写。

fun sortWithCustomMethod(words: Array<String>) {
    for (i in 0..words.size - 2) {
        for (j in i + 1 until words.size) {
            if (words[i].lowercase() > words[j].lowercase()) {
                val temp = words[i]
                words[i] = words[j]
                words[j] = temp
            }
        }
    }
}

✅ 测试验证

@Test
fun `sort using custom method`() {
    val words = arrayOf("banana", "apple", "cherry", "date", "A", "Result", "Union")

    sortWithCustomMethod(words)
    assertContentEquals(
        arrayOf("A", "apple", "banana", "cherry", "date", "Result", "Union"),
        words
    )
}

📌 缺点:时间复杂度 O(n²),性能差,仅适合学习理解排序原理,生产环境不推荐 ❌。


3. 使用 forEach + Lambda 实现迭代

我们可以通过 Kotlin 的 forEachIndexed 和嵌套 forEach 来简化循环结构,避免手动管理索引边界。

这种方式逻辑清晰,代码更函数式一些,但本质仍是 O(n²) 的选择排序变体。

fun sortStringsIgnoreCaseForEach(words: List<String>): List<String> {
    val sortedWords = words.toMutableList()
    sortedWords.forEachIndexed { i, word ->
        (i until sortedWords.size).forEach { j ->
            if (word.lowercase().compareTo(sortedWords[j].lowercase()) > 0) {
                sortedWords[i] = sortedWords[j].also { sortedWords[j] = sortedWords[i] }
            }
        }
    }
    return sortedWords
}

✅ 测试用例

@Test
fun `sort using forEachIndexed method`() {
    val words = listOf("banana", "apple", "cherry", "date", "A", "Result", "Union")

    val sortedWords = sortStringsIgnoreCaseForEach(words)
    assertContentEquals(
        listOf("A", "apple", "banana", "cherry", "date", "Result", "Union"),
        sortedWords
    )
}

📌 注意点

  • 使用了 compareTo() 而不是 >,更安全且语义明确
  • also 用于交换值,是 Kotlin 常见技巧
  • 返回新列表,原列表不受影响 ✅

但仍属于低效实现,仅作教学参考。


4. 使用 sortedWith() 配合自定义 Comparator

Kotlin 提供了强大的标准库函数 sortedWith(),它接受一个 Comparator<T> 并返回一个新的已排序列表。

我们可以使用 Java 标准库提供的 String.CASE_INSENSITIVE_ORDER,这是一个现成的不区分大小写的比较器。

@Test
fun `sort using sortedWith() method`() {
    val words = arrayOf("banana", "apple", "cherry", "date", "A", "Result", "Union")
    val sortedWords = words.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it })

    assertContentEquals(
        listOf("A", "apple", "banana", "cherry", "date", "Result", "Union"),
        sortedWords
    )
}

📌 关键点解析

  • compareBy(Comparator) 是构建比较器的工厂函数
  • String.CASE_INSENSITIVE_ORDER 是 Java 中定义的静态 Comparator<String>
  • 排序稳定,性能良好 ✅
  • 返回的是新列表,原数组不变 ✅

推荐场景:需要高度定制化排序规则时使用,比如组合多个字段排序。


5. 使用 sortedBy() 配合映射函数

这是最简洁、最常用的写法。sortedBy() 接收一个 lambda,表示“根据什么值来排序”。

我们要实现忽略大小写的字典序,只需将每个字符串映射为其小写形式即可。

@Test
fun `sort using sortedBy() method`() {
    val words = arrayOf("banana", "apple", "cherry", "date", "A", "Result", "Union")
    val sortedWords = words.sortedBy { it.lowercase() }

    assertContentEquals(
        listOf("A", "apple", "banana", "cherry", "date", "Result", "Union"),
        sortedWords
    )
}

✅ 优势总结

  • 语法极简,一目了然 ✅
  • 内部使用高效排序算法(Timsort)
  • 支持链式调用,可扩展性强
  • 是 Kotlin 社区中最常见的做法

⚠️ 踩坑提醒

  • sortedBy() 返回新列表;若需原地排序,使用 sortWith()sortBy()(作用于 MutableList)
  • 对于空字符串或 null 值需额外处理(本文假设输入非空)

6. 总结

方法 是否推荐 说明
手动迭代 教学用途,性能差
forEach 实现 函数式写法但效率低
sortedWith() + Comparator 灵活,适合复杂排序逻辑
sortedBy() + lowercase() ✅✅✅ 最佳实践,简洁高效

📌 最终建议

在绝大多数场景下,优先使用 sortedBy { it.lowercase() } 实现不区分大小写的字典序排序。代码清晰、性能优秀,符合 Kotlin 的惯用法(idiomatic Kotlin)。

只有在需要复合排序(如先按长度再按名称)或复用已有 Comparator 时,才考虑 sortedWith()


原始标题:How to Sort Elements in Lexicographical Order in Kotlin