refactor: split TypoCorrection-related API for separate namaespace

This commit is contained in:
Miwa / Ensan
2025-06-28 00:27:30 +09:00
parent 74d4d412c3
commit 854874fdc2
3 changed files with 35 additions and 43 deletions

View File

@@ -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
}

View File

@@ -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: truetypo
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
}

View File

@@ -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)
//