212 lines
7.9 KiB
Kotlin
212 lines
7.9 KiB
Kotlin
package com.huaban.analysis.jieba
|
|
|
|
import android.content.Context
|
|
import com.huaban.analysis.jieba.viterbi.FinalSeg
|
|
import java.util.*
|
|
|
|
@Suppress("LocalVariableName")
|
|
class JiebaSegmenter(context: Context) {
|
|
|
|
val dictionary by lazy { WordDictionary.getInstance(context) }
|
|
|
|
private val finalSeg by lazy { FinalSeg.instance }
|
|
|
|
enum class SegMode { INDEX, SEARCH }
|
|
|
|
private fun createDAG(sentence: String): Map<Int, MutableList<Int>> {
|
|
val dag = HashMap<Int, MutableList<Int>>()
|
|
val trie = dictionary.trie
|
|
val chars: CharArray = sentence.toCharArray()
|
|
val N = chars.size
|
|
var i = 0
|
|
var j = 0
|
|
while (i < N) {
|
|
val hit = trie.match(chars, i, j - i + 1)
|
|
if (hit.isPrefix || hit.isMatch) {
|
|
if (hit.isMatch) {
|
|
if (!dag.containsKey(i)) {
|
|
val value: MutableList<Int> = ArrayList()
|
|
dag[i] = value
|
|
value.add(j)
|
|
} else dag[i]!!.add(j)
|
|
}
|
|
j += 1
|
|
if (j >= N) {
|
|
i += 1
|
|
j = i
|
|
}
|
|
} else {
|
|
i += 1
|
|
j = i
|
|
}
|
|
}
|
|
i = 0
|
|
while (i < N) {
|
|
if (!dag.containsKey(i)) {
|
|
val value: MutableList<Int> = ArrayList()
|
|
value.add(i)
|
|
dag[i] = value
|
|
}
|
|
++i
|
|
}
|
|
return dag
|
|
}
|
|
|
|
private fun calc(sentence: String, dag: Map<Int, MutableList<Int>>): Map<Int, Pair<Int>?> {
|
|
val N = sentence.length
|
|
val route = HashMap<Int, Pair<Int>?>()
|
|
route[N] = Pair(0, 0.0)
|
|
for (i in N - 1 downTo -1 + 1) {
|
|
var candidate: Pair<Int>? = null
|
|
for (x in dag[i]!!) {
|
|
val freq = dictionary.getFreq(sentence.substring(i, x + 1)) + route[x + 1]!!.freq
|
|
if (null == candidate) {
|
|
candidate = Pair(x, freq)
|
|
} else if (candidate.freq < freq) {
|
|
candidate.freq = freq
|
|
candidate.key = x
|
|
}
|
|
}
|
|
route[i] = candidate
|
|
}
|
|
return route
|
|
}
|
|
|
|
fun process(paragraph: String, mode: SegMode): List<SegToken> {
|
|
val tokens: MutableList<SegToken> = ArrayList()
|
|
var sb = StringBuilder()
|
|
var offset = 0
|
|
for (i in paragraph.indices) {
|
|
val ch = CharacterUtil.regularize(paragraph[i])
|
|
when {
|
|
CharacterUtil.ccFind(ch) -> sb.append(ch)
|
|
else -> {
|
|
if (sb.isNotEmpty()) {
|
|
// process
|
|
when (mode) {
|
|
SegMode.SEARCH -> {
|
|
for (word in sentenceProcess(sb.toString())) {
|
|
tokens.add(SegToken(word, offset, word.length.let { offset += it; offset }))
|
|
}
|
|
}
|
|
else -> {
|
|
for (token in sentenceProcess(sb.toString())) {
|
|
if (token.length > 2) {
|
|
var gram2: String?
|
|
var j = 0
|
|
while (j < token.length - 1) {
|
|
gram2 = token.substring(j, j + 2)
|
|
if (dictionary.containsWord(gram2)) tokens.add(SegToken(gram2, offset + j, offset + j + 2))
|
|
++j
|
|
}
|
|
}
|
|
if (token.length > 3) {
|
|
var gram3: String?
|
|
var j = 0
|
|
while (j < token.length - 2) {
|
|
gram3 = token.substring(j, j + 3)
|
|
if (dictionary.containsWord(gram3)) tokens.add(SegToken(gram3, offset + j, offset + j + 3))
|
|
++j
|
|
}
|
|
}
|
|
tokens.add(SegToken(token, offset, token.length.let { offset += it; offset }))
|
|
}
|
|
}
|
|
}
|
|
sb = StringBuilder()
|
|
offset = i
|
|
}
|
|
if (dictionary.containsWord(paragraph.substring(i, i + 1))) tokens.add(SegToken(paragraph.substring(i, i + 1), offset, ++offset))
|
|
else tokens.add(SegToken(paragraph.substring(i, i + 1), offset, ++offset))
|
|
}
|
|
}
|
|
}
|
|
if (sb.isNotEmpty()) when (mode) {
|
|
SegMode.SEARCH -> {
|
|
sentenceProcess(sb.toString()).mapTo(tokens) { token -> SegToken(token, offset, token.length.let { offset += it; offset }) }
|
|
}
|
|
else -> sentenceProcess(sb.toString()).forEach { token ->
|
|
if (token.length > 2) {
|
|
var gram2: String?
|
|
var j = 0
|
|
while (j < token.length - 1) {
|
|
gram2 = token.substring(j, j + 2)
|
|
if (dictionary.containsWord(gram2)) tokens.add(SegToken(gram2, offset + j, offset + j + 2))
|
|
++j
|
|
}
|
|
}
|
|
if (token.length > 3) {
|
|
var gram3: String?
|
|
var j = 0
|
|
while (j < token.length - 2) {
|
|
gram3 = token.substring(j, j + 3)
|
|
if (dictionary.containsWord(gram3)) tokens.add(SegToken(gram3, offset + j, offset + j + 3))
|
|
++j
|
|
}
|
|
}
|
|
tokens.add(SegToken(token, offset, token.length.let { offset += it; offset }))
|
|
}
|
|
}
|
|
|
|
return tokens
|
|
}
|
|
|
|
private fun sentenceProcess(sentence: String): List<String> {
|
|
val tokens: MutableList<String> = ArrayList()
|
|
val N = sentence.length
|
|
val dag = createDAG(sentence)
|
|
val route = calc(sentence, dag)
|
|
|
|
var x = 0
|
|
var y: Int
|
|
var buf: String
|
|
var sb = StringBuilder()
|
|
while (x < N) {
|
|
y = route[x]!!.key + 1
|
|
val lWord: String = sentence.substring(x, y)
|
|
when {
|
|
y - x == 1 -> sb.append(lWord)
|
|
else -> {
|
|
if (sb.isNotEmpty()) {
|
|
buf = sb.toString()
|
|
sb = StringBuilder()
|
|
when (buf.length) {
|
|
1 -> tokens.add(buf)
|
|
else -> when {
|
|
dictionary.containsWord(buf) -> tokens.add(buf)
|
|
else -> finalSeg.cut(buf, tokens)
|
|
}
|
|
}
|
|
}
|
|
tokens.add(lWord)
|
|
}
|
|
}
|
|
x = y
|
|
}
|
|
buf = sb.toString()
|
|
if (buf.isNotEmpty()) {
|
|
when (buf.length) {
|
|
1 -> tokens.add(buf)
|
|
else -> when {
|
|
dictionary.containsWord(buf) -> tokens.add(buf)
|
|
else -> finalSeg.cut(buf, tokens)
|
|
}
|
|
}
|
|
}
|
|
return tokens
|
|
}
|
|
|
|
fun cutSmall(hans: String, limit: Int): List<String> = when {
|
|
hans.isEmpty() || limit <= 0 -> emptyList()
|
|
else -> process(hans, SegMode.SEARCH)
|
|
.map { token -> token.word }
|
|
.flatMap { word ->
|
|
when {
|
|
word.length > limit -> word.chunked(limit)
|
|
else -> listOf(word)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|