Home > Mobile >  Transform Kotlin List to certain size by padding or concatenating the last n elements
Transform Kotlin List to certain size by padding or concatenating the last n elements

Time:04-07

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.

  • Related