'Transform Kotlin List to certain size by padding or concatenating the last n elements
I often need to either shorten or pad a List to a certain amount of entries. For that I use a function like this:
fun List<String>.compactOrPadEnd(size: Int): List<String> {
if (this.size < size)
return this + List(if (this.size < size) size - this.size else 0) { "" }
else
return this.subList(0, size - 1) + this.subList(size - 1, this.size).joinToString("")
}
val list0 = emptyList<String>()
val list1 = listOf("A")
val list2 = listOf("A", "B")
val list3 = listOf("A", "B", "C")
val list4 = listOf("A", "B", "C", "D")
val list5 = listOf("A", "B", "C", "D", "E")
val size = 3
list0. compactOrPadEnd(size).onEach(::println) // [ , , ]
list1. compactOrPadEnd(size).onEach(::println) // [A, , ]
list2. compactOrPadEnd(size).onEach(::println) // [A, B, ]
list3. compactOrPadEnd(size).onEach(::println) // [A, B, C]
list4. compactOrPadEnd(size).onEach(::println) // [A, B, CD]
list5. compactOrPadEnd(size).onEach(::println) // [A, B, CDE]
The above code is more readable with separate functions:
fun List<String>.padEnd(size: Int) =
this + List(if (this.size < size) size - this.size else 0) { "" }
fun List<String>.compact(size: Int) =
this.subList(0, size - 1) + this.subList(size - 1, this.size).joinToString("")
fun List<String>.compactAndPadEnd(size: Int): List<String> =
if (this.size < size) padEnd(size) else compact(size)
I find both solutions too clumsy. I went through all the built-in collection functions to come up with something simpler, but to no avail.
Small side question: is there a better name than compactAndPadEnd?
Solution 1:[1]
You can write this as one case (i.e. without if-else) if you take advantage of the fact that joinToString happens to return your pad element "" when the list is empty.
fun List<String>.resizeEnd(size: Int): List<String> =
this.subList(0, min(size - 1, this.size)) +
this.subList(min(size - 1, this.size), this.size).joinToString("") +
List(max(0, size - this.size - 1)) { "" }
Notice that I'm creating a list of size size - this.size - 1 at the end. -1 because one of the empty strings would have been the one returned by joinToString("").
If you don't mind drop and take creating extra lists, you can make it shorter:
fun List<String>.resizeEnd(size: Int): List<String> =
this.take(size - 1) +
this.drop(size - 1).joinToString("") +
List(max(0, size - this.size - 1)) { "" }
You can also generalise this to:
fun <T> List<T>.resizeEnd(size: Int, padElement: T, foldFunction: (T, T) -> T): List<T> =
this.subList(0, min(size - 1, this.size)) +
this.subList(min(size - 1, this.size), this.size).fold(padElement, foldFunction) +
List(max(0, size - this.size)) { padElement }
But the catch is that padElement must be the identity for foldFunction.
Solution 2:[2]
As an alternative, you could create a List of fixed size using an initializer function combined with a when to initialize each item:
fun List<String>.resize(size: Int) = List(size) {
when {
it == size - 1 && this.size > size -> this.subList(size - 1, this.size).joinToString("")
this.size > it -> this[it]
else -> ""
}
}
I'm not entirely sure if this is any less clumsy, I suppose that depends on personal preference.
Solution 3:[3]
I see some slight clean-up you can do on your function. You're redundantly checking this.size < size, you could lift return out of the condition branches, and you could use take/drop for brevity.
fun List<String>.compactOrPadEnd(size: Int): List<String> {
return if (this.size < size)
this + List(size - this.size) { "" }
else
take(size - 1) + drop(size - 1).joinToString("")
}
Personally, I'd call it concatEndOrPad.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Sweeper |
| Solution 2 | Abby |
| Solution 3 |
