Alexander Glejezer
Alexander Glejezer

Reputation: 21

Divide list into two list

Is there a simple way to divide list of Double into two lists of pairs in Kotlin?

In such way:

[x1, y1, x2, y2, x3, y3] => [(x1, x2), (x2, x3), (x3, x1)], [(y1, y2), (y2, y3), (y3, y1)] 

I tried to use filterIndexed and zipWithNext

val x = filterIndexed { index, _ -> index % 2 == 0 }.zipWithNext()
val y = filterIndexed { index, _ -> index % 2 == 1 }.zipWithNext()

But the result is:

[x1, y1, x2, y2, x3, y3] => [(x1, x2), (x2, x3)], [(y1, y2), (y2, y3)] 

Upvotes: 2

Views: 177

Answers (3)

lukas.j
lukas.j

Reputation: 7163

val input = listOf("x1", "y1", "x2", "y2", "x3", "y3")

val result = list
  .withIndex()
  .groupBy { it.index % 2 }
  .map { entry -> entry.value.map { it.value } }
  .map { (it + it[0]).zipWithNext() }

println(result)

Output:

[[(x1, x2), (x2, x3), (x3, x1)], [(y1, y2), (y2, y3), (y3, y1)]]

Upvotes: 1

Ivo
Ivo

Reputation: 23164

You could do something like this:

val lst = listOf(1, 2, 3, 4, 5, 6, 7, 8)

val intermediate = lst.chunked(2).map { it[0] to it[1] }.let { it + it[0] }
val x = intermediate.map { it.first }.zipWithNext()
val y = intermediate.map { it.second }.zipWithNext()

println(x) //[(1, 3), (3, 5), (5, 7), (7, 1)]
println(y) //[(2, 4), (4, 6), (6, 8), (8, 2)]

Upvotes: 2

Sweeper
Sweeper

Reputation: 271410

If I understand correctly, the problem with the zipWithNext that you are using is that it doesn't "wrap around", i.e. output the final (x3, x1) or (y3, y1) pair, containing the last and first elements of the list.

You can fix this by simply declaring your own version of zipWithNext that does do this.

You can either do something like this:

fun <T> Iterable<T>.zipWithNextAndWrapAround(): List<Pair<T, T>> {
    val zippedWithNext = zipWithNext()
    if (zippedWithNext.isEmpty()) return zippedWithNext
    return zippedWithNext + (zippedWithNext.last().second to zippedWithNext.first().first)
}

Or copy and paste over the original source code of zipWithNext and slightly modify it:

fun <T> Iterable<T>.zipWithNextAndWrapAround(): List<Pair<T, T>> {
    val iterator = iterator()
    if (!iterator.hasNext()) return emptyList()
    val result = mutableListOf<Pair<T, T>>()
    var current = iterator.next()

    // remember what the first element was
    val first = current
    while (iterator.hasNext()) {
        val next = iterator.next()
        result.add(current to next)
        current = next
    }

    // at last, add this pair
    result.add(current to first)
    return result
}

Usage:

val x = list.filterIndexed { index, _ -> index % 2 == 0 }.zipWithNextAndWrapAround()
val y = list.filterIndexed { index, _ -> index % 2 == 1 }.zipWithNextAndWrapAround()

Note that this is looping through the list twice. You can avoid that by writing your own version of partition called partitionIndexed.

The code could be something like:

inline fun <T> Iterable<T>.partitionIndexed(predicate: (Int, T) -> Boolean): Pair<List<T>, List<T>> {
    val first = ArrayList<T>()
    val second = ArrayList<T>()
    forEachIndexed { index, element ->
        if (predicate(index, element)) {
            first.add(element)
        } else {
            second.add(element)
        }
    }
    return Pair(first, second)
}

// usage:
val (x, y) = list.partitionIndexed { index, _ -> 
    index % 2 == 0 
}.let { (a, b) ->
    a.zipWithNextAndWrapAround() to b.zipWithNextAndWrapAround()
}

Upvotes: 2

Related Questions