Rewrite TermTrie to not use recursion. Optimize put/lookup for speed.

This commit is contained in:
Fedor Isakov 2017-07-16 14:43:46 +02:00
parent e72585aa65
commit 3c96e3d36c
1 changed files with 101 additions and 94 deletions

View File

@ -17,7 +17,6 @@
package jetbrains.mps.logic.reactor.core
import com.github.andrewoma.dexx.collection.ConsList
import com.github.andrewoma.dexx.collection.ConsList.empty
import com.github.andrewoma.dexx.collection.Map as PersMap
import com.github.andrewoma.dexx.collection.Maps
import jetbrains.mps.logic.reactor.util.*
@ -57,7 +56,7 @@ class TermTrie<T>() {
}
private lateinit var root: PathNode<T>
private var root: PathNode<T>
init {
root = PathNode(WILDCARD, 1)
@ -67,13 +66,13 @@ class TermTrie<T>() {
this.root = setRoot
}
fun put(term: Term, value: T): TermTrie<T> = TermTrie(putValue(root, value, IdHashSet(), term, empty()))
fun put(term: Term, value: T): TermTrie<T> = TermTrie(putValue(term, value))
fun remove(term: Term, value: T): TermTrie<T> = TermTrie(removeValue(root, value, IdHashSet(), term, empty()))
fun remove(term: Term, value: T): TermTrie<T> = TermTrie(removeValue(term, value))
fun lookupValues(term: Term): Iterable<T> {
val result = ArrayList<T>()
visitMatching(root, IdHashSet(), term, empty()) { value -> result.add(value) }
visitMatching(term) { value -> result.add(value) }
return result
}
@ -83,89 +82,106 @@ class TermTrie<T>() {
return result
}
private fun putValue(node: PathNode<T>, value: T, seen: IdHashSet<Term>, term: Term, tail: ConsList<Term>): PathNode<T> {
val deref = if (seen.contains(deref(term))) term else deref(term)
val arguments = deref.arguments()
val newTail = arguments.reversed().fold(tail) { list, t -> list.prepend(t) }
private fun putValue(matchTerm: Term, value: T): PathNode<T> {
val seen = IdentityHashMap<Term, Term>()
var nodeStack: ConsList<PathNode<T>> = cons(root)
val termList = arrayListOf(matchTerm)
val nextNode = node.nextOrDefault(symbolOrWildcard(deref)) { sym -> PathNode(sym, arguments.size) }
while (!termList.isEmpty()) {
val node = nodeStack.first() !!
val term = termList.removeAt(termList.size - 1)
//invariant: terms arity is fixed
assert(nextNode.arity == arguments.size)
// dereferece the term only if it hasn't been dereferenced before
val deref = deref(term).let { dt -> seen[dt]?.run { term } ?: dt.apply { seen[dt] = term } }
val arguments = deref.arguments().toList()
for(i in 1..arguments.size) {
termList.add(arguments[arguments.size - i])
}
return if (newTail.isEmpty()) {
node.putNext(nextNode.addValue(value))
val nextNode = node.nextOrDefault(symbolOrWildcard(deref)) { sym -> PathNode(sym, arguments.size) }
nodeStack = nodeStack.prepend(nextNode)
}
val head = nodeStack.first() !!
return nodeStack.tail().fold(head.addValue(value)) { nextNode, node -> node.putNext(nextNode) }
}
private fun removeValue(matchTerm: Term, value: T): PathNode<T> {
val seen = IdentityHashMap<Term, Term>()
var nodeStack: ConsList<PathNode<T>> = cons(root)
val termList = arrayListOf(matchTerm)
while (!termList.isEmpty()) {
val node = nodeStack.first() !!
val term = termList.removeAt(termList.size - 1)
// dereferece the term only if it hasn't been dereferenced before
val deref = deref(term).let { dt -> seen[dt]?.run { term } ?: dt.apply { seen[dt] = term } }
val arguments = deref.arguments().toList()
for(i in 1..arguments.size) {
termList.add(arguments[arguments.size - i])
}
val nextNode = node.next(symbolOrWildcard(deref))
if (nextNode == null) {
// term not found
return root
}
nodeStack = nodeStack.prepend(nextNode)
}
val head = nodeStack.first() !!
val newHead = head.removeValue(value)
return if (head !== newHead) {
nodeStack.tail().fold(newHead) { nextNode, node -> node.putNext(nextNode) }
} else {
node.putNext(putValue(nextNode, value, seen.add(term), newTail.first()!!, newTail.drop(1)))
// value not found
root
}
}
private fun removeValue(node: PathNode<T>, value: T, seen: IdHashSet<Term>, term: Term, tail: ConsList<Term>): PathNode<T> {
val deref = if (seen.contains(deref(term))) term else deref(term)
val arguments = deref.arguments()
val newTail = arguments.reversed().fold(tail) { list, t -> list.prepend(t) }
private fun visitMatching(matchTerm: Term, visitor: (T) -> Unit) {
val seen = IdentityHashMap<Term, Term>()
val visitList = arrayListOf(root.to(cons(matchTerm)))
return node.next(symbolOrWildcard(deref))?.let { nextNode ->
while (!visitList.isEmpty()) {
val (node, termList) = visitList.removeAt(visitList.size - 1)
if (!termList.isEmpty) {
val term = termList.first() !!
val termTail = termList.tail()
//invariant: terms arity is fixed
assert(nextNode.arity == arguments.size)
// dereferece the term only if it hasn't been dereferenced before
val deref = deref(term).let { dt -> seen[dt]?.run { term } ?: dt.apply { seen[dt] = term } }
return if (newTail.isEmpty) {
val newNext = nextNode.removeValue(value)
if (newNext != nextNode) {
if (!newNext.hasValues()) {
node.removeNext(newNext)
val sym = symbolOrWildcard(deref)
if (sym == WILDCARD) {
if (!termTail.isEmpty) {
node.skipAllNext().forEach { n -> visitList.add(n.to(termTail)) }
} else {
node.putNext(newNext)
node.allNext().forEach { visitAll(it, visitor) }
}
} else {
node
}
} else {
val newNext = removeValue(nextNode, value, seen.add(term), newTail.first()!!, newTail.drop(1))
if (newNext !== nextNode) {
if (!newNext.hasNext()) {
node.removeNext(nextNode)
} else {
node.putNext(newNext)
node.next(WILDCARD)?.let { nn ->
nn.values().forEach(visitor)
if (!termTail.isEmpty) {
visitList.add(nn.to(termTail))
}
}
node.next(sym)?.let { nn ->
nn.values().forEach(visitor)
} else {
node
}
}
} ?: node
}
private fun visitMatching(node: PathNode<T>, seen: IdHashSet<Term>, term: Term, tail: ConsList<Term>, visitor: (T) -> Unit) {
val deref = if (seen.contains(deref(term))) term else deref(term)
val sym = symbolOrWildcard(deref)
if (sym == WILDCARD) {
if (!tail.isEmpty) {
node.skipAllNext().forEach { visitMatching(it, seen.add(term), tail.first()!!, tail.drop(1), visitor) }
} else {
node.allNext().forEach { visitAll(it, visitor) }
}
} else {
node.next(sym)?.let { nn ->
nn.values().forEach(visitor)
val newTail = deref.arguments().reversed().fold(tail) { list, t -> list.prepend(t) }
if (!newTail.isEmpty) {
visitMatching(nn, seen.add(term), newTail.first()!!, newTail.drop(1), visitor)
}
}
node.next(WILDCARD)?.let { nn ->
nn.values().forEach(visitor)
if (!tail.isEmpty) {
visitMatching(nn, seen.add(term), tail.first()!!, tail.drop(1), visitor)
var newTail = termTail
val arguments = deref.arguments().toList()
for(i in 1..arguments.size) {
newTail = newTail.prepend(arguments[arguments.size - i])
}
if (!newTail.isEmpty) {
visitList.add(nn.to(newTail))
}
}
}
}
}
@ -193,37 +209,27 @@ class TermTrie<T>() {
}
}
private class PathNode<T>(val symbol: Any, val arity: Int) {
private class PathNode<T>(val symbol: Any,
val arity: Int,
val next: PersMap<Any, PathNode<T>>,
val values: IdHashSet<T>)
{
private lateinit var next: PersMap<Any, PathNode<T>>
constructor(symbol: Any, arity: Int) :
this(symbol, arity, Maps.of(), emptySet())
private lateinit var values: IdHashSet<T>
init {
this.next = Maps.of()
this.values = emptySet()
}
private constructor(symbol: Any, arity: Int, next: PersMap<Any, PathNode<T>>, values: IdHashSet<T>) :
this(symbol, arity)
{
this.next = next
this.values = values
}
private constructor(copyFrom: PathNode<T>) :
this(copyFrom.symbol, copyFrom.arity, copyFrom.next, copyFrom.values)
private constructor(copyFrom: PathNode<T>, setValues: IdHashSet<T>) :
constructor(copyFrom: PathNode<T>, setValues: IdHashSet<T>) :
this(copyFrom.symbol, copyFrom.arity, copyFrom.next, setValues)
private constructor(copyFrom: PathNode<T>, setNext: PersMap<Any, PathNode<T>>) :
constructor(copyFrom: PathNode<T>, setNext: PersMap<Any, PathNode<T>>) :
this(copyFrom.symbol, copyFrom.arity, setNext, copyFrom.values)
fun values(): Iterable<T> = values
fun hasValues(): Boolean = !values.isEmpty
fun hasValue(value: T): Boolean = values.contains(value)
fun next(symbol: Any): PathNode<T>? = next[symbol]
fun hasNext(): Boolean = !next.isEmpty
@ -248,10 +254,11 @@ class TermTrie<T>() {
fun addValue(value: T): PathNode<T> = PathNode(this, values.add(value))
fun removeValue(value: T): PathNode<T> = PathNode(this, values.remove(value))
fun removeValue(value: T): PathNode<T> =
if (values.contains(value)) PathNode(this, values.remove(value)) else this
fun nextOrDefault(symbol: Any,
default: (sym: Any) -> PathNode<T>): PathNode<T>
inline fun nextOrDefault(symbol: Any,
default: (sym: Any) -> PathNode<T>): PathNode<T>
{
return next[symbol] ?: default(symbol)
}