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?
CodePudding user response:
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
.
CodePudding user response:
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.
CodePudding user response:
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
.