1. Overview
When we work with Kotlin, we often need to manipulate the contents of a list. One common operation is swapping elements in a MutableList. Swapping elements is a fundamental operation in programming and can be valuable in situations such as sorting algorithms, reordering lists, or even shuffling decks of cards.
In this tutorial, we’ll discuss different ways to swap elements in a mutable list. Moreover, we’ll finally come to an idiomatic and fluent approach to the swapping problem.
For simplicity, we’ll use unit test assertions to verify whether each approach can produce the expected result.
2. Using the java.util.Collections.swap() Function
If we focus on Kotlin on the JVM, all APIs provided by the Java standard library are available for Kotlin.
We know the java.util.Collections class offers many convenient helper methods, such as copy(), min(), max(), and so on. For list element swapping tasks, the swap() method is the right one:
val myList = mutableListOf("A", "B", "C", "D", "E", "F")
val expected = listOf("A", "E", "C", "D", "B", "F")
Collections.swap(myList, 1, 4)
assertEquals(expected, myList)
As the example above shows, the swap() method is pretty straightforward. We pass the mutable list and the two indexes of the elements we want to swap to swap(), and it does the job for us.
We know Kotlin brings a bunch of great features that allow us to write concise and fluent codes. So next, let’s see how we can solve this problem in Kotlin ways.
3. Creating an Element Swap Function
Swapping two elements in a list is similar to swapping two variables. In Kotlin, we can swap two variables in different ways.
For example, we can use the classical approach to swap two elements using a temporary variable:
val myList = mutableListOf("A", "B", "C", "D", "E", "F")
val expected = listOf("A", "E", "C", "D", "B", "F")
val t = myList[1]
myList[1] = myList[4]
myList[4] = t
assertEquals(expected, myList)
Alternatively, we can first create a Pair object to hold the two elements’ original values, then reassign them:
val myList = mutableListOf("A", "B", "C", "D", "E", "F")
val expected = listOf("A", "E", "C", "D", "B", "F")
(myList[4] to myList[1]).apply {
myList[1] = first
myList[4] = second
}
assertEquals(expected, myList)
In the example above, we used the apply() function to reassign the two elements. If we like, it can be done with the also() function too:
(myList[4] to myList[1]).also {
myList[1] = it.first
myList[4] = it.second
}
Since we’ve mentioned the also() scope function, let’s see one more clever way to swap a mutable list’s two elements using also():
val myList = mutableListOf("A", "B", "C", "D", "E", "F")
val expected = listOf("A", "E", "C", "D", "B", "F")
myList[1] = myList[4].also { myList[4] = myList[1] }
assertEquals(expected, myList)
As we can see in the test above, the line “*myList[1] = myList[4].also { myList[4] = myList[1] }*” does the job. a = b.also {b = a} may look a bit weird. Let’s understand what’s going on behind it.
First, it’s an assignment statement: a = [something]. So, the “something” part will be processed. Then, it assigns the variable a with the result of [something].
Here, [something] is an also() function call. also() is an extension function of any type T: public inline fun
In other words, myList[4].also { myList[4] = myList[1] } does the following:
- Assign myList[4] = myList[1]
- Return the string “E”
As [something] returns “E”, after the myList[1] = [something] assignment, myList[1] has the value “E.” Thus, myList[1] and myList[4] have been exchanged.
We’ve seen three Kotlin approaches to swap elements in a mutable list. Of course, if we want to swap any two elements in a mutable list, we can wrap any solution in a function, for example:
fun <T> swapElements(theList: MutableList<T>, idx1: Int, idx2: Int) {
val t = theList[idx1]
theList[idx1] = theList[idx2]
theList[idx2] = t
}
4. Improving the swapElements() Function
Using our self-made swapElements() is quite similar to Java’s Collections.swap() method. However, there are a couple of items we can still improve:
- To call this function, we must first type the function name, then pass the list and indexes to it. It isn’t a natural way to use it.
- It returns Unit/void, which means it cannot be chained fluently with other function calls.
So next, let’s solve these problems by transforming the swapElements() function into an extension function:
fun <T> MutableList<T>.swap(idx1: Int, idx2: Int): MutableList<T> = apply {
val t = this[idx1]
this[idx1] = this[idx2]
this[idx2] = t
}
As the code above shows, the swap() function is an extension to MutableList so that we can invoke it naturally:
val myList = mutableListOf("A", "B", "C", "D", "E", "F")
val expected = listOf("A", "E", "C", "D", "B", "F")
assertEquals(expected, myList.swap(1, 4))
Further, we made the swap() function return the caller (MutableList) itself. Therefore, it can be fluently used in a chain of function calls, for example:
val myList = mutableListOf("A", "B", "C", "D", "E", "F")
val result = myList.swap(1, 4)
.windowed(2, 2)
val expected = listOf(listOf("A", "E"), listOf("C", "D"), listOf("B", "F"))
assertEquals(expected, result)
5. Conclusion
In this article, we’ve explored different approaches to swapping two elements in a Kotlin mutable list. Also, we’ve discussed how to create an idiomatic extension function to call the swap() function naturally and fluently.
As always, the complete source code for the examples is available over on GitHub.