mirror of
https://github.com/mii443/AzooKeyKanaKanjiConverter.git
synced 2025-12-03 02:58:27 +00:00
refactor: split TypoCorrection-related API for separate namaespace
This commit is contained in:
@@ -318,7 +318,7 @@ public final class DicdataStore {
|
||||
segments.append((segments.last ?? "") + String(inputData.input[rightIndex].character.toKatakana()))
|
||||
}
|
||||
// MARK: 誤り訂正の対象を列挙する。非常に重い処理。
|
||||
var stringToInfo = inputData.getRangesWithTypos(fromIndex, rightIndexRange: toIndexLeft ..< toIndexRight)
|
||||
var stringToInfo = TypoCorrection.getRangesWithTypos(inputs: inputData.input, leftIndex: fromIndex, rightIndexRange: toIndexLeft ..< toIndexRight)
|
||||
// MARK: 検索対象を列挙していく。
|
||||
let stringSet: [([Character], [UInt8])] = stringToInfo.keys.map {($0, $0.map(self.character2charId))}
|
||||
let (minCharIDsCount, maxCharIDsCount) = stringSet.lazy.map {$0.1.count}.minAndMax() ?? (0, -1)
|
||||
@@ -425,7 +425,7 @@ public final class DicdataStore {
|
||||
}
|
||||
|
||||
// MARK: 誤り訂正なし
|
||||
let stringToEndIndex = inputData.getRangesWithoutTypos(fromIndex, rightIndexRange: toIndexLeft ..< toIndexRight)
|
||||
let stringToEndIndex = TypoCorrection.getRangesWithoutTypos(inputs: inputData.input, leftIndex: fromIndex, rightIndexRange: toIndexLeft ..< toIndexRight)
|
||||
// MARK: 検索対象を列挙していく。
|
||||
guard let (minString, maxString) = stringToEndIndex.keys.minAndMax(by: {$0.count < $1.count}) else {
|
||||
debug(#function, "minString/maxString is nil", stringToEndIndex)
|
||||
@@ -485,7 +485,7 @@ public final class DicdataStore {
|
||||
let segment = inputData.input[fromIndex...toIndex].reduce(into: "") {$0.append($1.character)}.toKatakana()
|
||||
|
||||
// TODO: 最適化の余地あり
|
||||
let string2penalty = inputData.getRangeWithTypos(fromIndex, toIndex).filter {
|
||||
let string2penalty = TypoCorrection.getRangeWithTypos(inputs: inputData.input, leftIndex: fromIndex, rightIndex: toIndex).filter {
|
||||
needTypoCorrection || $0.value == 0.0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
//
|
||||
// TypoCorrection.swift
|
||||
// Keyboard
|
||||
//
|
||||
// Created by ensan on 2022/12/18.
|
||||
// Copyright © 2022 ensan. All rights reserved.
|
||||
//
|
||||
import SwiftUtils
|
||||
|
||||
// MARK: 誤り訂正用のAPI
|
||||
extension ComposingText {
|
||||
private func shouldBeRemovedForDicdataStore(components: [ConvertTargetElement]) -> Bool {
|
||||
enum TypoCorrection {
|
||||
private static func shouldBeRemovedForDicdataStore(components: [ComposingText.ConvertTargetElement]) -> Bool {
|
||||
// 判定に使うのは最初の1エレメントの最初の文字で十分
|
||||
guard let first = components.first?.string.first?.toKatakana() else {
|
||||
return false
|
||||
@@ -21,7 +14,7 @@ extension ComposingText {
|
||||
/// getRangeWithTyposの複数版にあたる。`result`の計算が一回で済む分、高速になる。
|
||||
/// 例えば`left=4, rightIndexRange=6..<10`の場合、`4...6, 4...7, 4...8, 4...9`の範囲で計算する
|
||||
/// `left <= rightIndexRange.startIndex`が常に成り立つ
|
||||
func getRangesWithTypos(_ left: Int, rightIndexRange: Range<Int>) -> [[Character]: (endIndex: Int, penalty: PValue)] {
|
||||
static func getRangesWithTypos(inputs: [ComposingText.InputElement], leftIndex left: Int, rightIndexRange: Range<Int>) -> [[Character]: (endIndex: Int, penalty: PValue)] {
|
||||
let count = rightIndexRange.endIndex - left
|
||||
debug(#function, left, rightIndexRange, count)
|
||||
let nodes = (0..<count).map {(i: Int) in
|
||||
@@ -30,7 +23,7 @@ extension ComposingText {
|
||||
if count <= j {
|
||||
return []
|
||||
}
|
||||
return Self.getTypo(self.input[left + i ... left + j])
|
||||
return Self.getTypo(inputs[left + i ... left + j])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +32,12 @@ extension ComposingText {
|
||||
var stringToInfo: [([Character], (endIndex: Int, penalty: PValue))] = []
|
||||
|
||||
// 深さ優先で列挙する
|
||||
var stack: [(convertTargetElements: [ConvertTargetElement], lastElement: InputElement, count: Int, penalty: PValue)] = nodes[0].compactMap { typoCandidate in
|
||||
var stack: [(convertTargetElements: [ComposingText.ConvertTargetElement], lastElement: ComposingText.InputElement, count: Int, penalty: PValue)] = nodes[0].compactMap { typoCandidate in
|
||||
guard let firstElement = typoCandidate.inputElements.first else {
|
||||
return nil
|
||||
}
|
||||
if Self.isLeftSideValid(first: firstElement, of: self.input, from: left) {
|
||||
var convertTargetElements = [ConvertTargetElement]()
|
||||
if ComposingText.isLeftSideValid(first: firstElement, of: inputs, from: left) {
|
||||
var convertTargetElements = [ComposingText.ConvertTargetElement]()
|
||||
for element in typoCandidate.inputElements {
|
||||
ComposingText.updateConvertTargetElements(currentElements: &convertTargetElements, newElement: element)
|
||||
}
|
||||
@@ -54,7 +47,7 @@ extension ComposingText {
|
||||
}
|
||||
while let (convertTargetElements, lastElement, count, penalty) = stack.popLast() {
|
||||
if rightIndexRange.contains(count + left - 1) {
|
||||
if let convertTarget = ComposingText.getConvertTargetIfRightSideIsValid(lastElement: lastElement, of: self.input, to: count + left, convertTargetElements: convertTargetElements)?.map({$0.toKatakana()}) {
|
||||
if let convertTarget = ComposingText.getConvertTargetIfRightSideIsValid(lastElement: lastElement, of: inputs, to: count + left, convertTargetElements: convertTargetElements)?.map({$0.toKatakana()}) {
|
||||
stringToInfo.append((convertTarget, (count + left - 1, penalty)))
|
||||
}
|
||||
}
|
||||
@@ -65,7 +58,7 @@ extension ComposingText {
|
||||
// 訂正数上限(3個)
|
||||
if penalty >= maxPenalty {
|
||||
var convertTargetElements = convertTargetElements
|
||||
let correct = [self.input[left + count]].map {InputElement(character: $0.character.toKatakana(), inputStyle: $0.inputStyle)}
|
||||
let correct = [inputs[left + count]].map {ComposingText.InputElement(character: $0.character.toKatakana(), inputStyle: $0.inputStyle)}
|
||||
if count + correct.count > nodes.endIndex {
|
||||
continue
|
||||
}
|
||||
@@ -100,7 +93,7 @@ extension ComposingText {
|
||||
/// closedRangeでもらう
|
||||
/// 例えば`left=4, rightIndexRange=6..<10`の場合、`4...6, 4...7, 4...8, 4...9`の範囲で計算する
|
||||
/// `left <= rightIndexRange.startIndex`が常に成り立つ
|
||||
func getRangesWithoutTypos(_ left: Int, rightIndexRange: Range<Int>) -> [[Character]: Int] {
|
||||
static func getRangesWithoutTypos(inputs: [ComposingText.InputElement], leftIndex left: Int, rightIndexRange: Range<Int>) -> [[Character]: Int] {
|
||||
let count = rightIndexRange.endIndex - left
|
||||
debug(#function, left, rightIndexRange, count)
|
||||
let nodes = (0..<count).map {(i: Int) in
|
||||
@@ -110,7 +103,7 @@ extension ComposingText {
|
||||
return []
|
||||
}
|
||||
// frozen: trueとしているため、typo候補は含まれない
|
||||
return Self.getTypo(self.input[left + i ... left + j], frozen: true)
|
||||
return Self.getTypo(inputs[left + i ... left + j], frozen: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,12 +111,12 @@ extension ComposingText {
|
||||
var stringToInfo: [([Character], Int)] = []
|
||||
|
||||
// 深さ優先で列挙する
|
||||
var stack: [(convertTargetElements: [ConvertTargetElement], lastElement: InputElement, count: Int)] = nodes[0].compactMap { typoCandidate in
|
||||
var stack: [(convertTargetElements: [ComposingText.ConvertTargetElement], lastElement: ComposingText.InputElement, count: Int)] = nodes[0].compactMap { typoCandidate in
|
||||
guard let firstElement = typoCandidate.inputElements.first else {
|
||||
return nil
|
||||
}
|
||||
if Self.isLeftSideValid(first: firstElement, of: self.input, from: left) {
|
||||
var convertTargetElements = [ConvertTargetElement]()
|
||||
if ComposingText.isLeftSideValid(first: firstElement, of: inputs, from: left) {
|
||||
var convertTargetElements = [ComposingText.ConvertTargetElement]()
|
||||
for element in typoCandidate.inputElements {
|
||||
ComposingText.updateConvertTargetElements(currentElements: &convertTargetElements, newElement: element)
|
||||
}
|
||||
@@ -133,7 +126,7 @@ extension ComposingText {
|
||||
}
|
||||
while case .some((var convertTargetElements, let lastElement, let count)) = stack.popLast() {
|
||||
if rightIndexRange.contains(count + left - 1) {
|
||||
if let convertTarget = ComposingText.getConvertTargetIfRightSideIsValid(lastElement: lastElement, of: self.input, to: count + left, convertTargetElements: convertTargetElements)?.map({$0.toKatakana()}) {
|
||||
if let convertTarget = ComposingText.getConvertTargetIfRightSideIsValid(lastElement: lastElement, of: inputs, to: count + left, convertTargetElements: convertTargetElements)?.map({$0.toKatakana()}) {
|
||||
stringToInfo.append((convertTarget, (count + left - 1)))
|
||||
}
|
||||
}
|
||||
@@ -148,7 +141,7 @@ extension ComposingText {
|
||||
for element in $0.inputElements {
|
||||
ComposingText.updateConvertTargetElements(currentElements: &convertTargetElements, newElement: element)
|
||||
}
|
||||
if shouldBeRemovedForDicdataStore(components: convertTargetElements) {
|
||||
if Self.shouldBeRemovedForDicdataStore(components: convertTargetElements) {
|
||||
return nil
|
||||
}
|
||||
return (
|
||||
@@ -162,7 +155,7 @@ extension ComposingText {
|
||||
}
|
||||
|
||||
|
||||
func getRangeWithTypos(_ left: Int, _ right: Int) -> [[Character]: PValue] {
|
||||
static func getRangeWithTypos(inputs: [ComposingText.InputElement], leftIndex left: Int, rightIndex right: Int) -> [[Character]: PValue] {
|
||||
// 各iから始まる候補を列挙する
|
||||
// 例えばinput = [d(あ), r(s), r(i), r(t), r(s), d(は), d(は), d(れ)]の場合
|
||||
// nodes = [[d(あ)], [r(s)], [r(i)], [r(t), [r(t), r(a)]], [r(s)], [d(は), d(ば), d(ぱ)], [d(れ)]]
|
||||
@@ -174,19 +167,19 @@ extension ComposingText {
|
||||
if count <= j {
|
||||
return []
|
||||
}
|
||||
return Self.getTypo(self.input[left + i ... left + j])
|
||||
return Self.getTypo(inputs[left + i ... left + j])
|
||||
}
|
||||
}
|
||||
|
||||
let maxPenalty: PValue = 3.5 * 3
|
||||
|
||||
// 深さ優先で列挙する
|
||||
var stack: [(convertTargetElements: [ConvertTargetElement], lastElement: InputElement, count: Int, penalty: PValue)] = nodes[0].compactMap { typoCandidate in
|
||||
var stack: [(convertTargetElements: [ComposingText.ConvertTargetElement], lastElement: ComposingText.InputElement, count: Int, penalty: PValue)] = nodes[0].compactMap { typoCandidate in
|
||||
guard let firstElement = typoCandidate.inputElements.first else {
|
||||
return nil
|
||||
}
|
||||
if Self.isLeftSideValid(first: firstElement, of: self.input, from: left) {
|
||||
var convertTargetElements = [ConvertTargetElement]()
|
||||
if ComposingText.isLeftSideValid(first: firstElement, of: inputs, from: left) {
|
||||
var convertTargetElements = [ComposingText.ConvertTargetElement]()
|
||||
for element in typoCandidate.inputElements {
|
||||
ComposingText.updateConvertTargetElements(currentElements: &convertTargetElements, newElement: element)
|
||||
}
|
||||
@@ -199,7 +192,7 @@ extension ComposingText {
|
||||
|
||||
while let (convertTargetElements, lastElement, count, penalty) = stack.popLast() {
|
||||
if count + left - 1 == right {
|
||||
if let convertTarget = ComposingText.getConvertTargetIfRightSideIsValid(lastElement: lastElement, of: self.input, to: count + left, convertTargetElements: convertTargetElements)?.map({$0.toKatakana()}) {
|
||||
if let convertTarget = ComposingText.getConvertTargetIfRightSideIsValid(lastElement: lastElement, of: inputs, to: count + left, convertTargetElements: convertTargetElements)?.map({$0.toKatakana()}) {
|
||||
stringToPenalty.append((convertTarget, penalty))
|
||||
}
|
||||
continue
|
||||
@@ -211,7 +204,7 @@ extension ComposingText {
|
||||
// 訂正数上限(3個)
|
||||
if penalty >= maxPenalty {
|
||||
var convertTargetElements = convertTargetElements
|
||||
let correct = [self.input[left + count]].map {InputElement(character: $0.character.toKatakana(), inputStyle: $0.inputStyle)}
|
||||
let correct = [inputs[left + count]].map {ComposingText.InputElement(character: $0.character.toKatakana(), inputStyle: $0.inputStyle)}
|
||||
if count + correct.count > nodes.endIndex {
|
||||
continue
|
||||
}
|
||||
@@ -228,7 +221,7 @@ extension ComposingText {
|
||||
for element in $0.inputElements {
|
||||
ComposingText.updateConvertTargetElements(currentElements: &convertTargetElements, newElement: element)
|
||||
}
|
||||
if shouldBeRemovedForDicdataStore(components: convertTargetElements) {
|
||||
if Self.shouldBeRemovedForDicdataStore(components: convertTargetElements) {
|
||||
return nil
|
||||
}
|
||||
return (
|
||||
@@ -243,7 +236,7 @@ extension ComposingText {
|
||||
return Dictionary(stringToPenalty, uniquingKeysWith: max)
|
||||
}
|
||||
|
||||
private static func getTypo(_ elements: some Collection<InputElement>, frozen: Bool = false) -> [TypoCandidate] {
|
||||
private static func getTypo(_ elements: some Collection<ComposingText.InputElement>, frozen: Bool = false) -> [TypoCandidate] {
|
||||
let key = elements.reduce(into: "") {$0.append($1.character)}.toKatakana()
|
||||
|
||||
if (elements.allSatisfy {$0.inputStyle == .direct}) {
|
||||
@@ -251,19 +244,19 @@ extension ComposingText {
|
||||
if key.count > 1 {
|
||||
return dictionary[key, default: []].map {
|
||||
TypoCandidate(
|
||||
inputElements: $0.value.map {InputElement(character: $0, inputStyle: .direct)},
|
||||
inputElements: $0.value.map {ComposingText.InputElement(character: $0, inputStyle: .direct)},
|
||||
weight: $0.weight
|
||||
)
|
||||
}
|
||||
} else if key.count == 1 {
|
||||
var result = dictionary[key, default: []].map {
|
||||
TypoCandidate(
|
||||
inputElements: $0.value.map {InputElement(character: $0, inputStyle: .direct)},
|
||||
inputElements: $0.value.map {ComposingText.InputElement(character: $0, inputStyle: .direct)},
|
||||
weight: $0.weight
|
||||
)
|
||||
}
|
||||
// そのまま
|
||||
result.append(TypoCandidate(inputElements: key.map {InputElement(character: $0, inputStyle: .direct)}, weight: 0))
|
||||
result.append(TypoCandidate(inputElements: key.map {ComposingText.InputElement(character: $0, inputStyle: .direct)}, weight: 0))
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -272,20 +265,20 @@ extension ComposingText {
|
||||
if key.count > 1 {
|
||||
return dictionary[key, default: []].map {
|
||||
TypoCandidate(
|
||||
inputElements: $0.map {InputElement(character: $0, inputStyle: .roman2kana)},
|
||||
inputElements: $0.map {ComposingText.InputElement(character: $0, inputStyle: .roman2kana)},
|
||||
weight: 3.5
|
||||
)
|
||||
}
|
||||
} else if key.count == 1 {
|
||||
var result = dictionary[key, default: []].map {
|
||||
TypoCandidate(
|
||||
inputElements: $0.map {InputElement(character: $0, inputStyle: .roman2kana)},
|
||||
inputElements: $0.map {ComposingText.InputElement(character: $0, inputStyle: .roman2kana)},
|
||||
weight: 3.5
|
||||
)
|
||||
}
|
||||
// そのまま
|
||||
result.append(
|
||||
TypoCandidate(inputElements: key.map {InputElement(character: $0, inputStyle: .roman2kana)}, weight: 0)
|
||||
TypoCandidate(inputElements: key.map {ComposingText.InputElement(character: $0, inputStyle: .roman2kana)}, weight: 0)
|
||||
)
|
||||
return result
|
||||
}
|
||||
@@ -306,7 +299,7 @@ extension ComposingText {
|
||||
}
|
||||
|
||||
struct TypoCandidate: Equatable {
|
||||
var inputElements: [InputElement]
|
||||
var inputElements: [ComposingText.InputElement]
|
||||
var weight: PValue
|
||||
}
|
||||
|
||||
|
||||
@@ -238,7 +238,6 @@ package struct LOUDS: Sendable {
|
||||
/// - Note: より適切な名前に変更したい
|
||||
@inlinable func byfixNodeIndices(targets: [[UInt8]], depth: Range<Int>) -> [Int] {
|
||||
// 辞書順でソートする
|
||||
// let targets = targets.sorted(by: Self.lexLessThan)
|
||||
var targets = targets
|
||||
targets.sort(by: Self.lexLessThan)
|
||||
// 最終出力となる
|
||||
|
||||
Reference in New Issue
Block a user