refactor
This commit is contained in:
19
benchmark/build.gradle
Normal file
19
benchmark/build.gradle
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.jvm'
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation group: 'net.woggioni', name: 'jwo', version: getProperty('version.jwo')
|
||||||
|
implementation(rootProject)
|
||||||
|
// runtime(files(rootProject.projectDir.toPath().resolve("src/test/resources")))
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClassName = "net.woggioni.klevtree.benchmark.BenchmarkKt"
|
||||||
|
}
|
@@ -1,28 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("jvm")
|
|
||||||
application
|
|
||||||
}
|
|
||||||
|
|
||||||
group = "woggioni.net"
|
|
||||||
version = "0.1"
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
mavenCentral()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
|
||||||
compile(rootProject)
|
|
||||||
// runtime(files(rootProject.projectDir.toPath().resolve("src/test/resources")))
|
|
||||||
}
|
|
||||||
|
|
||||||
application {
|
|
||||||
mainClassName = "net.woggioni.klevtree.benchmark.BenchmarkKt"
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
rootProject.name = "klevtree-benchmark"
|
|
@@ -1,14 +1,11 @@
|
|||||||
package net.woggioni.klevtree.benchmark
|
package net.woggioni.klevtree.benchmark
|
||||||
|
|
||||||
import net.woggioni.klevtree.ILevTrie
|
|
||||||
import net.woggioni.klevtree.LevTrie
|
import net.woggioni.klevtree.LevTrie
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import net.woggioni.jwo.Chronometer
|
import net.woggioni.jwo.Chronometer
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
val reader = BufferedReader(
|
val reader = BufferedReader(
|
||||||
InputStreamReader(Chronometer::class.java.getResourceAsStream("/cracklib-small"))
|
InputStreamReader(Chronometer::class.java.getResourceAsStream("/cracklib-small"))
|
||||||
)
|
)
|
||||||
@@ -21,7 +18,7 @@ fun main(args: Array<String>) {
|
|||||||
} finally {
|
} finally {
|
||||||
reader.close()
|
reader.close()
|
||||||
}
|
}
|
||||||
tree.algorithm = ILevTrie.Algorithm.DAMERAU_LEVENSHTEIN
|
tree.algorithm = LevTrie.Algorithm.DAMERAU_LEVENSHTEIN
|
||||||
tree.caseSensitive = false
|
tree.caseSensitive = false
|
||||||
val chr = Chronometer()
|
val chr = Chronometer()
|
||||||
val keys = arrayOf("camel", "coriolis", "mattel", "cruzer", "cpoper", "roublesoot")
|
val keys = arrayOf("camel", "coriolis", "mattel", "cruzer", "cpoper", "roublesoot")
|
46
build.gradle
Normal file
46
build.gradle
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
plugins {
|
||||||
|
id 'maven-publish'
|
||||||
|
id 'net.woggioni.gradle.multi-release-jar'
|
||||||
|
id 'org.jetbrains.kotlin.jvm'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
group = "woggioni.net"
|
||||||
|
version = getProperty('version.klevtree')
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url = woggioniMavenRepositoryUrl
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ext {
|
||||||
|
setProperty('jpms.module.name', 'net.woggioni.klevtree')
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation group: 'net.woggioni', name: 'jwo', version: getProperty('version.jwo')
|
||||||
|
|
||||||
|
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: getProperty('version.junitJupiter')
|
||||||
|
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: getProperty('version.junitJupiter')
|
||||||
|
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: getProperty('version.junitJupiter')
|
||||||
|
testRuntimeOnly group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: getProperty('version.log4j2')
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
compileKotlin {
|
||||||
|
kotlinOptions.with {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
@@ -1,29 +0,0 @@
|
|||||||
|
|
||||||
plugins {
|
|
||||||
kotlin("jvm") version "1.3.41"
|
|
||||||
}
|
|
||||||
|
|
||||||
group = "woggioni.net"
|
|
||||||
version = "0.1"
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
mavenCentral()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
|
||||||
compile("net.woggioni:jwo:1.0")
|
|
||||||
testImplementation ("junit:junit:4.12")
|
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.3.41")
|
|
||||||
testImplementation("org.apache.logging.log4j:log4j-core:2.12.1")
|
|
||||||
testImplementation("org.apache.logging.log4j:log4j-slf4j-impl:2.12.1")
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@@ -1 +1,11 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
|
||||||
|
woggioniMavenRepositoryUrl=https://woggioni.net/mvn/
|
||||||
|
|
||||||
|
version.multiReleaseJarPlugin=0.1
|
||||||
|
version.klevtree=1.0
|
||||||
|
version.jwo=1.0
|
||||||
|
version.junitJupiter=5.7.2
|
||||||
|
version.slf4j=1.7.32
|
||||||
|
version.kotlin=1.5.31
|
||||||
|
version.log4j2=2.14.1
|
||||||
|
16
settings.gradle
Normal file
16
settings.gradle
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url = woggioniMavenRepositoryUrl
|
||||||
|
}
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.jvm' version getProperty('version.kotlin')
|
||||||
|
id 'net.woggioni.gradle.multi-release-jar' version getProperty('version.multiReleaseJarPlugin')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = 'klevtree'
|
||||||
|
include 'benchmark'
|
@@ -1,2 +0,0 @@
|
|||||||
rootProject.name = "klevtree"
|
|
||||||
include("benchmark")
|
|
4
src/main/java9/module-info.java
Normal file
4
src/main/java9/module-info.java
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module net.woggioni.klevtree {
|
||||||
|
requires net.woggioni.jwo;
|
||||||
|
exports net.woggioni.klevtree;
|
||||||
|
}
|
@@ -3,17 +3,28 @@ package net.woggioni.klevtree
|
|||||||
import net.woggioni.klevtree.node.CharNode
|
import net.woggioni.klevtree.node.CharNode
|
||||||
import net.woggioni.klevtree.node.TrieNode
|
import net.woggioni.klevtree.node.TrieNode
|
||||||
|
|
||||||
interface ICharTrie<PAYLOAD> : Trie<CharNode<PAYLOAD>, Char, PAYLOAD> {
|
open class CharTrie<PAYLOAD> : Trie<CharNode<PAYLOAD>, Char, PAYLOAD>() {
|
||||||
|
private class CaseInsensitiveKeyChecker : Keychecker<Char> {
|
||||||
class CaseInsensitiveKeyChecker : Trie.Keychecker<Char> {
|
|
||||||
override fun check(key1: Char?, key2: Char?) = key1 == key2
|
override fun check(key1: Char?, key2: Char?) = key1 == key2
|
||||||
}
|
}
|
||||||
|
|
||||||
class CaseSensitiveKeyChecker : Trie.Keychecker<Char> {
|
private class CaseSensitiveKeyChecker : Keychecker<Char> {
|
||||||
override fun check(key1: Char?, key2: Char?) = key1?.toLowerCase() == key2?.toLowerCase()
|
override fun check(key1: Char?, key2: Char?) = key1?.lowercaseChar() == key2?.lowercaseChar()
|
||||||
}
|
}
|
||||||
|
|
||||||
var caseSensitive : Boolean
|
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 add(word : String) = super.add(word.asIterable())
|
||||||
|
|
||||||
@@ -21,19 +32,3 @@ interface ICharTrie<PAYLOAD> : Trie<CharNode<PAYLOAD>, Char, PAYLOAD> {
|
|||||||
|
|
||||||
fun remove(word : String) = remove(word.asIterable().toList())
|
fun remove(word : String) = remove(word.asIterable().toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
class CharTrie<PAYLOAD> : ICharTrie<PAYLOAD> {
|
|
||||||
|
|
||||||
override val root: TrieNode<Char, PAYLOAD> = CharNode(null)
|
|
||||||
override val tails = mutableListOf<TrieNode<Char, PAYLOAD>>()
|
|
||||||
override var keyChecker: Trie.Keychecker<Char> = ICharTrie.CaseSensitiveKeyChecker()
|
|
||||||
override var caseSensitive : Boolean = true
|
|
||||||
set(value) {
|
|
||||||
if(value) {
|
|
||||||
keyChecker = ICharTrie.CaseSensitiveKeyChecker()
|
|
||||||
} else {
|
|
||||||
keyChecker = ICharTrie.CaseInsensitiveKeyChecker()
|
|
||||||
}
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
}
|
|
65
src/main/kotlin/net/woggioni/klevtree/DistanceCalculator.kt
Normal file
65
src/main/kotlin/net/woggioni/klevtree/DistanceCalculator.kt
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package net.woggioni.klevtree
|
||||||
|
|
||||||
|
import net.woggioni.jwo.TreeNodeVisitor
|
||||||
|
|
||||||
|
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] = Math.min(Math.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] = Math.min(Math.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] = Math.min(crow[i], pprow[i - 2] + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if(worstCase >= 0 && worstCase <= prow.minOrNull()!!) {
|
||||||
|
TreeNodeVisitor.VisitOutcome.SKIP
|
||||||
|
} else {
|
||||||
|
TreeNodeVisitor.VisitOutcome.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -1,75 +1,17 @@
|
|||||||
package net.woggioni.klevtree
|
package net.woggioni.klevtree
|
||||||
|
|
||||||
import net.woggioni.jwo.tree.StackContext
|
import net.woggioni.jwo.TreeNodeVisitor
|
||||||
import net.woggioni.jwo.tree.TreeNodeVisitor
|
import net.woggioni.jwo.TreeWalker
|
||||||
import net.woggioni.jwo.tree.TreeWalker
|
|
||||||
import net.woggioni.klevtree.node.CharNode
|
import net.woggioni.klevtree.node.CharNode
|
||||||
import net.woggioni.klevtree.node.TrieNode
|
import net.woggioni.klevtree.node.TrieNode
|
||||||
|
|
||||||
typealias LevNode = TrieNode<Char, IntArray>
|
internal typealias LevNode = TrieNode<Char, IntArray>
|
||||||
|
|
||||||
interface ILevTrie : ICharTrie<IntArray> {
|
class LevTrie : CharTrie<IntArray>() {
|
||||||
|
|
||||||
interface DistanceCalculator {
|
override val root: TrieNode<Char, IntArray> = CharNode(null)
|
||||||
fun compute(keyChecker : Trie.Keychecker<Char>,
|
|
||||||
stack: List<StackContext<LevNode, Unit>>,
|
|
||||||
wordkey: String,
|
|
||||||
worstCase : Int) : TreeNodeVisitor.VisitOutcome
|
|
||||||
}
|
|
||||||
|
|
||||||
object LevenshteinDistanceCalculator : DistanceCalculator {
|
override val tails = mutableListOf<TrieNode<Char, IntArray>>()
|
||||||
override fun compute(keyChecker : Trie.Keychecker<Char>,
|
|
||||||
stack: List<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] = Math.min(Math.min(currentRow[i - 1], previousRow[i -1]), previousRow[i]) + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return if(worstCase >= 0 && worstCase <= currentRow.min()!!) {
|
|
||||||
TreeNodeVisitor.VisitOutcome.SKIP
|
|
||||||
} else {
|
|
||||||
TreeNodeVisitor.VisitOutcome.CONTINUE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object DamerauLevenshteinDistanceCalculator : DistanceCalculator {
|
|
||||||
override fun compute(keyChecker : Trie.Keychecker<Char>,
|
|
||||||
stack: List<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] = Math.min(Math.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] = Math.min(crow[i], pprow[i - 2] + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return if(worstCase >= 0 && worstCase <= prow.min()!!) {
|
|
||||||
TreeNodeVisitor.VisitOutcome.SKIP
|
|
||||||
} else {
|
|
||||||
TreeNodeVisitor.VisitOutcome.CONTINUE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class Algorithm {
|
enum class Algorithm {
|
||||||
/**
|
/**
|
||||||
@@ -82,20 +24,19 @@ interface ILevTrie : ICharTrie<IntArray> {
|
|||||||
DAMERAU_LEVENSHTEIN
|
DAMERAU_LEVENSHTEIN
|
||||||
}
|
}
|
||||||
|
|
||||||
var distanceCalculator : DistanceCalculator
|
private var distanceCalculator : DistanceCalculator = DistanceCalculator.LevenshteinDistanceCalculator
|
||||||
|
|
||||||
var algorithm : Algorithm
|
var algorithm : Algorithm
|
||||||
get() {
|
get() {
|
||||||
return when(distanceCalculator) {
|
return when(distanceCalculator) {
|
||||||
LevenshteinDistanceCalculator -> Algorithm.LEVENSHTEIN
|
DistanceCalculator.LevenshteinDistanceCalculator -> Algorithm.LEVENSHTEIN
|
||||||
DamerauLevenshteinDistanceCalculator -> Algorithm.DAMERAU_LEVENSHTEIN
|
DistanceCalculator.DamerauLevenshteinDistanceCalculator -> Algorithm.DAMERAU_LEVENSHTEIN
|
||||||
else -> Algorithm.LEVENSHTEIN
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
when(value) {
|
distanceCalculator = when(value) {
|
||||||
Algorithm.LEVENSHTEIN -> distanceCalculator = LevenshteinDistanceCalculator
|
Algorithm.LEVENSHTEIN -> DistanceCalculator.LevenshteinDistanceCalculator
|
||||||
Algorithm.DAMERAU_LEVENSHTEIN -> distanceCalculator = DamerauLevenshteinDistanceCalculator
|
Algorithm.DAMERAU_LEVENSHTEIN -> DistanceCalculator.DamerauLevenshteinDistanceCalculator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +44,7 @@ interface ILevTrie : ICharTrie<IntArray> {
|
|||||||
val result = sortedSetOf<Pair<String, Int>>(compareBy({ it.second }, { it.first }))
|
val result = sortedSetOf<Pair<String, Int>>(compareBy({ it.second }, { it.first }))
|
||||||
val requiredSize = word.length + 1
|
val requiredSize = word.length + 1
|
||||||
val visitor = object: TreeNodeVisitor<LevNode, Unit> {
|
val visitor = object: TreeNodeVisitor<LevNode, Unit> {
|
||||||
override fun visitPre(stack: List<StackContext<LevNode, Unit>>): TreeNodeVisitor.VisitOutcome {
|
override fun visitPre(stack: List<TreeNodeVisitor.StackContext<LevNode, Unit>>): TreeNodeVisitor.VisitOutcome {
|
||||||
val currentStackElement = stack.last()
|
val currentStackElement = stack.last()
|
||||||
val currentNode = currentStackElement.node
|
val currentNode = currentStackElement.node
|
||||||
if(currentNode.payload == null ||
|
if(currentNode.payload == null ||
|
||||||
@@ -136,29 +77,8 @@ interface ILevTrie : ICharTrie<IntArray> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val walker = TreeWalker<LevNode, Unit>(visitor)
|
val walker = TreeWalker(visitor)
|
||||||
walker.walk(root)
|
walker.walk(root)
|
||||||
return result.toList()
|
return result.toList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LevTrie : ILevTrie {
|
|
||||||
|
|
||||||
override val root: TrieNode<Char, IntArray> = CharNode(null)
|
|
||||||
|
|
||||||
override val tails = mutableListOf<TrieNode<Char, IntArray>>()
|
|
||||||
|
|
||||||
override var keyChecker: Trie.Keychecker<Char> = ICharTrie.CaseSensitiveKeyChecker()
|
|
||||||
|
|
||||||
override var caseSensitive : Boolean = true
|
|
||||||
set(value) {
|
|
||||||
if(value) {
|
|
||||||
keyChecker = ICharTrie.CaseSensitiveKeyChecker()
|
|
||||||
} else {
|
|
||||||
keyChecker = ICharTrie.CaseInsensitiveKeyChecker()
|
|
||||||
}
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override var distanceCalculator : ILevTrie.DistanceCalculator = ILevTrie.LevenshteinDistanceCalculator
|
|
||||||
}
|
|
@@ -1,21 +1,20 @@
|
|||||||
package net.woggioni.klevtree
|
package net.woggioni.klevtree
|
||||||
|
|
||||||
import net.woggioni.jwo.tree.StackContext
|
import net.woggioni.jwo.TreeNodeVisitor
|
||||||
import net.woggioni.jwo.tree.TreeNodeVisitor
|
import net.woggioni.jwo.TreeWalker
|
||||||
import net.woggioni.jwo.tree.TreeWalker
|
|
||||||
import net.woggioni.klevtree.node.TrieNode
|
import net.woggioni.klevtree.node.TrieNode
|
||||||
|
|
||||||
interface Trie<T : TrieNode<KEY, PAYLOAD>, KEY, PAYLOAD> {
|
abstract class Trie<T : TrieNode<KEY, PAYLOAD>, KEY, PAYLOAD> {
|
||||||
|
|
||||||
interface Keychecker<KEY> {
|
interface Keychecker<KEY> {
|
||||||
fun check(key1 : KEY?, key2 : KEY?) : Boolean
|
fun check(key1 : KEY?, key2 : KEY?) : Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyChecker : Keychecker<KEY>
|
protected abstract var keyChecker : Keychecker<KEY>
|
||||||
|
|
||||||
val root : TrieNode<KEY, PAYLOAD>
|
protected abstract val root : TrieNode<KEY, PAYLOAD>
|
||||||
|
|
||||||
val tails : MutableList<TrieNode<KEY, PAYLOAD>>
|
protected abstract val tails : MutableList<TrieNode<KEY, PAYLOAD>>
|
||||||
val words : Iterable<List<KEY>>
|
val words : Iterable<List<KEY>>
|
||||||
get() {
|
get() {
|
||||||
val res = object : Iterator<List<KEY>> {
|
val res = object : Iterator<List<KEY>> {
|
||||||
@@ -126,7 +125,7 @@ interface Trie<T : TrieNode<KEY, PAYLOAD>, KEY, PAYLOAD> {
|
|||||||
fun search(path : List<KEY>) : TrieNode<KEY, PAYLOAD>? {
|
fun search(path : List<KEY>) : TrieNode<KEY, PAYLOAD>? {
|
||||||
var result : TrieNode<KEY, PAYLOAD>? = null
|
var result : TrieNode<KEY, PAYLOAD>? = null
|
||||||
val visitor = object: TreeNodeVisitor<TrieNode<KEY, PAYLOAD>, Unit> {
|
val visitor = object: TreeNodeVisitor<TrieNode<KEY, PAYLOAD>, Unit> {
|
||||||
override fun visitPre(stack: List<StackContext<TrieNode<KEY, PAYLOAD>, Unit>>): TreeNodeVisitor.VisitOutcome {
|
override fun visitPre(stack: List<TreeNodeVisitor.StackContext<TrieNode<KEY, PAYLOAD>, Unit>>): TreeNodeVisitor.VisitOutcome {
|
||||||
return if(stack.size == 1) {
|
return if(stack.size == 1) {
|
||||||
TreeNodeVisitor.VisitOutcome.CONTINUE
|
TreeNodeVisitor.VisitOutcome.CONTINUE
|
||||||
} else {
|
} else {
|
||||||
@@ -147,7 +146,7 @@ interface Trie<T : TrieNode<KEY, PAYLOAD>, KEY, PAYLOAD> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val walker = TreeWalker<TrieNode<KEY, PAYLOAD>, Unit>(visitor)
|
val walker = TreeWalker(visitor)
|
||||||
walker.walk(root)
|
walker.walk(root)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@@ -3,17 +3,29 @@ package net.woggioni.klevtree
|
|||||||
import net.woggioni.klevtree.node.StringNode
|
import net.woggioni.klevtree.node.StringNode
|
||||||
import net.woggioni.klevtree.node.TrieNode
|
import net.woggioni.klevtree.node.TrieNode
|
||||||
|
|
||||||
interface IWordTrie<PAYLOAD> : Trie<StringNode<PAYLOAD>, String, PAYLOAD> {
|
open class WordTrie<PAYLOAD> : Trie<StringNode<PAYLOAD>, String, PAYLOAD>() {
|
||||||
|
|
||||||
class CaseInsensitiveKeyChecker : Trie.Keychecker<String> {
|
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
|
override fun check(key1: String?, key2: String?) = key1 == key2
|
||||||
}
|
}
|
||||||
|
|
||||||
class CaseSensitiveKeyChecker : Trie.Keychecker<String> {
|
private class CaseSensitiveKeyChecker : Keychecker<String> {
|
||||||
override fun check(key1: String?, key2: String?) = key1?.toLowerCase() == key2?.toLowerCase()
|
override fun check(key1: String?, key2: String?) = key1?.lowercase() == key2?.lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
var caseSensitive : Boolean
|
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 add(word : String, delimiter : String) = super.add(word.split(delimiter))
|
||||||
|
|
||||||
@@ -21,20 +33,3 @@ interface IWordTrie<PAYLOAD> : Trie<StringNode<PAYLOAD>, String, PAYLOAD> {
|
|||||||
return search(word.split(delimiter))
|
return search(word.split(delimiter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WordTrie<PAYLOAD> : IWordTrie<PAYLOAD> {
|
|
||||||
|
|
||||||
override val root: TrieNode<String, PAYLOAD> = StringNode(null)
|
|
||||||
override val tails = mutableListOf<TrieNode<String, PAYLOAD>>()
|
|
||||||
override var keyChecker: Trie.Keychecker<String> = IWordTrie.CaseSensitiveKeyChecker()
|
|
||||||
|
|
||||||
override var caseSensitive : Boolean = true
|
|
||||||
set(value) {
|
|
||||||
if(value) {
|
|
||||||
keyChecker = IWordTrie.CaseSensitiveKeyChecker()
|
|
||||||
} else {
|
|
||||||
keyChecker = IWordTrie.CaseInsensitiveKeyChecker()
|
|
||||||
}
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,8 +1,9 @@
|
|||||||
package net.woggioni.klevtree.node
|
package net.woggioni.klevtree.node
|
||||||
|
|
||||||
import net.woggioni.jwo.tree.TreeNode
|
import net.woggioni.jwo.TreeNodeVisitor
|
||||||
|
|
||||||
open class TrieNode<T, PAYLOAD>(val key : T?) : TreeNode<TrieNode<T, PAYLOAD>> {
|
|
||||||
|
open class TrieNode<T, PAYLOAD>(val key : T?) : TreeNodeVisitor.TreeNode<TrieNode<T, PAYLOAD>> {
|
||||||
var parent : TrieNode<T, PAYLOAD>? = null
|
var parent : TrieNode<T, PAYLOAD>? = null
|
||||||
var child : TrieNode<T, PAYLOAD>? = null
|
var child : TrieNode<T, PAYLOAD>? = null
|
||||||
var next : TrieNode<T, PAYLOAD>? = null
|
var next : TrieNode<T, PAYLOAD>? = null
|
||||||
@@ -36,21 +37,6 @@ open class TrieNode<T, PAYLOAD>(val key : T?) : TreeNode<TrieNode<T, PAYLOAD>> {
|
|||||||
}
|
}
|
||||||
return chars.asReversed()
|
return chars.asReversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun root(node: TrieNode<Char>) : String {
|
|
||||||
// var node : TrieNode<Char>? = node
|
|
||||||
// val chars = mutableListOf<Char>()
|
|
||||||
// while(node != null) {
|
|
||||||
// val key = node.key
|
|
||||||
// if(key != Character.MIN_VALUE) {
|
|
||||||
// chars.add(node.key)
|
|
||||||
// }
|
|
||||||
// node = node.parent
|
|
||||||
// }
|
|
||||||
// val sb = StringBuilder()
|
|
||||||
// for(c in chars.asReversed()) sb.append(c)
|
|
||||||
// return sb.toString()
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CharNode<PAYLOAD>(key : Char?) : TrieNode<Char, PAYLOAD>(key)
|
class CharNode<PAYLOAD>(key : Char?) : TrieNode<Char, PAYLOAD>(key)
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
package net.woggioni.klevtree
|
package net.woggioni.klevtree
|
||||||
|
|
||||||
import org.junit.Assert
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
class LevtreeTest {
|
class LevtreeTest {
|
||||||
|
|
||||||
@@ -10,27 +9,27 @@ class LevtreeTest {
|
|||||||
fun trivialTest() {
|
fun trivialTest() {
|
||||||
val tree = LevTrie()
|
val tree = LevTrie()
|
||||||
tree.caseSensitive = false
|
tree.caseSensitive = false
|
||||||
tree.algorithm = ILevTrie.Algorithm.DAMERAU_LEVENSHTEIN
|
tree.algorithm = LevTrie.Algorithm.DAMERAU_LEVENSHTEIN
|
||||||
val word = "dailies"
|
val word = "dailies"
|
||||||
run {
|
run {
|
||||||
val pair = tree.add(word)
|
val pair = tree.add(word)
|
||||||
Assert.assertTrue(pair.first)
|
Assertions.assertTrue(pair.first)
|
||||||
val node = tree.search(word)
|
val node = tree.search(word)
|
||||||
Assert.assertNotNull(node)
|
Assertions.assertNotNull(node)
|
||||||
Assert.assertEquals(
|
Assertions.assertEquals(
|
||||||
word,
|
word,
|
||||||
node!!.linealDescendant().fold(StringBuilder(), StringBuilder::append).toString()
|
node!!.linealDescendant().fold(StringBuilder(), StringBuilder::append).toString()
|
||||||
)
|
)
|
||||||
val result = tree.fuzzySearch(word, 5)
|
val result = tree.fuzzySearch(word, 5)
|
||||||
Assert.assertEquals(1, result.size)
|
Assertions.assertEquals(1, result.size)
|
||||||
Assert.assertEquals(word to 0, result[0])
|
Assertions.assertEquals(word to 0, result[0])
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
tree.remove(word)
|
tree.remove(word)
|
||||||
val node = tree.search(word)
|
val node = tree.search(word)
|
||||||
Assert.assertNull(node)
|
Assertions.assertNull(node)
|
||||||
val result = tree.fuzzySearch(word, 5)
|
val result = tree.fuzzySearch(word, 5)
|
||||||
Assert.assertEquals(0, result.size)
|
Assertions.assertEquals(0, result.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,25 +59,25 @@ class LevtreeTest {
|
|||||||
fun levenshteinDistanceTest() {
|
fun levenshteinDistanceTest() {
|
||||||
val tree = initLevTrie()
|
val tree = initLevTrie()
|
||||||
tree.caseSensitive = false
|
tree.caseSensitive = false
|
||||||
tree.algorithm = ILevTrie.Algorithm.LEVENSHTEIN
|
tree.algorithm = LevTrie.Algorithm.LEVENSHTEIN
|
||||||
run {
|
run {
|
||||||
val word = "fired"
|
val word = "fired"
|
||||||
val result = tree.fuzzySearch(word, 4)
|
val result = tree.fuzzySearch(word, 4)
|
||||||
Assert.assertEquals(4, result.size)
|
Assertions.assertEquals(4, result.size)
|
||||||
Assert.assertEquals("tired" to 1, result[0])
|
Assertions.assertEquals("tired" to 1, result[0])
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
val word = "tierd"
|
val word = "tierd"
|
||||||
val result = tree.fuzzySearch(word, 4)
|
val result = tree.fuzzySearch(word, 4)
|
||||||
Assert.assertEquals(4, result.size)
|
Assertions.assertEquals(4, result.size)
|
||||||
Assert.assertEquals("tired" to 2, result[0])
|
Assertions.assertEquals("tired" to 2, result[0])
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
val word = "tierd"
|
val word = "tierd"
|
||||||
tree.remove("tired")
|
tree.remove("tired")
|
||||||
val result = tree.fuzzySearch(word, 4)
|
val result = tree.fuzzySearch(word, 4)
|
||||||
Assert.assertEquals(4, result.size)
|
Assertions.assertEquals(4, result.size)
|
||||||
Assert.assertEquals("trail" to 4, result[0])
|
Assertions.assertEquals("trail" to 4, result[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,26 +85,26 @@ class LevtreeTest {
|
|||||||
fun damerauLevenshteinDistanceTest() {
|
fun damerauLevenshteinDistanceTest() {
|
||||||
val tree = initLevTrie()
|
val tree = initLevTrie()
|
||||||
tree.caseSensitive = false
|
tree.caseSensitive = false
|
||||||
tree.algorithm = ILevTrie.Algorithm.DAMERAU_LEVENSHTEIN
|
tree.algorithm = LevTrie.Algorithm.DAMERAU_LEVENSHTEIN
|
||||||
run {
|
run {
|
||||||
val word = "fired"
|
val word = "fired"
|
||||||
val result = tree.fuzzySearch(word, 4)
|
val result = tree.fuzzySearch(word, 4)
|
||||||
Assert.assertEquals(4, result.size)
|
Assertions.assertEquals(4, result.size)
|
||||||
Assert.assertEquals("tired" to 1, result[0])
|
Assertions.assertEquals("tired" to 1, result[0])
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
val word = "capitvate"
|
val word = "capitvate"
|
||||||
val result = tree.fuzzySearch(word, 4)
|
val result = tree.fuzzySearch(word, 4)
|
||||||
Assert.assertEquals(4, result.size)
|
Assertions.assertEquals(4, result.size)
|
||||||
Assert.assertEquals("captivate" to 1, result[0])
|
Assertions.assertEquals("captivate" to 1, result[0])
|
||||||
Assert.assertEquals("captivity" to 3, result[1])
|
Assertions.assertEquals("captivity" to 3, result[1])
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
tree.remove("captivate")
|
tree.remove("captivate")
|
||||||
val word = "capitvate"
|
val word = "capitvate"
|
||||||
val result = tree.fuzzySearch(word, 4)
|
val result = tree.fuzzySearch(word, 4)
|
||||||
Assert.assertEquals(4, result.size)
|
Assertions.assertEquals(4, result.size)
|
||||||
Assert.assertEquals("captivity" to 3, result[0])
|
Assertions.assertEquals("captivity" to 3, result[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user