如何使用Kotlin检查字符串数组是否按字母顺序排序?

6

我收到了一份字符串列表,需要判断它们是否按字母顺序排序。

我知道需要使用for循环并检查每个字符串的第一个字符,但我不知道如何从那里继续。

for (item in array)
     println(item[0])

例如 ["adam", "ben", "chloe"] 应返回 true
同样地,对于 ["ben", "adam", "chloe"] 应返回 false
7个回答

14

更新:自Kotlin 1.2起,以下内容可用:

最高效的变体,创建了最少的中间对象:

listOf(1, 2, 3).asSequence().zipWithNext { a, b -> a <= b }.all { it }

效率说明:仅创建两个 Sequence 对象和一个函数对象。使用 Boolean 常量作为中间值。最后的 all 调用被内联为序列上的 while 循环。


稍微不太有效率的变体:

listOf(1, 2, 3).asSequence().windowed(2).all { (a, b) -> a <= b }

它的效率略低,因为它会为原始列表的每个元素创建一个中间List(a, b)

@Billbucket和@AshishChaudhary在下面的答案中列出了这两种变体。


旧版本Kotlin的答案:

以下是一行代码:

val a = listOf("a", "b", "c")
a.zip(a.drop(1)).all { (a, b) -> a <= b }
// true

解释:

a.zip(a.drop(1)) 返回相邻元素对。如果每对中第一个元素小于或等于下一个元素,则数组有序。

如果您想提高性能,可以首先将您的数组包装在懒序列中。在这种情况下,数组不会被复制:

a.asSequence().let { it.zip(it.drop(1)).all { (a, b) -> a < b }  }
整件事是O(N)(单遍通过数组),这对于此任务是最优的。

既然你已经意识到你的“最佳”版本会创建不必要的中间对象,那么你应该称其为“最不低效”的而不是“最高效”的。有更好的方法来做到这一点。 - Alexey Nezhdanov
@AlexeyNezhdanov,如果您正在引用中间的Boolean对象,请注意,自动装箱将重用Boolean.TRUEBoolean.FALSE常量(已经足够高效),并且它们可能会被JIT优化掉。 - Aivean
@AlexeyNezhdanov,如果您知道更好的方法,请单独发布答案(或提供现有答案的链接)。 - Aivean
@AlexeyNezhdanov 我已经添加了一条关于效率的注释。再次强调:顶部变量创建的中间对象数量是与输入大小无关的常数。 - Aivean
@Aivean 我以为这很明显,但显然并非如此。最有效的方法是不创建任何中间对象。Kotlin使得这项任务变得相当繁琐,迫使人们编写效率低下的代码,并希望JIT能够修复它。 - Alexey Nezhdanov
显示剩余3条评论

6

您可以使用一个简单的一行代码:

array.zipWithNext { s1, s2 -> s1 <= s2 }.all { it }

这使得 a.zip(a.drop(1)) 更加美观 :) - Willi Mentzel
6
array.zipWithNext().all { it.first <= it.second } 可以翻译为:将数组中相邻的元素组合成对,然后判断所有这些对中,每个元素对的第一个元素是否小于等于第二个元素。 - arekolek

1
我相信你可以完全使用for循环完成你想要的任务。
然而,在Kotlin中,我个人认为更符合惯用法的做法是使用until来实现类似的功能:
fun isAlphabetical(stringsArray: Array<String>): Boolean {
    if (stringsArray.size < 2) return true
    return (1 until stringsArray.size).none { stringsArray[it] <= stringsArray[it - 1] }
}

fun main(args: Array<String>) {
    val testCases = arrayOf(arrayOf("adam", "ben", "chloe"), arrayOf("ben", "adam", "chloe"))

    for(testCase : Array<String> in testCases){
        println("The strings are: ${testCase.joinToString()}")
        if (isAlphabetical(testCase)) {
            println("Strings are in alphabetical order.\n")
        } else {
            println("Strings are not in alphabetical order.\n")        
        }
    }    
}

输出:

The strings are: adam, ben, chloe
Strings are in alphabetical order.

The strings are: ben, adam, chloe
Strings are not in alphabetical order.

基本上,只有当数组长度大于1时,您才会将数组的每个元素与前一个元素进行比较(使用<=)。

1
一个非常简单易懂的方法是通过对原始列表进行排序并与排序后的列表进行比较来实现(当两个列表具有相同的元素且顺序相同时,它们是相等的)。由于你提到你正在处理一个数组,所以你首先需要将其转换为一个列表。这种解决方案在性能方面不是最好的(它是O(n log n)并将数组转换两次为列表),但它非常易读。
val test = array.asList() == array.asList().sorted()

您问题的完整代码可能如下:

println(if (array.asList() == array.asList().sorted()) "Strings are in alphabetical order." else "Strings are not in alphabetical order.")

1
另一个解决方案:
val list = listOf("a", "b", "c")
list.windowed(2).none { (a, b) -> a > b }
// true

windowed 是一个不错的选择。但请注意,它仅在 Kotlin 1.2 及以上版本中可用。 - Aivean

1

如果您想比较任意的Comparable列表:

fun <T : Comparable<T>> isCollectionSortedAsc(list: Collection<T>): Boolean {
  return list.zipWithNext { a, b -> a.compareTo(b) == -1 }.all { it }
}

基于上面被接受的答案:https://dev59.com/UKbja4cB1Zd3GeqPlcRZ#47296632


1
这个答案的性能较差,因为没有使用 asSequence(),所以 zipWithNext() 会遍历整个集合并返回一个列表。 - headsvk

0

又一个解决方案 :)

data class Result(val isInOrder: Boolean, val lastString: String) {
    val toString = when {
        isInOrder -> "Strings are in alphabetical order."
        else -> "Strings are not in alphabetical order."
    }
}

fun Array<String>.isInAlphabeticalOrder() =
    this.fold(Result(true, ""), { acc, word -> Result(acc.isInOrder && acc.lastString < word, word) })

fun main(args: Array<String>) {
    val test1 = arrayOf("adam", "ben", "chloe")
    val test2 = arrayOf("ben", "adam", "chloe")
    println(test1.isInAlphabeticalOrder().toString)
    println(test2.isInAlphabeticalOrder().toString)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接