Skip to content

Commit 6a64fc7

Browse files
Merge pull request #14 from word-view/improve-tokenize-kanji-perf
Performance improvements
2 parents d616319 + 0cdcc55 commit 6a64fc7

3 files changed

Lines changed: 55 additions & 45 deletions

File tree

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@
8888
<artifactId>gson</artifactId>
8989
<version>2.11.0</version>
9090
</dependency>
91+
<dependency>
92+
<groupId>org.jetbrains.kotlinx</groupId>
93+
<artifactId>kotlinx-coroutines-core</artifactId>
94+
<version>1.9.0</version>
95+
</dependency>
9196

9297
</dependencies>
9398

src/main/kotlin/cc/wordview/gengolex/languages/Tokenizer.kt

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,60 @@ import cc.wordview.gengolex.word.DerivatableWord
55
import cc.wordview.gengolex.word.Word
66
import com.google.gson.Gson
77
import com.google.gson.reflect.TypeToken
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.IO
10+
import kotlinx.coroutines.async
11+
import kotlinx.coroutines.awaitAll
12+
import kotlinx.coroutines.runBlocking
813
import java.io.File
914
import java.util.HashMap
15+
import kotlin.collections.emptyList
16+
import kotlin.collections.flatten
1017

1118
interface Tokenizer {
1219
val dictionary: ArrayList<DerivatableWord>
1320

21+
companion object {
22+
private val gson = Gson()
23+
private val typeToken = object : TypeToken<List<DerivatableWord>>() {}.type
24+
}
25+
1426
fun tokenize(words: List<String>): ArrayList<Word>
1527

1628
fun initializeDictionary(path: String)
1729

1830
fun initializeDictionary(path: String, lang: String) {
1931
val dictionaryDir = File("$path/$lang")
2032

21-
val files = dictionaryDir.listFiles()?.filter { it.isFile && it.name.endsWith(".json") }
22-
23-
if (files.isNullOrEmpty())
33+
val files = dictionaryDir.listFiles()?.filter { it.isFile && it.name.endsWith(".json") } ?:
2434
throw NoDictionaryException("Unable to find a dictionary for $lang")
2535

26-
for (file in files) {
27-
val content = file.inputStream().readBytes().toString(Charsets.UTF_8)
28-
29-
if (content.isEmpty()) continue
36+
dictionary.clear()
37+
// at the moment of this commit the largest dictionary contains 38 words,
38+
dictionary.ensureCapacity(50)
3039

31-
val typeToken = object : TypeToken<List<DerivatableWord>>() {}.type
40+
runBlocking(Dispatchers.IO) {
41+
val parsedDictionaries = files.map { file ->
42+
async {
43+
val content = file.inputStream().use { it.readBytes().toString(Charsets.UTF_8) }
44+
if (content.isEmpty()) emptyList() else gson.fromJson<List<DerivatableWord>>(content, typeToken)
45+
}
46+
}.awaitAll()
3247

33-
val parsedDictionary = Gson().fromJson<List<DerivatableWord>>(content, typeToken)
34-
35-
dictionary.addAll(parsedDictionary)
48+
dictionary.addAll(parsedDictionaries.flatten())
3649
}
3750
}
3851

3952
fun initializeDictionary(dictionaries: HashMap<String, String>)
4053

4154
fun initializeDictionary(dictionaries: HashMap<String, String>, lang: String) {
42-
val portugueseDictionary = dictionaries[lang]
43-
44-
if (portugueseDictionary.isNullOrEmpty())
45-
throw NoDictionaryException("Unable to find a dictionary for $lang")
46-
47-
val typeToken = object : TypeToken<List<DerivatableWord>>() {}.type
55+
val hashmapDictionary = dictionaries[lang] ?: throw NoDictionaryException("Unable to find a dictionary for $lang")
4856

49-
val parsedDictionary = Gson().fromJson<List<DerivatableWord>>(portugueseDictionary, typeToken)
57+
dictionary.clear()
58+
// at the moment of this commit the largest dictionary contains 38 words,
59+
dictionary.ensureCapacity(50)
5060

61+
val parsedDictionary = gson.fromJson<List<DerivatableWord>>(hashmapDictionary, typeToken)
5162
dictionary.addAll(parsedDictionary)
5263
}
5364
}
Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,48 @@
11
package cc.wordview.gengolex.languages.japanese
22

33
import cc.wordview.gengolex.languages.Tokenizer
4+
import cc.wordview.gengolex.languages.japanese.JapaneseKanjiStrategy.*
45
import cc.wordview.gengolex.word.DerivatableWord
56
import cc.wordview.gengolex.word.Word
67
import java.util.HashMap
7-
import java.util.regex.Pattern
88

99
object JapaneseTokenizer : Tokenizer {
1010
override var dictionary: ArrayList<DerivatableWord> = arrayListOf()
11+
private val wordMap: HashMap<String, DerivatableWord> = HashMap()
1112

1213
@Suppress("MemberVisibilityCanBePrivate")
13-
var kanjiStrategy = JapaneseKanjiStrategy.PREFER_DERIVATION
14-
15-
private val kanjiPattern: Pattern = Pattern.compile("[一-龯]")
14+
var kanjiStrategy = PREFER_DERIVATION
1615

1716
override fun tokenize(words: List<String>): ArrayList<Word> {
18-
val wordsByChars = words.joinToString().replace("", "").replace("", "").split("")
19-
val joinedWordsString = wordsByChars.joinToString("")
20-
21-
val wordsFound = ArrayList<Word>()
22-
var charsToSkipNext = 0
17+
val input = words.joinToString()
18+
.replace("", "")
19+
.replace("", "")
2320

24-
for (i in wordsByChars.indices) {
25-
if (charsToSkipNext > 0) {
26-
charsToSkipNext--
27-
continue
28-
}
21+
val chars = input.toCharArray()
2922

30-
val char = wordsByChars[i]
31-
32-
val currentWordsString =
33-
wordsFound.fold(joinedWordsString) { acc, foundWord -> acc.replace(foundWord.word, "") }
23+
val wordsFound = ArrayList<Word>()
24+
var i = 0
3425

35-
tokenizeKanji(char, currentWordsString)?.let {
26+
while (i < chars.size) {
27+
val char = chars[i].toString()
28+
tokenizeKanji(char, input.substring(i))?.let {
3629
wordsFound.add(it)
37-
charsToSkipNext = it.word.length - 1
38-
}
30+
i += it.word.length // Skip characters based on the word's length
31+
} ?: i++
3932
}
4033

4134
return wordsFound
4235
}
4336

4437
private fun tokenizeKanji(char: String, original: String): Word? {
45-
if (!kanjiPattern.matcher(char).matches()) return null
38+
if (char.isEmpty() || char[0].code !in 0x4E00..0x9FFF) return null
4639

47-
dictionary.firstOrNull { it.word == char }?.let { kanjiWord ->
40+
wordMap[char]?.let { kanjiWord ->
4841
return when (kanjiStrategy) {
49-
// TODO: Properly address this by removing all derivations that is not present in the phrase.
50-
JapaneseKanjiStrategy.PREFER_PARENT -> kanjiWord
51-
JapaneseKanjiStrategy.PREFER_DERIVATION -> {
42+
PREFER_PARENT -> kanjiWord
43+
PREFER_DERIVATION -> {
5244
@Suppress("UNNECESSARY_SAFE_CALL")
53-
kanjiWord.derivations?.firstOrNull { original.contains(it.word) } ?: kanjiWord
45+
kanjiWord.derivations?.find { original.startsWith(it.word) } ?: kanjiWord
5446
}
5547
}
5648
}
@@ -60,9 +52,11 @@ object JapaneseTokenizer : Tokenizer {
6052

6153
override fun initializeDictionary(path: String) {
6254
super.initializeDictionary(path, "kanji")
55+
dictionary.forEach { wordMap[it.word] = it }
6356
}
6457

6558
override fun initializeDictionary(dictionaries: HashMap<String, String>) {
6659
super.initializeDictionary(dictionaries, "kanji")
60+
dictionary.forEach { wordMap[it.word] = it }
6761
}
6862
}

0 commit comments

Comments
 (0)