能否写出“for tuple in multiple range”?


#1

循环套循环是一个常见的需求,比如:

for x in (1..50) {
    for y in (50..100) { "做一些事情" }
}

或者:

(1..50).forEach{ x -> (50..100).forEach{ y -> "做一些事情" } }

请问能否写出类似下面的代码呢?

for ((x, y) in (1..50, 50..100)) { "做一些事情" }
for ((x, y, z) in (1..50, 50..100, 100..150)) { "做一些事情" }

我已经看了解构声明,但还是没写出来。


#2

可以是可以,但是 Kotlin 在语法层面并不支持元组,所以语法上可能没有你想的那么简洁

先写一个辅助类 RangePair,实现 Iterable 接口:

data class RangePair(val range1: IntRange, val range2: IntRange) : Iterable<Pair<Int, Int>> {

    override fun iterator(): Iterator<Pair<Int, Int>> {
        return iterator {
            for (x in range1) {
                for (y in range2) {
                    yield(Pair(x, y))
                }
            }
        }
    }
}

用法:

for ((x, y) in RangePair(1..10, 1..10)) {
    println("$x:$y")
}

#3

缺点是这个类只支持两层嵌套的循环,如果需要三层嵌套,就另写一个 RangeTriple。。。


#4

万能的StackOverflow帮我解决了这个问题。

如果只需要两个元素,这样应该比较简洁:

(1..100).zip(50 downTo 0).forEach { (x, y) -> /* do something */ }

或者这样:

fun <T1, T2> zipOf(first: Iterable<T1>, second: Iterable<T2>) = sequence {
    for (t1 in first)
        for (t2 in second)
            yield(t1 to t2)
}
for ((x, y) in zipOf(1..100, 50 downTo 0)) {}

这位作者还给出了一种通用解决方案:

fun <T> zipOf(vararg iterables: Iterable<T>): Sequence<List<T>> =
    iterables.fold(sequenceOf(emptyList())) { result, iterable ->
        result.flatMap { list -> iterable.asSequence().map { elm -> list + elm } }
    }

我们可以这样用:

for ((x, y) in zipOf(1..100, 50 downTo 0)) {}
for ((x, y, z) in zipOf(1..100, 50 downTo 0, 500..1000 step 2)) {}
for ((x, y, z, w) in zipOf(1..100, 50 downTo 0, 500..1000 step 2, 0..1)) {}

#5

zip和嵌套循环是不一样的,zip会拼接两个list,大小取小的那个的长度,嵌套循环实际上是两个list的交叉配对,大小是长度的乘积


京ICP备16022265号-2 Kotlin China 2017 - 2018