All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kr.bydelta.koala.arirang.dic.kt Maven / Gradle / Ivy

@file:JvmName("Util")
@file:JvmMultifileClass

package kr.bydelta.koala.arirang

import kr.bydelta.koala.POS
import kr.bydelta.koala.proc.CanCompileDict
import kr.bydelta.koala.proc.DicEntry
import org.apache.lucene.analysis.ko.morph.WordEntry
import org.apache.lucene.analysis.ko.utils.DictionaryUtil
import org.apache.lucene.analysis.ko.utils.FileUtil
import org.apache.lucene.analysis.ko.utils.KoreanEnv

/**
 * Arirang 분석기의 사전 인터페이스입니다.
 *
 * @since 1.x
 * @see CanCompileDict
 */
object Dictionary : CanCompileDict {
    @JvmStatic
    private val userItems = mutableListOf()

    @JvmStatic
    private val systemdic = mutableMapOf>()

    /**
     * 사용자 사전에, (표면형,품사)의 여러 순서쌍을 추가합니다.
     *
     * @since 1.x
     * @param dict 추가할 (표면형, 품사)의 순서쌍들 (가변인자). 즉, [Pair]<[String], [POS]>들
     */
    override fun addUserDictionary(vararg dict: DicEntry) {
        userItems.addAll(dict)
        dict.forEach {
            val (word, pos) = it

            val features = //NVZDBIPSCC 순서로 코딩됨
                    if (pos.isNoun()) "100000000X"
                    else if (pos.isPredicate()) "010000000X"
                    else if (pos.isModifier() || pos == POS.IC) "001000000X"
                    else "000000000X"
            DictionaryUtil.addEntry(WordEntry(word, features.toCharArray()))
        }
    }

    /**
     * 원본 사전에 등재된 항목 중에서, 지정된 형태소의 항목만을 가져옵니다. (복합 품사 결합 형태는 제외)
     *
     * @since 1.x
     * @param filter 가져올 품사인지 판단하는 함수.
     * @return (형태소, 품사)의 Iterator.
     */
    override fun getBaseEntries(filter: (POS) -> Boolean): Iterator {
        return systemdic.filterKeys(filter).flatMap { entry ->
            val (key, item) = entry
            item.map { it to key }
        }.listIterator()
    }

    /**
     * 사용자 사전에 등재된 모든 Item을 불러옵니다.
     *
     * @since 1.x
     * @return (형태소, 통합품사)의 Sequence.
     */
    override fun getItems(): Set = userItems.toSet()

    /**
     * 사전에 등재되어 있는지 확인하고, 사전에 없는단어만 반환합니다.
     *
     * @since 1.x
     * @param onlySystemDic 시스템 사전에서만 검색할지 결정합니다.
     * @param word          확인할 (형태소, 품사)들.
     * @return 사전에 없는 단어들, 즉, [Pair]<[String], [POS]>들.
     */
    @JvmOverloads
    override fun getNotExists(onlySystemDic: Boolean, vararg word: DicEntry): Array {
        if (systemdic.isEmpty()) load()

        // Filter out existing morphemes!
        val (_, system) =
                if (onlySystemDic) Pair(emptyList(), word.toList())
                else word.partition { getItems().contains(it) }

        return system.groupBy { it.second }.flatMap {
            val (pos, words) = it

            val dict = systemdic[mapToTag(pos)] ?: emptySet()
            words.filterNot { it.first in dict }
        }.toTypedArray()
    }

    /** 사전 불러오기 **/
    @JvmStatic
    private fun load() {
        synchronized(this) {
            readAuxDict("josa.dic", POS.JX)
            readAuxDict("eomi.dic", POS.EP)
            readAuxDict("prefix.dic", POS.XPN)
            readAuxDict("suffix.dic", POS.XSN)
            readDict()
        }
    }

    /** 보조 사전 불러오기 */
    @JvmStatic
    private fun readAuxDict(dic: String, tag: POS) {
        val path = KoreanEnv.getInstance().getValue(dic)

        try {
            systemdic[tag] = FileUtil.readLines(path, "UTF-8").map { it.trim() }.toSet()
        } catch (_: Throwable) {
        }
    }

    /** 사전 읽기 */
    @JvmStatic
    private fun readDict() {
        try {
            val list = FileUtil.readLines(KoreanEnv.getInstance().getValue("dictionary.dic"), "UTF-8")
            list.addAll(FileUtil.readLines(KoreanEnv.getInstance().getValue("extension.dic"), "UTF-8"))

            list.flatMap { item ->
                val segs = item.split("[,]+".toRegex())
                if (segs.size == 2) {
                    val buffer = mutableListOf()

                    val word = segs[0].trim()

                    if (segs.last()[0] != '0') buffer.add(word to POS.NNG)
                    if (segs.last()[1] != '0') buffer.add(word to POS.VV)
                    if (segs.last()[2] != '0') buffer.add(word to POS.MAG)
                    if (segs.last().take(3) == "000") buffer.add(word to POS.NA)

                    buffer.toList()
                } else emptyList()
            }.groupBy { it.second }.forEach {
                systemdic[it.key] = it.value.map { v -> v.first }.toSet()
            }
        } catch (_: Throwable) {
        }
    }

    /** 아리랑 태그 변환 */
    @JvmStatic
    private fun mapToTag(tag: POS): POS =
            when {
                tag.isPostPosition() -> POS.JX
                tag == POS.XPN || tag == POS.XPV -> POS.XPN
                tag.isSuffix() -> POS.XSN
                tag.isEnding() -> POS.EP
                tag.isNoun() -> POS.NNG
                tag.isPredicate() -> POS.VV
                tag == POS.IC || tag.isModifier() -> POS.MAG
                else -> POS.NA
            }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy