8

I am given a list of strings and I need to see if they are in alphabetical order.

I know I need to use a for loop and check the first character of each string but I don't know how to progress from there.

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

For example ["adam", "ben", "chloe"] should return true.

And likewise for ["ben", "adam", "chloe"] should return false.

7 Answers 7

16

UPD: Since Kotlin 1.2 the following is available:

Most efficient variant, creates the smallest amount of intermediate objects:

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

Note on efficiency: Creates only two Sequence objects and one function object. Uses Boolean constants as an intermediate value. The final all invocation is inlined as a while loop over the sequence.


Slightly less efficient variant:

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

It's slightly less efficient, as it creates creates intermediate List(a, b) for every element of the original list.

Both of these variants were listed in the answers below by @Billbucket and @AshishChaudhary.


Old answer, for previous versions of Kotlin:

Here is a one liner:

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

Explanation:

a.zip(a.drop(1)) returns pairs of neighbour elements. If in every pair first element is less or equal to the next, array is in order.

If you want to improve performance, you can wrap your array in lazy sequence first. In this case array won't be copied:

a.asSequence().let { it.zip(it.drop(1)).all { (a, b) -> a < b }  }

The whole thing is O(N) (single pass through array), which is optimal for this task.

Sign up to request clarification or add additional context in comments.

8 Comments

Better to stop when a violation is found. This works better in place of .all : .none { (a, b) -> a > b }
@AshishChaudhary it doesn't matter. These functions are symmetrical and both stop when first violation is found.
Since you are actually aware that your "best" variant creates unnecessary intermediate objects you should be calling it "least inefficient" rather than "most efficient". There are better ways to do that.
@AlexeyNezhdanov, if you are referring to the intermediate Boolean objects, then please note, that autoboxing will reuse Boolean.TRUE and Boolean.FALSE constants (already efficient enough), and they will probably be optimized away by JIT.
@AlexeyNezhdanov, also, if you know a better way, please post it as a separate answer (or give the link to an existing answer).
|
6

you can use a simple one-liner:

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

2 Comments

this makes a.zip(a.drop(1)) even nicer :)
array.zipWithNext().all { it.first <= it.second }
1

Im sure you could do your desired task completely using a for-loop.

However in Kotlin I personally think it would be more idiomatic to do something like this using 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")        
        }
    }    
}

Output:

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

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

Basically you only compare each element of the array with the previous element (using <=) if the length of the array is more than 1.

Comments

1

A very simple and easy way to accomplish it is by sorting and comparing the original list with the sorted one (two lists are equal when they have the exact same elements in the same order). Since you mentioned you are dealing with an array, you first need to convert it to a list. This solution is not the best in terms of performance (it's O(n log n) and converts the array twice to a list) but it's very readable:

val test = array.asList() == array.asList().sorted()

The full code for your question could be:

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

Comments

1

Another solution:

val list = listOf("a", "b", "c")
list.windowed(2).none { (a, b) -> a > b }
// true

1 Comment

windowed is a good catch. But note, it is available only since Kotlin 1.2.
1

In case you want to compare arbitrary Comparable list:

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

Based on the accepted answer above: https://stackoverflow.com/a/47296632/4919972

1 Comment

This answer has worse performance because asSequence() is not used so zipWithNext() goes through the whole collection and returns a list.
0

Yet another solution :)

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)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.