moved to Kotlin multiplatform
This commit is contained in:
34
src/commonMain/kotlin/net.woggioni.klevtree/CharTrie.kt
Normal file
34
src/commonMain/kotlin/net.woggioni.klevtree/CharTrie.kt
Normal file
@@ -0,0 +1,34 @@
|
||||
package net.woggioni.klevtree
|
||||
|
||||
import net.woggioni.klevtree.node.CharNode
|
||||
import net.woggioni.klevtree.node.TrieNode
|
||||
|
||||
open class CharTrie<PAYLOAD> : Trie<CharNode<PAYLOAD>, Char, PAYLOAD>() {
|
||||
private class CaseInsensitiveKeyChecker : Keychecker<Char> {
|
||||
override fun check(key1: Char?, key2: Char?) = key1 == key2
|
||||
}
|
||||
|
||||
private class CaseSensitiveKeyChecker : Keychecker<Char> {
|
||||
override fun check(key1: Char?, key2: Char?) = key1?.lowercaseChar() == key2?.lowercaseChar()
|
||||
}
|
||||
|
||||
override val root: TrieNode<Char, PAYLOAD> = CharNode(null)
|
||||
override val tails = mutableListOf<TrieNode<Char, PAYLOAD>>()
|
||||
override var keyChecker: Keychecker<Char> = CaseSensitiveKeyChecker()
|
||||
|
||||
var caseSensitive : Boolean = true
|
||||
set(value) {
|
||||
if(value) {
|
||||
keyChecker = CaseSensitiveKeyChecker()
|
||||
} else {
|
||||
keyChecker = CaseInsensitiveKeyChecker()
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
fun add(word : String) = super.add(word.asIterable())
|
||||
|
||||
fun search(word : String) : TrieNode<Char, PAYLOAD>? = search(word.asIterable().toList())
|
||||
|
||||
fun remove(word : String) = remove(word.asIterable().toList())
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
package net.woggioni.klevtree
|
||||
|
||||
import net.woggioni.klevtree.tree.TreeNodeVisitor
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
sealed class DistanceCalculator {
|
||||
abstract fun compute(keyChecker : Trie.Keychecker<Char>,
|
||||
stack: List<TreeNodeVisitor.StackContext<LevNode, Unit>>,
|
||||
wordkey: String,
|
||||
worstCase : Int) : TreeNodeVisitor.VisitOutcome
|
||||
|
||||
object LevenshteinDistanceCalculator : DistanceCalculator() {
|
||||
override fun compute(keyChecker : Trie.Keychecker<Char>,
|
||||
stack: List<TreeNodeVisitor.StackContext<LevNode, Unit>>,
|
||||
wordkey: String,
|
||||
worstCase: Int) : TreeNodeVisitor.VisitOutcome {
|
||||
val previousStackElement = stack[stack.size - 2]
|
||||
val currentStackElement = stack.last()
|
||||
val previousRow : IntArray = previousStackElement.node.payload!!
|
||||
val currentRow : IntArray = currentStackElement.node.payload!!
|
||||
for (i in 1..wordkey.length) {
|
||||
if(keyChecker.check(wordkey[i - 1], currentStackElement.node.key)) {
|
||||
currentRow[i] = previousRow[i - 1]
|
||||
} else {
|
||||
currentRow[i] = min(min(currentRow[i - 1], previousRow[i -1]), previousRow[i]) + 1
|
||||
}
|
||||
}
|
||||
return if(worstCase >= 0 && worstCase <= currentRow.minOrNull()!!) {
|
||||
TreeNodeVisitor.VisitOutcome.SKIP
|
||||
} else {
|
||||
TreeNodeVisitor.VisitOutcome.CONTINUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DamerauLevenshteinDistanceCalculator : DistanceCalculator() {
|
||||
override fun compute(keyChecker : Trie.Keychecker<Char>,
|
||||
stack: List<TreeNodeVisitor.StackContext<LevNode, Unit>>,
|
||||
wordkey: String,
|
||||
worstCase : Int) : TreeNodeVisitor.VisitOutcome {
|
||||
val pse = stack[stack.size - 2]
|
||||
val cse = stack.last()
|
||||
val prow : IntArray = pse.node.payload!!
|
||||
val crow : IntArray = cse.node.payload!!
|
||||
for (i in 1..wordkey.length) {
|
||||
if (keyChecker.check(wordkey[i - 1], cse.node.key)) {
|
||||
crow[i] = prow[i - 1]
|
||||
} else {
|
||||
crow[i] = min(min(crow[i - 1], prow[i - 1]), prow[i]) + 1
|
||||
}
|
||||
if (stack.size > 2 && i > 1 && keyChecker.check(wordkey[i - 2], cse.node.key)
|
||||
&& keyChecker.check(wordkey[i - 1], pse.node.key)) {
|
||||
val ppse = stack[stack.size - 3]
|
||||
val pprow: IntArray = ppse.node.payload!!
|
||||
crow[i] = min(crow[i], pprow[i - 2] + 1)
|
||||
}
|
||||
}
|
||||
return if(worstCase >= 0 && worstCase <= prow.minOrNull()!!) {
|
||||
TreeNodeVisitor.VisitOutcome.SKIP
|
||||
} else {
|
||||
TreeNodeVisitor.VisitOutcome.CONTINUE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
86
src/commonMain/kotlin/net.woggioni.klevtree/LevTrie.kt
Normal file
86
src/commonMain/kotlin/net.woggioni.klevtree/LevTrie.kt
Normal file
@@ -0,0 +1,86 @@
|
||||
package net.woggioni.klevtree
|
||||
|
||||
import net.woggioni.klevtree.node.CharNode
|
||||
import net.woggioni.klevtree.node.TrieNode
|
||||
import net.woggioni.klevtree.tree.TreeNodeVisitor
|
||||
import net.woggioni.klevtree.tree.TreeWalker
|
||||
|
||||
internal typealias LevNode = TrieNode<Char, IntArray>
|
||||
|
||||
class LevTrie : CharTrie<IntArray>() {
|
||||
|
||||
override val root: TrieNode<Char, IntArray> = CharNode(null)
|
||||
|
||||
override val tails = mutableListOf<TrieNode<Char, IntArray>>()
|
||||
|
||||
enum class Algorithm {
|
||||
/**
|
||||
* Plain Levenshtein distance
|
||||
*/
|
||||
LEVENSHTEIN,
|
||||
/**
|
||||
* Damerau-Levenshtein distance
|
||||
*/
|
||||
DAMERAU_LEVENSHTEIN
|
||||
}
|
||||
|
||||
private var distanceCalculator : DistanceCalculator = DistanceCalculator.LevenshteinDistanceCalculator
|
||||
|
||||
var algorithm : Algorithm
|
||||
get() {
|
||||
return when(distanceCalculator) {
|
||||
DistanceCalculator.LevenshteinDistanceCalculator -> Algorithm.LEVENSHTEIN
|
||||
DistanceCalculator.DamerauLevenshteinDistanceCalculator -> Algorithm.DAMERAU_LEVENSHTEIN
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
distanceCalculator = when(value) {
|
||||
Algorithm.LEVENSHTEIN -> DistanceCalculator.LevenshteinDistanceCalculator
|
||||
Algorithm.DAMERAU_LEVENSHTEIN -> DistanceCalculator.DamerauLevenshteinDistanceCalculator
|
||||
}
|
||||
}
|
||||
|
||||
fun fuzzySearch(word : String, maxResult: Int) : List<Pair<String, Int>> {
|
||||
val comparator : Comparator<Pair<String, Int>> = compareBy({ it.second }, { it.first })
|
||||
val result = mutableListOf<Pair<String, Int>>()
|
||||
val requiredSize = word.length + 1
|
||||
val visitor = object: TreeNodeVisitor<LevNode, Unit> {
|
||||
override fun visitPre(stack: List<TreeNodeVisitor.StackContext<LevNode, Unit>>): TreeNodeVisitor.VisitOutcome {
|
||||
val currentStackElement = stack.last()
|
||||
val currentNode = currentStackElement.node
|
||||
if(currentNode.payload == null ||
|
||||
currentNode.payload!!.size < requiredSize) {
|
||||
if(stack.size == 1) {
|
||||
currentNode.payload = IntArray(requiredSize) { i -> i }
|
||||
} else {
|
||||
currentNode.payload = IntArray(requiredSize) { i -> if(i == 0) stack.size - 1 else 0 }
|
||||
}
|
||||
}
|
||||
if(stack.size > 1) {
|
||||
if(currentStackElement.node.key == null) {
|
||||
val sb = StringBuilder()
|
||||
for(c in currentStackElement.node.linealDescendant()) {
|
||||
sb.append(c)
|
||||
}
|
||||
val candidate = sb.toString()
|
||||
val distance = stack[stack.size - 2].node.payload!![word.length]
|
||||
result.add(candidate to distance)
|
||||
result.sortWith(comparator)
|
||||
if(result.size > maxResult) {
|
||||
result.remove(result.last())
|
||||
}
|
||||
return TreeNodeVisitor.VisitOutcome.SKIP
|
||||
} else {
|
||||
return distanceCalculator.compute(keyChecker, stack, word,
|
||||
if(result.size == maxResult) result.last().second else -1)
|
||||
}
|
||||
} else {
|
||||
return TreeNodeVisitor.VisitOutcome.CONTINUE
|
||||
}
|
||||
}
|
||||
}
|
||||
val walker = TreeWalker(visitor)
|
||||
walker.walk(root)
|
||||
return result.toList()
|
||||
}
|
||||
}
|
153
src/commonMain/kotlin/net.woggioni.klevtree/Trie.kt
Normal file
153
src/commonMain/kotlin/net.woggioni.klevtree/Trie.kt
Normal file
@@ -0,0 +1,153 @@
|
||||
package net.woggioni.klevtree
|
||||
|
||||
import net.woggioni.klevtree.node.TrieNode
|
||||
import net.woggioni.klevtree.tree.TreeNodeVisitor
|
||||
import net.woggioni.klevtree.tree.TreeWalker
|
||||
|
||||
abstract class Trie<T : TrieNode<KEY, PAYLOAD>, KEY, PAYLOAD> {
|
||||
|
||||
interface Keychecker<KEY> {
|
||||
fun check(key1 : KEY?, key2 : KEY?) : Boolean
|
||||
}
|
||||
|
||||
protected abstract var keyChecker : Keychecker<KEY>
|
||||
|
||||
protected abstract val root : TrieNode<KEY, PAYLOAD>
|
||||
|
||||
protected abstract val tails : MutableList<TrieNode<KEY, PAYLOAD>>
|
||||
val words : Iterable<List<KEY>>
|
||||
get() {
|
||||
val res = object : Iterator<List<KEY>> {
|
||||
val it = tails.iterator()
|
||||
override fun hasNext(): Boolean {
|
||||
return it.hasNext()
|
||||
}
|
||||
|
||||
override fun next(): List<KEY> {
|
||||
return it.next().linealDescendant()
|
||||
}
|
||||
}
|
||||
return object : Iterable<List<KEY>> {
|
||||
override fun iterator() : Iterator<List<KEY>> {
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addNode(key : KEY?, parent : TrieNode<KEY, PAYLOAD>, prev : TrieNode<KEY, PAYLOAD>? = null) : TrieNode<KEY, PAYLOAD> {
|
||||
val result = TrieNode<KEY, PAYLOAD>(key)
|
||||
result.parent = parent
|
||||
if(prev != null) {
|
||||
prev.next = result
|
||||
result.prev = prev
|
||||
} else {
|
||||
when(parent.child) {
|
||||
null -> parent.child = result
|
||||
else -> {
|
||||
var node : TrieNode<KEY, PAYLOAD>? = parent.child
|
||||
while(node!!.next != null) {
|
||||
node = node.next
|
||||
}
|
||||
node.next = result
|
||||
result.prev = node
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun add(path : Iterable<KEY>) : Pair<Boolean, TrieNode<KEY, PAYLOAD>?> {
|
||||
var result = false
|
||||
var pnode : TrieNode<KEY, PAYLOAD> = root
|
||||
var length = 0
|
||||
wordLoop@
|
||||
for(key in path) {
|
||||
++length
|
||||
var cnode = pnode.child
|
||||
if(cnode != null) {
|
||||
while (true) {
|
||||
if (cnode!!.key == key) {
|
||||
pnode = cnode
|
||||
continue@wordLoop
|
||||
} else if (cnode.next == null) break
|
||||
else cnode = cnode.next
|
||||
}
|
||||
}
|
||||
pnode = addNode(key, pnode, cnode)
|
||||
result = true
|
||||
}
|
||||
return if(result) {
|
||||
val tail = addNode(null, pnode)
|
||||
tails.add(tail)
|
||||
var node : TrieNode<KEY, PAYLOAD>? = tail
|
||||
while(node != null) {
|
||||
++node.refCount
|
||||
node = node.parent
|
||||
}
|
||||
Pair(true, tail)
|
||||
} else {
|
||||
Pair(false, pnode)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(path : List<KEY>) : Boolean {
|
||||
val deleteNode = { n : TrieNode<KEY, PAYLOAD> ->
|
||||
val parent = n.parent
|
||||
if(parent != null && parent.child == n) {
|
||||
parent.child = n.next
|
||||
}
|
||||
val prev = n.prev
|
||||
if(prev != null) {
|
||||
prev.next = n.next
|
||||
}
|
||||
val next = n.next
|
||||
if(next != null) {
|
||||
next.prev = n.prev
|
||||
}
|
||||
n.parent = null
|
||||
}
|
||||
return when(val res = search(path)) {
|
||||
null -> false
|
||||
else -> {
|
||||
var current = res
|
||||
do {
|
||||
val parent = current!!.parent
|
||||
if(--current.refCount == 0){
|
||||
deleteNode(current)
|
||||
}
|
||||
current = parent
|
||||
} while(current != null)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun search(path : List<KEY>) : TrieNode<KEY, PAYLOAD>? {
|
||||
var result : TrieNode<KEY, PAYLOAD>? = null
|
||||
val visitor = object: TreeNodeVisitor<TrieNode<KEY, PAYLOAD>, Unit> {
|
||||
override fun visitPre(stack: List<TreeNodeVisitor.StackContext<TrieNode<KEY, PAYLOAD>, Unit>>): TreeNodeVisitor.VisitOutcome {
|
||||
return if(stack.size == 1) {
|
||||
TreeNodeVisitor.VisitOutcome.CONTINUE
|
||||
} else {
|
||||
val lastNode = stack.last().node
|
||||
val index = stack.size - 2
|
||||
if (index < path.size) {
|
||||
if(lastNode.key == path[index]) {
|
||||
TreeNodeVisitor.VisitOutcome.CONTINUE
|
||||
} else {
|
||||
TreeNodeVisitor.VisitOutcome.SKIP
|
||||
}
|
||||
} else {
|
||||
if (lastNode.key == null) {
|
||||
result = lastNode
|
||||
}
|
||||
TreeNodeVisitor.VisitOutcome.EARLY_EXIT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val walker = TreeWalker(visitor)
|
||||
walker.walk(root)
|
||||
return result
|
||||
}
|
||||
}
|
35
src/commonMain/kotlin/net.woggioni.klevtree/WordTrie.kt
Normal file
35
src/commonMain/kotlin/net.woggioni.klevtree/WordTrie.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
package net.woggioni.klevtree
|
||||
|
||||
import net.woggioni.klevtree.node.StringNode
|
||||
import net.woggioni.klevtree.node.TrieNode
|
||||
|
||||
open class WordTrie<PAYLOAD> : Trie<StringNode<PAYLOAD>, String, PAYLOAD>() {
|
||||
|
||||
override val root: TrieNode<String, PAYLOAD> = StringNode(null)
|
||||
override val tails = mutableListOf<TrieNode<String, PAYLOAD>>()
|
||||
override var keyChecker: Keychecker<String> = CaseSensitiveKeyChecker()
|
||||
|
||||
private class CaseInsensitiveKeyChecker : Keychecker<String> {
|
||||
override fun check(key1: String?, key2: String?) = key1 == key2
|
||||
}
|
||||
|
||||
private class CaseSensitiveKeyChecker : Keychecker<String> {
|
||||
override fun check(key1: String?, key2: String?) = key1?.lowercase() == key2?.lowercase()
|
||||
}
|
||||
|
||||
var caseSensitive : Boolean = true
|
||||
set(value) {
|
||||
if(value) {
|
||||
keyChecker = CaseSensitiveKeyChecker()
|
||||
} else {
|
||||
keyChecker = CaseInsensitiveKeyChecker()
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
fun add(word : String, delimiter : String) = super.add(word.split(delimiter))
|
||||
|
||||
fun search(word : String, delimiter : String) : TrieNode<String, PAYLOAD>? {
|
||||
return search(word.split(delimiter))
|
||||
}
|
||||
}
|
44
src/commonMain/kotlin/net.woggioni.klevtree/node/nodes.kt
Normal file
44
src/commonMain/kotlin/net.woggioni.klevtree/node/nodes.kt
Normal file
@@ -0,0 +1,44 @@
|
||||
package net.woggioni.klevtree.node
|
||||
|
||||
import net.woggioni.klevtree.tree.TreeNodeVisitor
|
||||
|
||||
|
||||
open class TrieNode<T, PAYLOAD>(val key : T?) : TreeNodeVisitor.TreeNode<TrieNode<T, PAYLOAD>> {
|
||||
var parent : TrieNode<T, PAYLOAD>? = null
|
||||
var child : TrieNode<T, PAYLOAD>? = null
|
||||
var next : TrieNode<T, PAYLOAD>? = null
|
||||
var prev : TrieNode<T, PAYLOAD>? = null
|
||||
var payload : PAYLOAD? = null
|
||||
var refCount = 0
|
||||
|
||||
override fun children(): Iterator<TrieNode<T, PAYLOAD>> {
|
||||
return object : Iterator<TrieNode<T, PAYLOAD>> {
|
||||
var nextChild : TrieNode<T, PAYLOAD>? = child
|
||||
|
||||
override fun hasNext(): Boolean = nextChild != null
|
||||
|
||||
override fun next(): TrieNode<T, PAYLOAD> {
|
||||
val result = nextChild
|
||||
nextChild = nextChild?.next
|
||||
return result!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun linealDescendant() : List<T> {
|
||||
var node : TrieNode<T, PAYLOAD>? = this
|
||||
val chars = mutableListOf<T>()
|
||||
while(node != null) {
|
||||
val key = node.key
|
||||
if(key != null) {
|
||||
chars.add(key)
|
||||
}
|
||||
node = node.parent
|
||||
}
|
||||
return chars.asReversed()
|
||||
}
|
||||
}
|
||||
|
||||
class CharNode<PAYLOAD>(key : Char?) : TrieNode<Char, PAYLOAD>(key)
|
||||
|
||||
class StringNode<PAYLOAD>(key : String?) : TrieNode<String, PAYLOAD>(key)
|
@@ -0,0 +1,64 @@
|
||||
package net.woggioni.klevtree.tree
|
||||
|
||||
/**
|
||||
* This interface must be implemented by the user of [TreeWalker] and its methods will be called by
|
||||
* [TreeWalker.walk]. The methods will receive as an input a list of [StackContext]
|
||||
* instances each one correspond to a node in the tree, each node is preceded in the list
|
||||
* by its parents in the tree. Each instance has a method, [StackContext.context]
|
||||
* to set a custom object that can be used in the [.visitPre] method and the method
|
||||
* [StackContext.context] that can be used in the [.visitPost] method to retrieve
|
||||
* the same instance. This is to provide support for algorithms that require both pre-order and post-order logic.
|
||||
* The last element of the list corresponds to the node currently being traversed.
|
||||
* @param <T> the type of the context object used
|
||||
</T> */
|
||||
interface TreeNodeVisitor<NODE : TreeNodeVisitor.TreeNode<NODE>, T> {
|
||||
interface TreeNode<NODE : TreeNode<NODE>> {
|
||||
fun children(): Iterator<NODE>?
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface exposes the methods that are visible to the user of
|
||||
* [TreeWalker], it allows to
|
||||
* set/get a custom object in the current stack context or to get the current link's Aci
|
||||
* @param <T> the type of the context object used
|
||||
</T> */
|
||||
interface StackContext<NODE : TreeNode<*>?, T> {
|
||||
/**
|
||||
* @return the current user object
|
||||
*/
|
||||
/**
|
||||
* @param ctx the user object to set for this stack level
|
||||
*/
|
||||
var context: T?
|
||||
|
||||
/**
|
||||
* @return the current TreeNode
|
||||
*/
|
||||
val node: NODE
|
||||
}
|
||||
|
||||
enum class VisitOutcome {
|
||||
CONTINUE,
|
||||
SKIP,
|
||||
EARLY_EXIT
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called for each link using
|
||||
* [a Depth-first pre-oder algorithm](https://en.wikipedia.org/wiki/Tree_traversal#Pre-order_(NLR))
|
||||
* @param stack is a list of [StackContext] instances corresponding to the full path from the root to the
|
||||
* current node in the tree
|
||||
* @return a boolean that will be used to decide whether to traverse the subtree rooted in the current link or not
|
||||
*/
|
||||
fun visitPre(stack: List<StackContext<NODE, T>>): VisitOutcome {
|
||||
return VisitOutcome.CONTINUE
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called for each node using
|
||||
* [a Depth-first post-oder algorithm](https://en.wikipedia.org/wiki/Tree_traversal#Post-order_(LRN))
|
||||
* @param stack is a list of [StackContext] instances corresponding to the full path from the root to the
|
||||
* current node in the tree
|
||||
*/
|
||||
fun visitPost(stack: List<StackContext<NODE, T>>) {}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package net.woggioni.klevtree.tree
|
||||
|
||||
|
||||
class TreeWalker<NODE : TreeNodeVisitor.TreeNode<NODE>, T>(
|
||||
private val visitor: TreeNodeVisitor<NODE, T>
|
||||
) {
|
||||
|
||||
private class StackElement<NODE : TreeNodeVisitor.TreeNode<NODE>, T>(override val node: NODE) :
|
||||
TreeNodeVisitor.StackContext<NODE, T> {
|
||||
override var context: T? = null
|
||||
var childrenIterator: Iterator<NODE>? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods does the actual job of traversing the tree calling the methods of the provided
|
||||
* [TreeNodeVisitor] instance
|
||||
* @param root the root node of the tree
|
||||
*/
|
||||
fun walk(root: NODE) {
|
||||
val stack: MutableList<StackElement<NODE, T>> = mutableListOf()
|
||||
val rootStackElement = StackElement<NODE, T>(root)
|
||||
stack.add(rootStackElement)
|
||||
val publicStack: List<TreeNodeVisitor.StackContext<NODE, T>> = stack
|
||||
when (visitor.visitPre(publicStack)) {
|
||||
TreeNodeVisitor.VisitOutcome.CONTINUE -> rootStackElement.childrenIterator = root.children()
|
||||
TreeNodeVisitor.VisitOutcome.SKIP -> rootStackElement.childrenIterator = null
|
||||
TreeNodeVisitor.VisitOutcome.EARLY_EXIT -> return
|
||||
}
|
||||
while (stack.isNotEmpty()) {
|
||||
val lastElement: StackElement<NODE, T> = stack.last()
|
||||
val childrenIterator = lastElement.childrenIterator
|
||||
if (childrenIterator != null && childrenIterator.hasNext()) {
|
||||
val childNode = childrenIterator.next()
|
||||
val childStackElement = StackElement<NODE, T>(childNode)
|
||||
stack.add(childStackElement)
|
||||
when (visitor.visitPre(publicStack)) {
|
||||
TreeNodeVisitor.VisitOutcome.CONTINUE -> childStackElement.childrenIterator = childNode.children()
|
||||
TreeNodeVisitor.VisitOutcome.SKIP -> childStackElement.childrenIterator = null
|
||||
TreeNodeVisitor.VisitOutcome.EARLY_EXIT -> return
|
||||
}
|
||||
} else {
|
||||
visitor.visitPost(publicStack)
|
||||
stack.removeLast()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user