diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt index 6e84455e566..be8129d5a43 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt @@ -1265,6 +1265,21 @@ public fun Iterable.fold(MA: Monoid): A = public fun Iterable.foldMap(MB: Monoid, f: (A) -> B): B = fold(MB.empty()) { acc, a -> MB.run { acc.combine(f(a)) } } +/** + * Applies function [f] to each element + * and returns a list for the applied result Iterable. + * + * ```kotlin + * import arrow.core.crosswalk + * import io.kotest.matchers.shouldBe + * + * fun test() { + * val ints = listOf(1, 2) + * val res = ints.crosswalk { i -> listOf("a${i}", "b${i}", "c${i}") } + * res shouldBe listOf(listOf("a2", "a1"), listOf("b2", "b1"), listOf("c2", "c1")) + * } + * ``` + */ public fun Iterable.crosswalk(f: (A) -> Iterable): List> = fold(emptyList()) { bs, a -> f(a).align(bs) { ior -> @@ -1276,6 +1291,21 @@ public fun Iterable.crosswalk(f: (A) -> Iterable): List> = } } +/** + * Applies function [f] to each element + * and returns the concatenated Map of the applied result Map. + * + * ```kotlin + * import arrow.core.crosswalk + * import io.kotest.matchers.shouldBe + * + * fun test() { + * val ints = listOf(1, 2) + * val res = ints.crosswalkMap { i -> mapOf("a" to i, "b" to i, "c" to i) } + * res shouldBe listOf("a", "b", "c").map { a -> a to ints.reversed() }.toMap() + * } + * ``` + */ public fun Iterable.crosswalkMap(f: (A) -> Map): Map> = fold(emptyMap()) { bs, a -> f(a).align(bs) { (_, ior) -> @@ -1287,6 +1317,21 @@ public fun Iterable.crosswalkMap(f: (A) -> Map): Map without null. + * + * ```kotlin + * import arrow.core.crosswalk + * import io.kotest.matchers.shouldBe + * + * fun test() { + * val ints = listOf(1, 2) + * val res = ints.crosswalkNull { i -> if (i % 2 == 0) "x${i}" else null } + * res shouldBe listOf("x2") + * } + * ``` + */ public fun Iterable.crosswalkNull(f: (A) -> B?): List? = fold?>(emptyList()) { bs, a -> Ior.fromNullables(f(a), bs)?.fold( diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/IterableTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/IterableTest.kt index b2ceab7d9b1..f3bd57fd850 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/IterableTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/IterableTest.kt @@ -17,6 +17,7 @@ import io.kotest.property.arbitrary.list import io.kotest.property.arbitrary.orNull import io.kotest.property.arbitrary.pair import io.kotest.property.arbitrary.string +import io.kotest.property.arbitrary.char import io.kotest.property.checkAll import kotlin.math.max import kotlin.math.min @@ -663,4 +664,32 @@ class IterableTest : StringSpec({ listOf(1,2,3).compareTo(listOf(1,1,3)) shouldBe 1 } + "crosswalk" { + checkAll(Arb.pair(Arb.list(Arb.int()), Arb.list(Arb.char()))) { (ints, chars) -> + val res = ints.crosswalk { i -> chars.map { c -> "${c}${i}" } } + val expected = + if (ints.isNotEmpty()) chars.map { c -> ints.reversed().map { i -> "${c}${i}" } } + else emptyList() + res shouldBe expected + } + } + + "crosswalkMap" { + checkAll(Arb.pair(Arb.list(Arb.int()), Arb.list(Arb.char()))) { (ints, chars) -> + val res = ints.crosswalkMap { i -> chars.map { c -> c to i }.toMap() } + val expected = + if (ints.isNotEmpty()) chars.map { c -> c to ints.reversed() }.toMap() + else emptyMap() + res shouldBe expected + } + } + + "crosswalkNull" { + checkAll(Arb.list(Arb.int())) { ints -> + val res = ints.crosswalkNull { i -> if (i % 2 == 0) "x${i}" else null } + val expected = ints.mapNotNull { i -> if (i % 2 == 0 ) "x${i}" else null }.reversed() + res shouldBe expected + } + } + })