KT-31741: stdlib: DeepRecursiveFunction (#3398)
Introduces experimental coroutines-based framework to execute deeply recursive functions that utilities the heap and thus avoid StackOverflowError.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package test.utils
|
||||
|
||||
import kotlin.test.*
|
||||
|
||||
class DeepRecursiveTest {
|
||||
@Test
|
||||
fun testSimpleReturn() {
|
||||
// just returns a value without any recursive calls
|
||||
val ok = DeepRecursiveFunction<Int, String> { i -> "Ok$i" }
|
||||
assertEquals("Ok42", ok(42))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeepTreeDepth() {
|
||||
val n = 100_000
|
||||
assertEquals(n, depth(deepTree(n)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBinaryTreeDepth() {
|
||||
val k = 15
|
||||
assertEquals(k, depth(binaryTree(k)))
|
||||
}
|
||||
|
||||
private class MutualRec {
|
||||
val even: DeepRecursiveFunction<Tree?, Int> = DeepRecursiveFunction { t ->
|
||||
if (t == null) 0 else odd.callRecursive(t.left) + odd.callRecursive(t.right) + 1
|
||||
}
|
||||
|
||||
val odd: DeepRecursiveFunction<Tree?, Int> = DeepRecursiveFunction { t ->
|
||||
if (t == null) 0 else even.callRecursive(t.left) + even.callRecursive(t.right)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeepTreeOddEvenNodesMutual() {
|
||||
val n = 100_000
|
||||
val dt = deepTree(n)
|
||||
val rec = MutualRec()
|
||||
assertEquals(n / 2, rec.even(dt))
|
||||
assertEquals(n / 2, rec.odd(dt))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBinaryTreeOddEvenNodesMutual() {
|
||||
val k = 15
|
||||
val bt = binaryTree(k)
|
||||
val rec = MutualRec()
|
||||
assertEquals(21845, rec.even(bt))
|
||||
assertEquals(10922, rec.odd(bt))
|
||||
}
|
||||
|
||||
private class MutualAndDirectMixRec {
|
||||
val b: DeepRecursiveFunction<Int, String> = DeepRecursiveFunction { i -> "b$i" }
|
||||
|
||||
val a: DeepRecursiveFunction<Int, String> = DeepRecursiveFunction { i ->
|
||||
when (i) {
|
||||
// mix callRecursive calls to other function and in this context
|
||||
0 -> b.callRecursive(1) + callRecursive(2) + aa().callRecursive(3)
|
||||
else -> "a$i"
|
||||
}
|
||||
}
|
||||
|
||||
fun aa() = a
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMutualAndDirectMix() {
|
||||
// mix of callRecursion on this scope and on other DRF
|
||||
val rec = MutualAndDirectMixRec()
|
||||
val s = rec.a.invoke(0)
|
||||
assertEquals("b1a2a3", s)
|
||||
}
|
||||
|
||||
private class EqualToAnythingClassRec {
|
||||
var nullCount = 0
|
||||
|
||||
val a: DeepRecursiveFunction<Tree?, EqualToAnything> = DeepRecursiveFunction { t ->
|
||||
if (t == null) EqualToAnything(nullCount++) else b.callRecursive(t.left)
|
||||
}
|
||||
|
||||
val b: DeepRecursiveFunction<Tree?, EqualToAnything> = DeepRecursiveFunction { t ->
|
||||
if (t == null) EqualToAnything(nullCount++) else a.callRecursive(t.left)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEqualToAnythingClass() {
|
||||
// Mutually recursive tail calls & broken equals
|
||||
val rec = EqualToAnythingClassRec()
|
||||
val result = rec.a.invoke(deepTree(100))
|
||||
assertEquals(1, rec.nullCount)
|
||||
assertEquals(0, result.i)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBadClass() {
|
||||
val compute = object {
|
||||
val a: DeepRecursiveFunction<Bad, Bad> = DeepRecursiveFunction { v -> Bad(v.i + 1) }
|
||||
val b: DeepRecursiveFunction<Bad, Bad> = DeepRecursiveFunction { v ->
|
||||
when (v.i) {
|
||||
0 -> callRecursive(Bad(1))
|
||||
1 -> Bad(a.callRecursive(Bad(19)).i + callRecursive(Bad(2)).i)
|
||||
2 -> Bad(a.callRecursive(Bad(20)).i + 1)
|
||||
else -> error("Cannot happen")
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(42, compute.b(Bad(0)).i)
|
||||
}
|
||||
|
||||
private class Tree(val left: Tree? = null, val right: Tree? = null)
|
||||
|
||||
private fun deepTree(n: Int) = generateSequence(Tree()) { Tree(it) }.take(n).last()
|
||||
|
||||
private fun binaryTree(k: Int): Tree? =
|
||||
if (k == 0) null else Tree(binaryTree(k - 1), binaryTree(k - 1))
|
||||
|
||||
private val depth = DeepRecursiveFunction<Tree?, Int> { t ->
|
||||
if (t == null) 0 else maxOf(
|
||||
callRecursive(t.left),
|
||||
callRecursive(t.right)
|
||||
) + 1
|
||||
}
|
||||
|
||||
// It is equals to any other class
|
||||
private class EqualToAnything(val i: Int) {
|
||||
override fun equals(other: Any?): Boolean = true
|
||||
override fun toString(): String = "OK"
|
||||
}
|
||||
|
||||
// Throws exception on all object methods
|
||||
private class Bad(val i: Int) {
|
||||
override fun equals(other: Any?): Boolean = error("BAD")
|
||||
override fun hashCode(): Int = error("BAD")
|
||||
override fun toString(): String = error("BAD")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user