WIP: switching to new architecture

This commit is contained in:
Miwa / Ensan
2024-03-17 02:24:55 +09:00
parent 85c3bd3d02
commit 54bce5a5e4
9 changed files with 707 additions and 769 deletions

View File

@ -9,50 +9,53 @@ import XCTest
import Foundation
@testable import KanaKanjiConverterModule
struct ConvertGraph: InputGraphProtocol {
struct Node: InputGraphNodeProtocol {
struct ConvertGraph {
struct Node {
var latticeNodes: [LatticeNode]
var displayedTextRange: InputGraphStructure.Range
var inputElementsRange: InputGraphStructure.Range
var correction: CorrectGraph.Correction = .none
var inputElementsRange: InputGraphRange
var correction: CorrectGraph2.Correction = .none
}
var nodes: [Node] = [
// root node
Node(latticeNodes: [], displayedTextRange: .endIndex(0), inputElementsRange: .endIndex(0))
Node(latticeNodes: [], inputElementsRange: .endIndex(0))
]
var structure: InputGraphStructure = InputGraphStructure()
/// NextIndex
var allowedNextIndex: [Int: IndexSet] = [:]
/// prevIndex
var allowedPrevIndex: [Int: IndexSet] = [:]
static func build(input: LookupGraph, nodeIndex2LatticeNode: [Int: [LatticeNode]]) -> Self {
let nodes = input.nodes.enumerated().map { (index, node) in
Node(latticeNodes: nodeIndex2LatticeNode[index, default: []], displayedTextRange: node.displayedTextRange, inputElementsRange: node.inputElementsRange, correction: node.correction)
Node(latticeNodes: nodeIndex2LatticeNode[index, default: []], inputElementsRange: node.inputElementsRange, correction: node.correction)
}
return Self(nodes: nodes, structure: input.structure)
return Self(nodes: nodes, allowedNextIndex: input.allowedNextIndex, allowedPrevIndex: input.allowedPrevIndex)
}
}
extension ConvertGraph {
///
final class LatticeNode: CustomStringConvertible {
///
public let data: DicdataElement
/// ConvertGraphindex
var nextConvertNodeIndices: IndexSet = []
/// `N_best`
var prevs: [RegisteredNode] = []
/// `prevs`
var values: [PValue] = []
/// inputData.inputrange
var displayedTextRange: InputGraphStructure.Range
var inputElementsRange: InputGraphStructure.Range
var inputElementsRange: InputGraphRange
/// `EOS`
static var EOSNode: LatticeNode {
LatticeNode(data: DicdataElement.EOSData, displayedTextRange: .unknown, inputElementsRange: .unknown)
LatticeNode(data: DicdataElement.EOSData, nextConvertNodeIndices: [], inputElementsRange: .unknown)
}
init(data: DicdataElement, displayedTextRange: InputGraphStructure.Range, inputElementsRange: InputGraphStructure.Range, prevs: [RegisteredNode] = []) {
init(data: DicdataElement, nextConvertNodeIndices: IndexSet, inputElementsRange: InputGraphRange, prevs: [RegisteredNode] = []) {
self.data = data
self.values = [data.value()]
self.displayedTextRange = displayedTextRange
self.nextConvertNodeIndices = nextConvertNodeIndices
self.inputElementsRange = inputElementsRange
self.prevs = prevs
}
@ -65,7 +68,6 @@ extension ConvertGraph {
data: self.data,
registered: self.prevs[index],
totalValue: value,
displayedTextRange: self.displayedTextRange,
inputElementsRange: self.inputElementsRange
)
}
@ -82,21 +84,19 @@ extension ConvertGraph {
///
let totalValue: PValue
/// inputData.inputrange
var displayedTextRange: InputGraphStructure.Range
var inputElementsRange: InputGraphStructure.Range
var inputElementsRange: InputGraphRange
init(data: DicdataElement, registered: RegisteredNode?, totalValue: PValue, displayedTextRange: InputGraphStructure.Range, inputElementsRange: InputGraphStructure.Range) {
init(data: DicdataElement, registered: RegisteredNode?, totalValue: PValue, inputElementsRange: InputGraphRange) {
self.data = data
self.prev = registered
self.totalValue = totalValue
self.displayedTextRange = displayedTextRange
self.inputElementsRange = inputElementsRange
}
///
/// - Returns:
static func BOSNode() -> RegisteredNode {
RegisteredNode(data: DicdataElement.BOSData, registered: nil, totalValue: 0, displayedTextRange: .endIndex(0), inputElementsRange: .endIndex(0))
RegisteredNode(data: DicdataElement.BOSData, registered: nil, totalValue: 0, inputElementsRange: .endIndex(0))
}
}
@ -108,31 +108,29 @@ protocol RegisteredNodeProtocol {
var data: DicdataElement {get}
var prev: (any RegisteredNodeProtocol)? {get}
var totalValue: PValue {get}
/// inputData.inputrange
var displayedTextRange: InputGraphStructure.Range {get}
var inputElementsRange: InputGraphStructure.Range {get}
var inputElementsRange: InputGraphRange {get}
}
extension ConvertGraph {
func convertAll(option: borrowing ConvertRequestOptions, dicdataStore: DicdataStore) -> LatticeNode {
let result: LatticeNode = LatticeNode.EOSNode
result.displayedTextRange = .startIndex(self.structure.displayedTextEndIndexToNodeIndices.endIndex)
result.inputElementsRange = .startIndex(self.structure.inputElementsEndIndexToNodeIndices.endIndex)
result.inputElementsRange = .init(startIndex: self.nodes.compactMap {$0.inputElementsRange.endIndex}.max(), endIndex: nil)
var processStack = Array(self.nodes.enumerated().reversed())
var processedIndices: IndexSet = [0] // root
var invalidIndices: IndexSet = []
// inodes
while let (i, graphNode) = processStack.popLast() {
//
guard !processedIndices.contains(i), !invalidIndices.contains(i) else {
continue
}
// prevNode
let prevIndices = self.structure.prevIndices(displayedTextStartIndex: graphNode.displayedTextRange.startIndex, inputElementsStartIndex: graphNode.inputElementsRange.startIndex)
let prevIndices = self.allowedPrevIndex[i, default: []]
guard !prevIndices.isEmpty else {
//
invalidIndices.insert(i)
continue
}
var unprocessedPrevs: [(Int, Node)] = []
for prevIndex in prevIndices {
if !processedIndices.contains(prevIndex) && !invalidIndices.contains(prevIndex) {
@ -145,7 +143,7 @@ extension ConvertGraph {
processStack.append(contentsOf: unprocessedPrevs)
continue
}
print(i, graphNode.displayedTextRange, graphNode.inputElementsRange)
print(i, graphNode.inputElementsRange)
processedIndices.insert(i)
//
for node in graphNode.latticeNodes {
@ -164,19 +162,14 @@ extension ConvertGraph {
// values
node.values = node.prevs.map {$0.totalValue + wValue}
}
// LatticeNode
let nextIndices = self.structure.nextIndices(
displayedTextEndIndex: node.displayedTextRange.endIndex,
inputElementsEndIndex: node.inputElementsRange.endIndex
)
// count
if nextIndices.isEmpty || self.structure.inputElementsStartIndexToNodeIndices.endIndex == node.inputElementsRange.endIndex {
//
if node.nextConvertNodeIndices.isEmpty || result.inputElementsRange.startIndex == node.inputElementsRange.endIndex {
for index in node.prevs.indices {
let newnode: RegisteredNode = node.getRegisteredNode(index, value: node.values[index])
result.prevs.append(newnode)
}
} else {
for nextIndex in nextIndices {
for nextIndex in node.nextConvertNodeIndices {
// nodenextnode
for nextnode in self.nodes[nextIndex].latticeNodes {
// node.registered.isEmpty

View File

@ -10,6 +10,136 @@ import Foundation
import XCTest
struct CorrectGraph {
var nodes: [Node] = [
// BOS
.init(inputElementsRange: .endIndex(0), inputStyle: .all, correction: .none, value: "\0")
]
/// NextIndex
var allowedNextIndex: [Int: IndexSet] = [:]
/// prevIndex
var allowedPrevIndex: [Int: IndexSet] = [:]
struct Node: Equatable, Sendable {
var inputElementsRange: InputGraphRange
var inputStyle: InputGraphInputStyle.ID
var correction: CorrectGraph2.Correction
var value: Character
}
@discardableResult
mutating func insert(_ node: consuming Node, nextTo prevNodeIndexSet: IndexSet) -> Int {
let index = nodes.count
for prevNodeIndex in prevNodeIndexSet {
self.allowedNextIndex[prevNodeIndex, default: IndexSet()].insert(index)
}
self.allowedPrevIndex[index, default: IndexSet()].formUnion(prevNodeIndexSet)
self.nodes.append(consume node)
return index
}
mutating func insertConnectedTypoNodes(values: [Character], startIndex: Int, endIndex: Int, inputStyle: InputGraphInputStyle.ID, lastIndexSet: IndexSet) -> Int {
guard !values.isEmpty else {
fatalError("values must not be empty")
}
var lastIndexSet = lastIndexSet
for (i, c) in zip(values.indices, values) {
let inputElementRange: InputGraphRange = if i == values.startIndex && i+1 == values.endIndex {
.range(startIndex, endIndex)
} else if i == values.startIndex {
.init(startIndex: startIndex, endIndex: nil)
} else if i+1 == values.endIndex {
.init(startIndex: nil, endIndex: endIndex)
} else {
.unknown
}
let node = Node(
inputElementsRange: inputElementRange,
inputStyle: inputStyle,
correction: .typo,
value: c
)
lastIndexSet = IndexSet(integer: self.insert(node, nextTo: lastIndexSet))
}
return lastIndexSet.first!
}
static func build(input: [ComposingText.InputElement]) -> Self {
var correctGraph = Self()
var inputIndexToEndNodeIndices: [Int: IndexSet] = [0: IndexSet(integer: 0)]
for (index, item) in zip(input.indices, input) {
//
do {
let nodeIndex = correctGraph.insert(
Node(
inputElementsRange: .range(index, index + 1),
inputStyle: InputGraphInputStyle(from: input[index].inputStyle).id,
correction: .none,
value: item.character
),
nextTo: inputIndexToEndNodeIndices[index, default: IndexSet()]
)
inputIndexToEndNodeIndices[index + 1, default: IndexSet()].insert(nodeIndex)
}
//
let correctPrefixTree = switch item.inputStyle {
case .roman2kana: CorrectPrefixTree.roman2kana
case .direct: CorrectPrefixTree.direct
}
typealias Match = (replace: String, inputCount: Int)
typealias SearchItem = (
node: CorrectPrefixTree.Node,
nextIndex: Int,
route: [Character],
inputStyleId: InputGraphInputStyle.ID
)
var stack: [SearchItem] = [
(correctPrefixTree, index, [], .all),
]
while let (cNode, cIndex, cRoute, cInputStyleId) = stack.popLast() {
guard cIndex < input.endIndex else {
continue
}
let inputStyleId = InputGraphInputStyle(from: input[cIndex].inputStyle).id
guard cInputStyleId.isCompatible(with: inputStyleId) else {
continue
}
if let nNode = cNode.find(key: input[cIndex].character) {
stack.append((nNode, cIndex + 1, cRoute + [input[cIndex].character], inputStyleId))
for value in nNode.value {
if value.isEmpty {
continue
} else if value.count > 1 {
let nodeIndex = correctGraph.insertConnectedTypoNodes(
values: Array(value),
startIndex: index,
endIndex: index + cRoute.count + 1,
inputStyle: inputStyleId,
lastIndexSet: inputIndexToEndNodeIndices[index, default: IndexSet()]
)
inputIndexToEndNodeIndices[index + cRoute.count + 1, default: IndexSet()].insert(nodeIndex)
} else {
let nodeIndex = correctGraph.insert(
Node(
inputElementsRange: .range(index, index + cRoute.count + 1),
inputStyle: inputStyleId,
correction: .typo,
value: value.first!
),
nextTo: inputIndexToEndNodeIndices[index, default: IndexSet()]
)
inputIndexToEndNodeIndices[index + cRoute.count + 1, default: IndexSet()].insert(nodeIndex)
}
}
}
}
}
return correctGraph
}
}
struct CorrectGraph2 {
var nodes: [Node] = []
/// NextIndex
var allowedNextIndex: [Int: Int] = [:]
@ -44,8 +174,8 @@ struct CorrectGraph {
}
struct Node: Equatable, Sendable {
var inputElementsRange: InputGraphStructure.Range
var inputStyle: InputGraph.InputStyle.ID
var inputElementsRange: InputGraphRange
var inputStyle: InputGraphInputStyle.ID
var correction: Correction
var value: Character
var groupId: Int?
@ -88,11 +218,11 @@ struct CorrectGraph {
return index
}
mutating func insertConnectedTypoNodes(values: [Character], startIndex: Int, endIndex: Int, inputStyle: InputGraph.InputStyle.ID) {
mutating func insertConnectedTypoNodes(values: [Character], startIndex: Int, endIndex: Int, inputStyle: InputGraphInputStyle.ID) {
var indices: [Int] = []
let id = self.groupIdIota.new()
for (i, c) in zip(values.indices, values) {
let inputElementRange: InputGraphStructure.Range = if i == values.startIndex && i+1 == values.endIndex {
let inputElementRange: InputGraphRange = if i == values.startIndex && i+1 == values.endIndex {
.range(startIndex, endIndex)
} else if i == values.startIndex {
.init(startIndex: startIndex, endIndex: nil)
@ -124,7 +254,7 @@ struct CorrectGraph {
correctGraph.insert(
Node(
inputElementsRange: .range(index, index + 1),
inputStyle: InputGraph.InputStyle(from: input[index].inputStyle).id,
inputStyle: InputGraphInputStyle(from: input[index].inputStyle).id,
correction: .none,
value: item.character
)
@ -138,7 +268,7 @@ struct CorrectGraph {
node: CorrectPrefixTree.Node,
nextIndex: Int,
route: [Character],
inputStyleId: InputGraph.InputStyle.ID
inputStyleId: InputGraphInputStyle.ID
)
var stack: [SearchItem] = [
(correctPrefixTree, index, [], .all),
@ -147,7 +277,7 @@ struct CorrectGraph {
guard cIndex < input.endIndex else {
continue
}
let inputStyleId = InputGraph.InputStyle(from: input[cIndex].inputStyle).id
let inputStyleId = InputGraphInputStyle(from: input[cIndex].inputStyle).id
guard cInputStyleId.isCompatible(with: inputStyleId) else {
continue
}
@ -223,7 +353,7 @@ final class CorrectGraphTests: XCTestCase {
.init(inputElementsRange: .range(2, 3), inputStyle: .systemFlickDirect, correction: .none, value: "")
)
if let index = graph.nodes.firstIndex(where: {$0.value == ""}) {
XCTAssertEqual(graph.prevIndices(for: index).count, 2)
XCTAssertEqual(graph.allowedPrevIndex[index, default: .init()].count, 2)
} else {
XCTAssertThrowsError("Should not be nil")
}
@ -257,14 +387,14 @@ final class CorrectGraphTests: XCTestCase {
)
XCTAssertEqual(
graph.nodes.first(where: {$0.value == "t" && $0.inputElementsRange == .startIndex(0)}),
.init(inputElementsRange: .startIndex(0), inputStyle: .systemRomanKana, correction: .typo, value: "t", groupId: 0)
.init(inputElementsRange: .startIndex(0), inputStyle: .systemRomanKana, correction: .typo, value: "t")
)
XCTAssertEqual(
graph.nodes.first(where: {$0.value == "a"}),
.init(inputElementsRange: .endIndex(2), inputStyle: .systemRomanKana, correction: .typo, value: "a", groupId: 0)
.init(inputElementsRange: .endIndex(2), inputStyle: .systemRomanKana, correction: .typo, value: "a")
)
if let index = graph.nodes.firstIndex(where: {$0.value == "a"}) {
let indices = graph.prevIndices(for: index)
let indices = graph.allowedPrevIndex[index, default: .init()]
XCTAssertEqual(indices.count, 1)
XCTAssertEqual(
indices.first,

View File

@ -11,504 +11,208 @@ import DequeModule
@testable import KanaKanjiConverterModule
import XCTest
struct InputGraphStructure {
enum Range: Equatable, Sendable {
case unknown
case startIndex(Int)
case endIndex(Int)
case range(Int, Int)
init(startIndex: Int?, endIndex: Int?) {
self = switch (startIndex, endIndex) {
case let (s?, e?): .range(s, e)
case (let s?, nil): .startIndex(s)
case (nil, let e?): .endIndex(e)
case (nil, nil): .unknown
}
}
var startIndex: Int? {
switch self {
case .unknown, .endIndex: nil
case .startIndex(let index), .range(let index, _): index
}
}
var endIndex: Int? {
switch self {
case .unknown, .startIndex: nil
case .endIndex(let index), .range(_, let index): index
}
}
}
/// `displayedTextStartIndexToNodeIndices[0]``displayedTextRange==.startIndex(0)``displayedTextRange==.range(0, k)`index
var displayedTextStartIndexToNodeIndices: [IndexSet] = []
var inputElementsStartIndexToNodeIndices: [IndexSet] = []
var displayedTextEndIndexToNodeIndices: [IndexSet] = [IndexSet(integer: 0)] // rootindex
var inputElementsEndIndexToNodeIndices: [IndexSet] = [IndexSet(integer: 0)] // rootindex
/// 使
var deadNodeIndices: [Int] = []
/// NextIndex
var allowedNextIndex: [Int: [Int]] = [:]
/// prevIndex
var allowedPrevIndex: [Int: [Int]] = [:]
/// id
var groupIdIota: Iota = Iota()
func nextIndices(displayedTextEndIndex: Int?, inputElementsEndIndex: Int?) -> IndexSet {
var indexSet = IndexSet()
if let displayedTextEndIndex {
if displayedTextEndIndex < self.displayedTextStartIndexToNodeIndices.endIndex {
indexSet.formUnion(self.displayedTextStartIndexToNodeIndices[displayedTextEndIndex])
}
}
if let inputElementsEndIndex {
if inputElementsEndIndex < self.inputElementsStartIndexToNodeIndices.endIndex {
indexSet.formUnion(self.inputElementsStartIndexToNodeIndices[inputElementsEndIndex])
}
}
return indexSet
}
func prevIndices(displayedTextStartIndex: Int?, inputElementsStartIndex: Int?) -> IndexSet {
var indexSet = IndexSet()
if let displayedTextStartIndex {
if displayedTextStartIndex < self.displayedTextEndIndexToNodeIndices.endIndex {
indexSet.formUnion(self.displayedTextEndIndexToNodeIndices[displayedTextStartIndex])
}
}
if let inputElementsStartIndex {
if inputElementsStartIndex < self.inputElementsEndIndexToNodeIndices.endIndex {
indexSet.formUnion(self.inputElementsEndIndexToNodeIndices[inputElementsStartIndex])
}
}
return indexSet
}
enum Connection {
case none
case nextRestriction(Int)
case restriction(prev: Int, next: Int)
case prevRestriction(Int)
}
/// `index`
mutating func insert<T>(_ node: T, nodes: inout [T], displayedTextRange: Range, inputElementsRange: Range, connection: Connection = .none) -> Int {
// deadNodeIndices
let index: Int
if let deadIndex = self.deadNodeIndices.popLast() {
nodes[deadIndex] = node
index = deadIndex
} else {
nodes.append(node)
index = nodes.count - 1
}
//
if case let .restriction(prev, next) = connection {
self.allowedPrevIndex[prev, default: []].append(index)
self.allowedPrevIndex[index, default: []].append(prev)
self.allowedNextIndex[next, default: []].append(index)
self.allowedNextIndex[index, default: []].append(next)
return index
}
if case let .nextRestriction(next) = connection {
self.allowedNextIndex[index, default: []].append(next)
self.allowedNextIndex[next, default: []].append(index)
} else {
// endIndex
if let endIndex = displayedTextRange.endIndex {
if self.displayedTextEndIndexToNodeIndices.endIndex <= endIndex {
self.displayedTextEndIndexToNodeIndices.append(contentsOf: Array(repeating: IndexSet(), count: endIndex - self.displayedTextEndIndexToNodeIndices.endIndex + 1))
}
self.displayedTextEndIndexToNodeIndices[endIndex].insert(index)
}
if let endIndex = inputElementsRange.endIndex {
if self.inputElementsEndIndexToNodeIndices.endIndex <= endIndex {
self.inputElementsEndIndexToNodeIndices.append(contentsOf: Array(repeating: IndexSet(), count: endIndex - self.inputElementsEndIndexToNodeIndices.endIndex + 1))
}
self.inputElementsEndIndexToNodeIndices[endIndex].insert(index)
}
}
if case let .prevRestriction(prev) = connection {
self.allowedPrevIndex[index, default: []].append(prev)
self.allowedPrevIndex[prev, default: []].append(index)
} else {
// startIndex
//
if let startIndex = displayedTextRange.startIndex {
if self.displayedTextStartIndexToNodeIndices.endIndex <= startIndex {
self.displayedTextStartIndexToNodeIndices.append(contentsOf: Array(repeating: IndexSet(), count: startIndex - self.displayedTextStartIndexToNodeIndices.endIndex + 1))
}
self.displayedTextStartIndexToNodeIndices[startIndex].insert(index)
}
if let startIndex = inputElementsRange.startIndex {
if self.inputElementsStartIndexToNodeIndices.endIndex <= startIndex {
self.inputElementsStartIndexToNodeIndices.append(contentsOf: Array(repeating: IndexSet(), count: startIndex - self.inputElementsStartIndexToNodeIndices.endIndex + 1))
}
self.inputElementsStartIndexToNodeIndices[startIndex].insert(index)
}
}
return index
}
mutating func remove(at index: Int) {
assert(index != 0, "Node at index 0 is root and must not be removed.")
self.deadNodeIndices.append(index)
// FIXME: node使remove
self.allowedPrevIndex.values.mutatingForeach {
$0.removeAll(where: {$0 == index})
}
self.allowedPrevIndex.removeValue(forKey: index)
self.allowedNextIndex.values.mutatingForeach {
$0.removeAll(where: {$0 == index})
}
self.allowedNextIndex.removeValue(forKey: index)
self.displayedTextStartIndexToNodeIndices.mutatingForeach {
$0.remove(index)
}
self.displayedTextEndIndexToNodeIndices.mutatingForeach {
$0.remove(index)
}
self.inputElementsStartIndexToNodeIndices.mutatingForeach {
$0.remove(index)
}
self.inputElementsEndIndexToNodeIndices.mutatingForeach {
$0.remove(index)
}
}
}
struct InputGraph: InputGraphProtocol {
struct InputStyle: Identifiable {
init(from deprecatedInputStyle: KanaKanjiConverterModule.InputStyle) {
switch deprecatedInputStyle {
case .direct:
self = .systemFlickDirect
case .roman2kana:
self = .systemRomanKana
}
}
init(id: InputGraph.InputStyle.ID, replacePrefixTree: ReplacePrefixTree.Node, correctPrefixTree: CorrectPrefixTree.Node) {
self.id = id
self.replacePrefixTree = replacePrefixTree
self.correctPrefixTree = correctPrefixTree
}
struct ID: Equatable, Hashable, Sendable, CustomStringConvertible {
init(id: UInt8) {
self.id = id
}
init(from deprecatedInputStyle: KanaKanjiConverterModule.InputStyle) {
switch deprecatedInputStyle {
case .direct:
self = .systemFlickDirect
case .roman2kana:
self = .systemRomanKana
}
}
static let all = Self(id: 0x00)
static let systemFlickDirect = Self(id: 0x01)
static let systemRomanKana = Self(id: 0x02)
var id: UInt8
func isCompatible(with id: ID) -> Bool {
if self == .all {
true
} else {
self == id
}
}
var description: String {
"ID(\(id))"
}
}
static let all: Self = InputStyle(
id: .all,
replacePrefixTree: ReplacePrefixTree.Node(),
correctPrefixTree: CorrectPrefixTree.Node()
)
static let systemFlickDirect: Self = InputStyle(
id: .systemFlickDirect,
replacePrefixTree: ReplacePrefixTree.direct,
correctPrefixTree: CorrectPrefixTree.direct
)
static let systemRomanKana: Self = InputStyle(
id: .systemRomanKana,
replacePrefixTree: ReplacePrefixTree.roman2kana,
correctPrefixTree: CorrectPrefixTree.roman2kana
)
/// `id` for the input style.
/// - warning: value `0x00-0x7F` is reserved for system space.
var id: ID
var replacePrefixTree: ReplacePrefixTree.Node
var correctPrefixTree: CorrectPrefixTree.Node
}
struct Node: InputGraphNodeProtocol, Equatable, CustomStringConvertible {
struct InputGraph {
struct Node: Equatable, CustomStringConvertible {
var character: Character
var displayedTextRange: InputGraphStructure.Range
var inputElementsRange: InputGraphStructure.Range
var groupId: Int? = nil
var correction: CorrectGraph.Correction = .none
/// replace
var isReplaced: Bool = false
var inputElementsRange: InputGraphRange
var correction: CorrectGraph2.Correction = .none
var description: String {
let ds = displayedTextRange.startIndex?.description ?? "?"
let de = displayedTextRange.endIndex?.description ?? "?"
let `is` = inputElementsRange.startIndex?.description ?? "?"
let ie = inputElementsRange.endIndex?.description ?? "?"
return "Node(\"\(character)\", d(\(ds)..<\(de)), i(\(`is`)..<\(ie)), isTypo: \(correction.isTypo), id: \(groupId))"
return "Node(\"\(character)\", i(\(`is`)..<\(ie)), isTypo: \(correction.isTypo))"
}
}
var nodes: [Node] = [
// root node
Node(character: "\0", displayedTextRange: .endIndex(0), inputElementsRange: .endIndex(0))
Node(character: "\0", inputElementsRange: .endIndex(0), correction: .none)
]
/// NextIndex
var allowedNextIndex: [Int: IndexSet] = [:]
/// prevIndex
var allowedPrevIndex: [Int: IndexSet] = [:]
/// correctGraph
var nextCorrectNodeIndices: [Int: IndexSet] = [:]
var structure: InputGraphStructure = InputGraphStructure()
mutating func backwardMatches(_ correctGraph: CorrectGraph, nodeIndex: Int) {
let correctGraphNode = correctGraph.nodes[nodeIndex]
mutating func update(_ correctGraph: CorrectGraph, nodeIndex: Int) {
let cgNode = correctGraph.nodes[nodeIndex]
//
// 1. nodeIndexnextCorrectNodeIndices
// 2. cgNodes[nodeIndex]
// 3.
// cgNodeinsert
let prevNodeIndices: [Int] = self.nextCorrectNodeIndices.lazy.filter {
$0.value.contains(nodeIndex)
}.map {
$0.key
}
let newIndex = self.nodes.endIndex
self.nodes.append(Node(character: cgNode.value, inputElementsRange: cgNode.inputElementsRange, correction: cgNode.correction))
//
self.allowedPrevIndex[newIndex] = IndexSet(prevNodeIndices)
for prevNodeIndex in prevNodeIndices {
self.allowedNextIndex[prevNodeIndex, default: IndexSet()].insert(newIndex)
}
// correct graphnext node
self.nextCorrectNodeIndices[newIndex] = correctGraph.allowedNextIndex[nodeIndex]
let startNode = switch correctGraphNode.inputStyle {
//
let startNode = switch cgNode.inputStyle {
case .systemFlickDirect:
ReplaceSuffixTree.direct
case .systemRomanKana:
ReplaceSuffixTree.roman2kana
default: fatalError("implement it")
}
print(nodeIndex, startNode.children.count, correctGraphNode)
// nodes
typealias SearchItem = (
suffixTreeNode: ReplaceSuffixTree.Node,
startNodeIndex: Int,
// 辿
route: [Int],
correction: CorrectGraph.Correction
//
foundValue: Replacement?,
correction: CorrectGraph2.Correction
)
typealias Match = (
displayedTextStartIndex: Int?,
inputElementsStartIndex: Int?,
inputElementsEndIndex: Int?,
backwardRoute: [Int],
value: String,
/// `index`
licenserNodeIndex: Int?,
/// groupId
groupId: Int?,
correction: CorrectGraph.Correction
//
replacement: Replacement,
// route
route: [Int]
)
struct Replacement: Hashable {
var route: [Int]
var value: String
}
var backSearchMatch: [Match] = []
var stack: [SearchItem] = [(startNode, nodeIndex, [nodeIndex], correctGraphNode.correction.isTypo ? .typo : .none)]
while let (cSuffixTreeNode, cNodeIndex, cRoute, cCorrection) = stack.popLast() {
let isUnInsertedNode = cNodeIndex == nodeIndex && cRoute.count == 1
let bNode = if isUnInsertedNode {
cSuffixTreeNode.find(key: correctGraphNode.value)
} else {
cSuffixTreeNode.find(key: self.nodes[cNodeIndex].character)
}
print(nodeIndex, cRoute, isUnInsertedNode ? correctGraphNode.value : self.nodes[cNodeIndex].character, bNode?.character, cSuffixTreeNode.children.count, cCorrection)
if let bNode {
// cNodeIndexprev
let indices = if isUnInsertedNode {
if let groupId = correctGraphNode.groupId,
let lastNodeIndex = self.nodes.lastIndex(where: {$0.groupId == groupId}) {
self.structure.prevIndices(displayedTextStartIndex: nil, inputElementsStartIndex: correctGraphNode.inputElementsRange.startIndex)
.union(IndexSet(integer: lastNodeIndex))
} else {
self.structure.prevIndices(displayedTextStartIndex: nil, inputElementsStartIndex: correctGraphNode.inputElementsRange.startIndex)
}
} else {
self.prevIndices(for: self.nodes[cNodeIndex]).union(IndexSet(self.structure.allowedPrevIndex[cNodeIndex, default: []]))
.filteredIndexSet {
if let pEndIndex = self.nodes[$0].inputElementsRange.endIndex,
let cStartIndex = self.nodes[cNodeIndex].inputElementsRange.startIndex {
return pEndIndex == cStartIndex
}
return true
}
}
let nonReplacedIndices = indices.filteredIndexSet {!self.nodes[$0].isReplaced}
print(nodeIndex, cRoute, bNode.character, Array(indices), indices.map{(self.nodes[$0].character, self.nodes[$0].isReplaced)})
// bNode: 1
// bNode
if let value = bNode.value {
// MARK: A: bNodechildrenlongestMatch
if bNode.children.isEmpty && !cCorrection.isTypo {
let lastNode = nonReplacedIndices.first {!self.nodes[$0].correction.isTypo}.map{self.nodes[$0]}
let inputElementsStartIndex = lastNode?.inputElementsRange.endIndex
let displayedTextStartIndex = lastNode?.displayedTextRange.endIndex
backSearchMatch.append(
(displayedTextStartIndex, inputElementsStartIndex, correctGraphNode.inputElementsRange.endIndex, cRoute, value, nil, nil, cCorrection)
)
} else {
// MARK: B: findprev
for prevGraphNodeIndex in nonReplacedIndices {
if bNode.find(key: self.nodes[prevGraphNodeIndex].character) == nil {
let inputElementsStartIndex = self.nodes[prevGraphNodeIndex].inputElementsRange.endIndex
let displayedTextStartIndex = self.nodes[prevGraphNodeIndex].displayedTextRange.endIndex
let licenser: Int? = if self.nodes[prevGraphNodeIndex].correction.isTypo {
prevGraphNodeIndex
} else {
nil
}
backSearchMatch.append(
(displayedTextStartIndex, inputElementsStartIndex, correctGraphNode.inputElementsRange.endIndex, cRoute, value, licenser, nil, cCorrection)
)
}
}
}
} else if isUnInsertedNode {
let lastNode = nonReplacedIndices.first {!self.nodes[$0].correction.isTypo}.map{self.nodes[$0]}
let displayedTextStartIndex = lastNode?.displayedTextRange.endIndex
backSearchMatch.append(
(
displayedTextStartIndex,
correctGraphNode.inputElementsRange.startIndex,
correctGraphNode.inputElementsRange.endIndex,
cRoute,
String(correctGraphNode.value),
nil,
groupId: correctGraphNode.groupId,
cCorrection
)
)
}
for prevGraphNodeIndex in indices {
var stack: [SearchItem] = [(startNode, [newIndex], foundValue: nil, correction: cgNode.correction)]
while let (cSuffixTreeNode, cRoute, cFoundValue, cCorrection) = stack.popLast() {
// must not be empty
let cNodeIndex = cRoute[0]
if let bNode = cSuffixTreeNode.find(key: self.nodes[cNodeIndex].character) {
for prevGraphNodeIndex in self.allowedPrevIndex[cNodeIndex, default: IndexSet()] {
// TODO: InputGraph.NodeInputStyle.ID
stack.append(
(
bNode,
prevGraphNodeIndex,
// FIXME:
[prevGraphNodeIndex] + cRoute,
// bNodevalue
foundValue: bNode.value.map {Replacement(route: cRoute, value: $0)} ?? cFoundValue,
cCorrection.isTypo ? .typo : self.nodes[prevGraphNodeIndex].correction
)
)
}
} else {
//
if isUnInsertedNode {
let displayedTextStartIndex: Int? = if cCorrection.isTypo {
nil
} else {
nil
}
backSearchMatch.append(
(
displayedTextStartIndex,
correctGraphNode.inputElementsRange.startIndex,
correctGraphNode.inputElementsRange.endIndex,
cRoute,
String(correctGraphNode.value),
nil,
correctGraphNode.groupId,
cCorrection
)
)
// bNodebackSearcMatch
if let cFoundValue {
backSearchMatch.append((cFoundValue, cRoute))
}
}
}
print(backSearchMatch)
var removeTargetIndices: IndexSet = IndexSet()
for match in backSearchMatch {
// licenserlicensergroupId
let licenser = match.licenserNodeIndex.map{self.nodes[$0]}
// groupgroupId
if match.value.count > 1 {
self.insertConnectedNodes(
values: Array(match.value),
inputElementsRange: .init(startIndex: match.inputElementsStartIndex, endIndex: match.inputElementsEndIndex),
displayedTextStartIndex: match.displayedTextStartIndex,
correction: match.correction,
inputStyle: correctGraphNode.inputStyle
)
} else if match.value.count == 1 {
let index = self.insert(
Node(
character: match.value.first!,
displayedTextRange: match.displayedTextStartIndex.map{.range($0, $0 + match.value.count)} ?? .unknown,
inputElementsRange: .init(startIndex: match.inputElementsStartIndex, endIndex: match.inputElementsEndIndex),
groupId: match.groupId ?? licenser?.groupId,
correction: match.correction
)
)
if licenser?.groupId == nil, let licenserNodeIndex = match.licenserNodeIndex {
self.createNewConnection(from: licenserNodeIndex, to: index)
}
}
if match.correction == .none {
for nodeIndex in match.backwardRoute.dropLast() {
self.nodes[nodeIndex].isReplaced = true
}
}
}
}
// backSearchMatch
let replacementToTarget = Dictionary(grouping: backSearchMatch, by: \.replacement)
for (replacement, matches) in replacementToTarget {
// MARK: replace
// 1. valuenode
// 2. routenodeinvalidate
mutating func createNewConnection(from fromNodeIndex: Int, to toNodeIndex: Int) {
assert(self.nodes[fromNodeIndex].groupId == nil)
let newId = self.structure.groupIdIota.new()
self.nodes[fromNodeIndex].groupId = newId
self.nodes[toNodeIndex].groupId = newId
self.structure.inputElementsStartIndexToNodeIndices.mutatingForeach { indexSet in
indexSet.remove(toNodeIndex)
}
self.structure.displayedTextStartIndexToNodeIndices.mutatingForeach { indexSet in
indexSet.remove(toNodeIndex)
}
self.structure.inputElementsEndIndexToNodeIndices.mutatingForeach { indexSet in
indexSet.remove(fromNodeIndex)
}
self.structure.displayedTextEndIndexToNodeIndices.mutatingForeach { indexSet in
indexSet.remove(fromNodeIndex)
}
self.structure.allowedNextIndex[fromNodeIndex, default: []].append(toNodeIndex)
self.structure.allowedPrevIndex[toNodeIndex, default: []].append(fromNodeIndex)
}
// MARK:
let startIndex = self.nodes[replacement.route[0]].inputElementsRange.startIndex
let endIndex = self.nodes[replacement.route[replacement.route.endIndex - 1]].inputElementsRange.endIndex
mutating func insertConnectedNodes(values: [Character], inputElementsRange: InputGraphStructure.Range, displayedTextStartIndex: Int?, correction: CorrectGraph.Correction, inputStyle: InputGraph.InputStyle.ID) {
let id = self.structure.groupIdIota.new()
var lastNodeIndex: Int? = nil
for (i, c) in zip(values.indices, values) {
let inputElementRange: InputGraphStructure.Range = if i == values.startIndex && i+1 == values.endIndex {
.init(startIndex: inputElementsRange.startIndex, endIndex: inputElementsRange.endIndex)
} else if i == values.startIndex {
.init(startIndex: inputElementsRange.startIndex, endIndex: nil)
} else if i+1 == values.endIndex {
.init(startIndex: nil, endIndex: inputElementsRange.endIndex)
let characters = Array(replacement.value)
let correction: CorrectGraph2.Correction = if replacement.route.allSatisfy({!self.nodes[$0].correction.isTypo}) {
.none
} else {
.unknown
.typo
}
let node = Node(
character: c,
displayedTextRange: displayedTextStartIndex.map{.range($0+i, $0 + i+1)} ?? .unknown,
inputElementsRange: inputElementRange,
groupId: id,
correction: correction
)
lastNodeIndex = self.insert(node, connection: lastNodeIndex.map {.prevRestriction($0)} ?? .none)
let newNodes = characters.indices.map { index in
let range: InputGraphRange = if index == characters.startIndex && index == characters.endIndex - 1 {
.init(startIndex: startIndex, endIndex: endIndex)
} else if index == characters.startIndex {
.init(startIndex: startIndex, endIndex: nil)
} else if index == characters.endIndex - 1 {
.init(startIndex: nil, endIndex: endIndex)
} else {
.unknown
}
return Node(character: characters[index], inputElementsRange: range, correction: correction)
}
let firstIndex = self.nodes.endIndex
let lastIndex = self.nodes.endIndex + newNodes.count - 1
self.nodes.append(contentsOf: newNodes)
// MARK: next/prev調
// firstIndex:
// routereplaceindex
let prevIndices = matches.compactMap { match in
assert(match.route.hasSuffix(replacement.route))
return match.route.dropLast(replacement.route.count).last
}
self.allowedPrevIndex[firstIndex] = IndexSet(prevIndices)
for i in prevIndices {
// firstIndexreplacement
self.allowedNextIndex[i, default: IndexSet()].insert(firstIndex)
self.allowedNextIndex[i, default: IndexSet()].remove(replacement.route[0])
}
//
for i in firstIndex ..< lastIndex {
self.allowedNextIndex[i, default: IndexSet()].insert(i + 1)
self.allowedPrevIndex[i + 1, default: IndexSet()].insert(i)
}
// lastIndex: correctGraph
self.nextCorrectNodeIndices[lastIndex] = correctGraph.allowedNextIndex[nodeIndex]
}
// for
for replacement in replacementToTarget.keys {
//
self.nextCorrectNodeIndices[replacement.route.last!] = IndexSet()
self.allowedPrevIndex[replacement.route.last!] = IndexSet()
}
}
private mutating func clean() {
var newGraph = Self(nodes: [])
var indices: [(nodeIndex: Int, fromIndex: Int?)] = [(0, nil)]
var processedNodeIndices: [Int: Int] = [:]
while let (nodeIndex, fromIndex) = indices.popLast() {
let newIndex = if let newIndex = processedNodeIndices[nodeIndex] {
newIndex
} else {
{
let newIndex = newGraph.nodes.endIndex
newGraph.nodes.append(self.nodes[nodeIndex])
newGraph.nextCorrectNodeIndices[newIndex] = self.nextCorrectNodeIndices[nodeIndex]
return newIndex
}()
}
if let fromIndex {
newGraph.allowedNextIndex[fromIndex, default: IndexSet()].insert(newIndex)
newGraph.allowedPrevIndex[newIndex, default: IndexSet()].insert(fromIndex)
}
for nextNodeIndex in self.allowedNextIndex[nodeIndex, default: IndexSet()] {
indices.append((nextNodeIndex, newIndex))
}
processedNodeIndices[nodeIndex] = newIndex
}
self = newGraph
}
static func build(input: CorrectGraph) -> Self {
var inputGraph = Self()
inputGraph.structure.groupIdIota = input.groupIdIota
// insertCorrectGraphNode
var nodeIndices = Array(input.inputElementsStartIndexToNodeIndices.first ?? .init())
// update
var nodeIndices = Array([0])
var processedIndices = IndexSet()
while let nodeIndex = nodeIndices.popLast() {
print("build", input.nodes[nodeIndex].value)
if processedIndices.contains(nodeIndex) {
continue
}
var prevIndices = input.prevIndices(for: nodeIndex)
if let prevIndex = input.allowedPrevIndex[nodeIndex] {
prevIndices.insert(prevIndex)
}
let prevIndices = input.allowedPrevIndex[nodeIndex, default: IndexSet()]
//
let diff = prevIndices.subtracting(processedIndices)
guard diff.isEmpty else {
@ -517,12 +221,18 @@ struct InputGraph: InputGraphProtocol {
continue
}
processedIndices.insert(nodeIndex)
inputGraph.backwardMatches(input, nodeIndex: nodeIndex)
nodeIndices.append(contentsOf: input.nextIndices(for: nodeIndex))
if let nextIndex = input.allowedNextIndex[nodeIndex] {
nodeIndices.append(nextIndex)
// root
if nodeIndex != 0 {
inputGraph.update(input, nodeIndex: nodeIndex)
} else {
// nextCorrectNodeIndices
inputGraph.nextCorrectNodeIndices[0] = input.allowedNextIndex[0]
}
nodeIndices.append(contentsOf: input.allowedNextIndex[nodeIndex, default: IndexSet()])
}
// invalidatenode
inputGraph.clean()
return inputGraph
}
}

View File

@ -0,0 +1,110 @@
//
// InputGraph.swift
//
//
// Created by miwa on 2024/02/21.
//
import Foundation
import DequeModule
@testable import KanaKanjiConverterModule
import XCTest
enum InputGraphRange: Equatable, Sendable {
case unknown
case startIndex(Int)
case endIndex(Int)
case range(Int, Int)
init(startIndex: Int?, endIndex: Int?) {
self = switch (startIndex, endIndex) {
case let (s?, e?): .range(s, e)
case (let s?, nil): .startIndex(s)
case (nil, let e?): .endIndex(e)
case (nil, nil): .unknown
}
}
var startIndex: Int? {
switch self {
case .unknown, .endIndex: nil
case .startIndex(let index), .range(let index, _): index
}
}
var endIndex: Int? {
switch self {
case .unknown, .startIndex: nil
case .endIndex(let index), .range(_, let index): index
}
}
}
struct InputGraphInputStyle: Identifiable {
init(from deprecatedInputStyle: KanaKanjiConverterModule.InputStyle) {
switch deprecatedInputStyle {
case .direct:
self = .systemFlickDirect
case .roman2kana:
self = .systemRomanKana
}
}
init(id: InputGraphInputStyle.ID, replacePrefixTree: ReplacePrefixTree.Node, correctPrefixTree: CorrectPrefixTree.Node) {
self.id = id
self.replacePrefixTree = replacePrefixTree
self.correctPrefixTree = correctPrefixTree
}
struct ID: Equatable, Hashable, Sendable, CustomStringConvertible {
init(id: UInt8) {
self.id = id
}
init(from deprecatedInputStyle: KanaKanjiConverterModule.InputStyle) {
switch deprecatedInputStyle {
case .direct:
self = .systemFlickDirect
case .roman2kana:
self = .systemRomanKana
}
}
static let all = Self(id: 0x00)
static let systemFlickDirect = Self(id: 0x01)
static let systemRomanKana = Self(id: 0x02)
var id: UInt8
func isCompatible(with id: ID) -> Bool {
if self == .all {
true
} else {
self == id
}
}
var description: String {
"ID(\(id))"
}
}
static let all: Self = Self(
id: .all,
replacePrefixTree: ReplacePrefixTree.Node(),
correctPrefixTree: CorrectPrefixTree.Node()
)
static let systemFlickDirect: Self = Self(
id: .systemFlickDirect,
replacePrefixTree: ReplacePrefixTree.direct,
correctPrefixTree: CorrectPrefixTree.direct
)
static let systemRomanKana: Self = Self(
id: .systemRomanKana,
replacePrefixTree: ReplacePrefixTree.roman2kana,
correctPrefixTree: CorrectPrefixTree.roman2kana
)
/// `id` for the input style.
/// - warning: value `0x00-0x7F` is reserved for system space.
var id: ID
var replacePrefixTree: ReplacePrefixTree.Node
var correctPrefixTree: CorrectPrefixTree.Node
}

View File

@ -1,61 +0,0 @@
//
// InputGraphProtocol.swift
//
//
// Created by miwa on 2024/02/23.
//
import Foundation
protocol InputGraphNodeProtocol {
var displayedTextRange: InputGraphStructure.Range { get set }
var inputElementsRange: InputGraphStructure.Range { get set }
}
protocol InputGraphProtocol {
associatedtype Node: InputGraphNodeProtocol
var nodes: [Node] { get set }
var structure: InputGraphStructure { get set }
}
extension InputGraphProtocol {
var root: Node {
nodes[0]
}
func nextIndices(for node: Node) -> IndexSet {
self.structure.nextIndices(
displayedTextEndIndex: node.displayedTextRange.endIndex,
inputElementsEndIndex: node.inputElementsRange.endIndex
)
}
func next(for node: Node) -> [Node] {
nextIndices(for: node).map{ self.nodes[$0] }
}
func prevIndices(for node: Node) -> IndexSet {
self.structure.prevIndices(
displayedTextStartIndex: node.displayedTextRange.startIndex,
inputElementsStartIndex: node.inputElementsRange.startIndex
)
}
func prev(for node: Node) -> [Node] {
prevIndices(for: node).map{ self.nodes[$0] }
}
mutating func remove(at index: Int) {
assert(index != 0, "Node at index 0 is root and must not be removed.")
self.structure.remove(at: index)
}
@discardableResult
mutating func insert(_ node: Node, connection: InputGraphStructure.Connection = .none) -> Int {
var nodes = self.nodes
let index = self.structure.insert(node, nodes: &nodes, displayedTextRange: node.displayedTextRange, inputElementsRange: node.inputElementsRange, connection: connection)
self.nodes = nodes
return index
}
}

View File

@ -12,16 +12,6 @@ import XCTest
final class InputGraphTests: XCTestCase {
func testInsert() throws {
var graph = InputGraph()
let node1 = InputGraph.Node(character: "a", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 1))
let node2 = InputGraph.Node(character: "b", displayedTextRange: .range(1, 2), inputElementsRange: .range(1, 2))
graph.insert(node1)
graph.insert(node2)
XCTAssertEqual(graph.next(for: node1), [node2])
XCTAssertEqual(graph.prev(for: node2), [node1])
}
func testBuildSimpleDirectInput() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "", inputStyle: .direct),
@ -31,7 +21,7 @@ final class InputGraphTests: XCTestCase {
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(inputGraph.nodes.count, 4) // Root nodes
}
func testBuildSimpleDirectInput_typoあり() throws {
func testBuildSimpleDirectInput_あかう() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "", inputStyle: .direct),
.init(character: "", inputStyle: .direct),
@ -40,6 +30,17 @@ final class InputGraphTests: XCTestCase {
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(inputGraph.nodes.count, 5) // Root nodes
}
func testBuildSimpleDirectInput_たいか() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "", inputStyle: .direct),
.init(character: "", inputStyle: .direct),
.init(character: "", inputStyle: .direct)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(inputGraph.nodes.count, 5) // Root nodes
}
func testBuildSimpleRoman2KanaInput_1文字だけ() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "i", inputStyle: .roman2kana),
@ -47,12 +48,8 @@ final class InputGraphTests: XCTestCase {
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 1), correction: .none)
.init(character: "", inputElementsRange: .range(0, 1), correction: .none)
)
XCTAssertNil(
inputGraph.nodes.first(where: {$0.character == "i"})
)
XCTAssertEqual(inputGraph.nodes.count, 2) // Root nodes
}
func testBuildSimpleRoman2KanaInput_2文字_it() throws {
let correctGraph = CorrectGraph.build(input: [
@ -62,13 +59,12 @@ final class InputGraphTests: XCTestCase {
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 1), correction: .none)
.init(character: "", inputElementsRange: .range(0, 1), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "t"}),
.init(character: "t", displayedTextRange: .range(1, 2), inputElementsRange: .range(1, 2), correction: .none)
.init(character: "t", inputElementsRange: .range(1, 2), correction: .none)
)
print(inputGraph)
}
func testBuildSimpleRoman2KanaInput_3文字_ita() throws {
let correctGraph = CorrectGraph.build(input: [
@ -79,17 +75,12 @@ final class InputGraphTests: XCTestCase {
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 1), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "t"}),
.init(character: "t", displayedTextRange: .range(1, 2), inputElementsRange: .range(1, 2), correction: .none, isReplaced: true)
.init(character: "", inputElementsRange: .range(0, 1), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .range(1, 3), correction: .none)
.init(character: "", inputElementsRange: .range(1, 3), correction: .none)
)
print(inputGraph)
}
func testBuildSimpleRoman2KanaInput_4文字_sits() throws {
let correctGraph = CorrectGraph.build(input: [
@ -100,19 +91,17 @@ final class InputGraphTests: XCTestCase {
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "" && $0.displayedTextRange == .range(0, 1)}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 2), correction: .none)
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .range(0, 2), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "s"}),
.init(character: "s", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 1), correction: .none, isReplaced: true)
inputGraph.nodes.first(where: {$0.character == "t" && !$0.correction.isTypo}),
.init(character: "t", inputElementsRange: .range(2, 3), correction: .none)
)
// [s]displayedTextIndex
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .range(2, 4), correction: .typo)
.init(character: "", inputElementsRange: .range(2, 4), correction: .typo)
)
print(inputGraph)
}
func testBuildSimpleRoman2KanaInput_3文字_its() throws {
let correctGraph = CorrectGraph.build(input: [
@ -123,25 +112,24 @@ final class InputGraphTests: XCTestCase {
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 1), correction: .none)
.init(character: "", inputElementsRange: .range(0, 1), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "t" && $0.inputElementsRange == .range(1, 2)}),
.init(character: "t", displayedTextRange: .range(1, 2), inputElementsRange: .range(1, 2), correction: .none)
.init(character: "t", inputElementsRange: .range(1, 2), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "s"}),
.init(character: "s", displayedTextRange: .range(2, 3), inputElementsRange: .range(2, 3), correction: .none)
.init(character: "s", inputElementsRange: .range(2, 3), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "t" && $0.inputElementsRange == .startIndex(1)}),
.init(character: "t", displayedTextRange: .range(1, 2), inputElementsRange: .startIndex(1), groupId: 0, correction: .typo)
//
XCTAssertNil(
inputGraph.nodes.first(where: {$0.character == "t" && $0.inputElementsRange == .startIndex(1)})
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .range(1, 3), correction: .typo)
.init(character: "", inputElementsRange: .range(1, 3), correction: .typo)
)
print(inputGraph)
}
func testBuildSimpleRoman2KanaInput_4文字_itsa() throws {
let correctGraph = CorrectGraph.build(input: [
@ -153,36 +141,33 @@ final class InputGraphTests: XCTestCase {
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 1), correction: .none)
.init(character: "", inputElementsRange: .range(0, 1), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "t" && $0.inputElementsRange == .range(1, 2)}),
.init(character: "t", displayedTextRange: .range(1, 2), inputElementsRange: .range(1, 2), correction: .none, isReplaced: true)
XCTAssertNil(
inputGraph.nodes.first(where: {$0.character == "t" && $0.inputElementsRange == .range(1, 2)})
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "s"}),
.init(character: "s", displayedTextRange: .range(2, 3), inputElementsRange: .range(2, 3), correction: .none, isReplaced: true)
XCTAssertNil(
inputGraph.nodes.first(where: {$0.character == "s"})
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "t" && $0.inputElementsRange == .startIndex(1)}),
.init(character: "t", displayedTextRange: .range(1, 2), inputElementsRange: .startIndex(1), groupId: 0, correction: .typo)
XCTAssertNil(
inputGraph.nodes.first(where: {$0.character == "t" && $0.inputElementsRange == .startIndex(1)})
)
// groupId
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .range(1, 3), groupId: 1, correction: .typo)
.init(character: "", inputElementsRange: .range(1, 3), correction: .typo)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(2, 3), inputElementsRange: .range(3, 4), groupId: 1, correction: .none)
.init(character: "", inputElementsRange: .range(3, 4), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .startIndex(1), groupId: 2, correction: .none)
.init(character: "", inputElementsRange: .startIndex(1), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(2, 3), inputElementsRange: .endIndex(4), groupId: 2, correction: .none)
.init(character: "", inputElementsRange: .endIndex(4), correction: .none)
)
//
XCTAssertNil(inputGraph.nodes.first(where: {$0.character == ""}))
@ -201,28 +186,149 @@ final class InputGraphTests: XCTestCase {
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 2), correction: .none)
.init(character: "", inputElementsRange: .range(0, 2), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "" && $0.displayedTextRange.startIndex == 1}),
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .range(2, 3), correction: .none)
inputGraph.nodes.first(where: {$0.character == "" && $0.inputElementsRange == .range(2, 3)}),
.init(character: "", inputElementsRange: .range(2, 3), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(2, 3), inputElementsRange: .startIndex(3), groupId: 0, correction: .none)
.init(character: "", inputElementsRange: .startIndex(3), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(3, 4), inputElementsRange: .endIndex(6), groupId: 0,
correction: .none)
.init(character: "", inputElementsRange: .endIndex(6), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "" && $0.displayedTextRange.startIndex == 4}),
.init(character: "", displayedTextRange: .range(4, 5), inputElementsRange: .range(6, 7), correction: .none)
inputGraph.nodes.first(where: {$0.character == "" && $0.inputElementsRange == .range(6, 7)}),
.init(character: "", inputElementsRange: .range(6, 7), correction: .none)
)
}
func testBuildSimpleRoman2KanaInput_2文字_tt() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .startIndex(0), correction: .none)
)
XCTAssertNil(
inputGraph.nodes.first(where: {$0.character == "t" && $0.inputElementsRange == .range(0, 1)})
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "t" && $0.inputElementsRange == .endIndex(2)}),
.init(character: "t", inputElementsRange: .endIndex(2), correction: .none)
)
}
func testBuildSimpleRoman2KanaInput_3文字_tta() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .startIndex(0), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .endIndex(3), correction: .none)
)
}
func testBuildSimpleRoman2KanaInput_3文字_nta() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "n", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .startIndex(0), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .endIndex(3), correction: .none)
)
}
func testBuildSimpleRoman2KanaInput_4文字_itta() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .range(0, 1), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .startIndex(1), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .endIndex(4), correction: .none)
)
}
func testBuildSimpleRoman2KanaInput_5文字_sitsi() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "s", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .range(0, 2), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .range(2, 4), correction: .typo)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .range(4, 5), correction: .none)
)
}
func testBuildSimpleRoman2KanaInput_3文字_tts() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "" && $0.correction == .none}),
.init(character: "", inputElementsRange: .startIndex(0), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "" && $0.correction == .typo}),
.init(character: "", inputElementsRange: .startIndex(0), correction: .typo)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "s"}),
.init(character: "s", inputElementsRange: .range(2, 3), correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", inputElementsRange: .endIndex(3), correction: .typo)
)
print(inputGraph)
}
func testBuildMixedInput_2文字_ts() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
@ -231,121 +337,8 @@ final class InputGraphTests: XCTestCase {
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "t"}),
.init(character: "t", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 1), correction: .none)
.init(character: "t", inputElementsRange: .range(0, 1), correction: .none)
)
XCTAssertFalse(inputGraph.nodes.contains(.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 2), correction: .typo)))
XCTAssertFalse(inputGraph.nodes.contains(.init(character: "", inputElementsRange: .range(0, 2), correction: .typo)))
}
func testBuildMixedInput_2文字_tt() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .startIndex(0), groupId: 0, correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "t" && $0.groupId != nil}),
.init(character: "t", displayedTextRange: .range(1, 2), inputElementsRange: .endIndex(2), groupId: 0, correction: .none)
)
}
func testBuildMixedInput_3文字_tta() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .startIndex(0), groupId: 0, correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
// FIXME: groupId0
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .endIndex(3), groupId: nil, correction: .none)
)
}
func testBuildMixedInput_3文字_nta() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "n", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .startIndex(0), groupId: 0, correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .endIndex(3), groupId: nil, correction: .none)
)
}
func testBuildMixedInput_4文字_itta() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 1), groupId: nil, correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .startIndex(1), groupId: 0, correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
// FIXME: groupId0
.init(character: "", displayedTextRange: .range(2, 3), inputElementsRange: .endIndex(4), groupId: nil, correction: .none)
)
}
func testBuildMixedInput_5文字_sitsi() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "s", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .range(0, 2), groupId: nil, correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .range(2, 4), groupId: 1, correction: .typo)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
.init(character: "", displayedTextRange: .range(2, 3), inputElementsRange: .range(4, 5), groupId: 1, correction: .none)
)
}
func testBuildMixedInput_3文字_tts() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == "" && $0.correction == .none}),
.init(character: "", displayedTextRange: .range(0, 1), inputElementsRange: .startIndex(0), groupId: 2, correction: .none)
)
XCTAssertEqual(
inputGraph.nodes.first(where: {$0.character == ""}),
// FIXME: groupId0
.init(character: "", displayedTextRange: .range(1, 2), inputElementsRange: .range(1, 3), groupId: nil, correction: .typo)
)
}
}

View File

@ -9,37 +9,38 @@ import XCTest
import Foundation
@testable import KanaKanjiConverterModule
struct LookupGraph: InputGraphProtocol {
struct Node: Equatable, InputGraphNodeProtocol {
struct LookupGraph {
struct Node: Equatable {
var character: Character
var charId: UInt8
var loudsNodeIndices: Set<Int> = []
var displayedTextRange: InputGraphStructure.Range
var inputElementsRange: InputGraphStructure.Range
var correction: CorrectGraph.Correction = .none
var inputElementsRange: InputGraphRange
var correction: CorrectGraph2.Correction = .none
}
var nodes: [Node] = [
// root node
Node(character: "\0", charId: 0x00, displayedTextRange: .endIndex(0), inputElementsRange: .endIndex(0))
Node(character: "\0", charId: 0x00, inputElementsRange: .endIndex(0))
]
var structure: InputGraphStructure = InputGraphStructure()
/// NextIndex
var allowedNextIndex: [Int: IndexSet] = [:]
/// prevIndex
var allowedPrevIndex: [Int: IndexSet] = [:]
static func build(input: InputGraph, character2CharId: (Character) -> UInt8) -> Self {
let nodes = input.nodes.map {
Node(character: $0.character, charId: character2CharId($0.character), displayedTextRange: $0.displayedTextRange, inputElementsRange: $0.inputElementsRange, correction: $0.correction)
Node(character: $0.character, charId: character2CharId($0.character), inputElementsRange: $0.inputElementsRange, correction: $0.correction)
}
return Self(nodes: nodes, structure: input.structure)
return Self(nodes: nodes, allowedNextIndex: input.allowedNextIndex, allowedPrevIndex: input.allowedPrevIndex)
}
}
extension LOUDS {
func byfixNodeIndices(_ lookupGraph: LookupGraph, startGraphNodeIndex: Int = 0) -> (IndexSet, [Int: [(displayedTextEndIndex: Int?, inputElementsEndIndex: Int?)]]) {
func byfixNodeIndices(_ lookupGraph: LookupGraph, startGraphNodeIndex: Int = 0) -> (IndexSet, [Int: [Int?]]) {
var indexSet = IndexSet(integer: 1)
// loudsLookupGraph
var loudsNodeIndex2GraphNodeEndIndices: [Int: [(displayedTextEndIndex: Int?, inputElementsEndIndex: Int?)]] = [:]
var loudsNodeIndex2GraphNodeEndIndices: [Int: [Int?]] = [:]
typealias SearchItem = (
nodeIndex: Int,
lastLoudsNodeIndex: Int
@ -49,20 +50,13 @@ extension LOUDS {
let cNode = lookupGraph.nodes[cNodeIndex]
// nextNodes
if let loudsNodeIndex = self.searchCharNodeIndex(from: cLastLoudsNodeIndex, char: cNode.charId) {
loudsNodeIndex2GraphNodeEndIndices[loudsNodeIndex, default: []].append((cNode.displayedTextRange.endIndex, cNode.inputElementsRange.endIndex))
loudsNodeIndex2GraphNodeEndIndices[loudsNodeIndex, default: []].append(cNode.inputElementsRange.endIndex)
indexSet.insert(loudsNodeIndex)
var nextIndices = lookupGraph.nextIndices(for: cNode)
nextIndices.formUnion(IndexSet(lookupGraph.structure.allowedNextIndex[cNodeIndex, default: []]))
let nextIndices = lookupGraph.allowedNextIndex[cNodeIndex, default: IndexSet()]
stack.append(contentsOf: nextIndices.compactMap { index in
let node = lookupGraph.nodes[index]
// endIndex
// endIndex調
if let cDisplayedTextEndIndex = cNode.displayedTextRange.endIndex,
let nDisplayedTextEndIndex = node.displayedTextRange.endIndex {
guard cDisplayedTextEndIndex < nDisplayedTextEndIndex else {
return nil
}
}
if let cInputElementsEndIndex = cNode.inputElementsRange.endIndex,
let nInputElementsEndIndex = node.inputElementsRange.endIndex {
guard cInputElementsEndIndex < nInputElementsEndIndex else {
@ -77,12 +71,13 @@ extension LOUDS {
}
return (indexSet, loudsNodeIndex2GraphNodeEndIndices)
}
}
extension DicdataStore {
func buildConvertGraph(inputGraph: consuming InputGraph, option: ConvertRequestOptions) -> ConvertGraph {
let lookupGraph = LookupGraph.build(input: consume inputGraph, character2CharId: { self.character2charId($0.toKatakana()) } )
var stack: [Int] = Array(lookupGraph.nextIndices(for: lookupGraph.root) + lookupGraph.structure.allowedNextIndex[0, default: []])
var stack = Array(lookupGraph.allowedNextIndex[0, default: []])
var graphNodeIndex2LatticeNodes: [Int: [ConvertGraph.LatticeNode]] = [:]
while let graphNodeIndex = stack.popLast() {
let graphNode = lookupGraph.nodes[graphNodeIndex]
@ -94,22 +89,20 @@ extension DicdataStore {
var latticeNodes: [ConvertGraph.LatticeNode] = []
for (loudsNodeIndex, dicdata) in dicdataWithIndex {
for endIndex in loudsNodeIndex2GraphEndIndices[loudsNodeIndex, default: []] {
let displayedTextRange = InputGraphStructure.Range(startIndex: graphNode.displayedTextRange.startIndex, endIndex: endIndex.displayedTextEndIndex)
let inputElementsRange = InputGraphStructure.Range(startIndex: graphNode.inputElementsRange.startIndex, endIndex: endIndex.inputElementsEndIndex)
if graphNode.displayedTextRange.startIndex == 0 || graphNode.inputElementsRange.startIndex == 0 {
let inputElementsRange = InputGraphRange(startIndex: graphNode.inputElementsRange.startIndex, endIndex: endIndex)
if graphNode.inputElementsRange.startIndex == 0 {
latticeNodes.append(contentsOf: dicdata.map {
.init(data: $0, displayedTextRange: displayedTextRange, inputElementsRange: inputElementsRange, prevs: [.BOSNode()])
.init(data: $0, nextConvertNodeIndices: lookupGraph.allowedNextIndex[graphNodeIndex, default: []], inputElementsRange: inputElementsRange, prevs: [.BOSNode()])
})
} else {
latticeNodes.append(contentsOf: dicdata.map {
.init(data: $0, displayedTextRange: displayedTextRange, inputElementsRange: inputElementsRange)
.init(data: $0, nextConvertNodeIndices: lookupGraph.allowedNextIndex[graphNodeIndex, default: []], inputElementsRange: inputElementsRange)
})
}
}
}
graphNodeIndex2LatticeNodes[graphNodeIndex] = latticeNodes
stack.append(contentsOf: lookupGraph.nextIndices(for: graphNode))
stack.append(contentsOf: lookupGraph.structure.allowedNextIndex[graphNodeIndex, default: []])
stack.append(contentsOf: lookupGraph.allowedNextIndex[graphNodeIndex, default: []])
}
return ConvertGraph.build(input: consume lookupGraph, nodeIndex2LatticeNode: graphNodeIndex2LatticeNodes)
}
@ -151,7 +144,7 @@ final class LookupGraphTests: XCTestCase {
])
let inputGraph = InputGraph.build(input: correctGraph)
let lookupGraph = LookupGraph.build(input: inputGraph, character2CharId: values.character2CharId)
let startNodeIndex = lookupGraph.nextIndices(for: lookupGraph.root).first(where: { lookupGraph.nodes[$0].character == "" })
let startNodeIndex = lookupGraph.allowedNextIndex[0, default: IndexSet()].first(where: { lookupGraph.nodes[$0].character == "" })
XCTAssertNotNil(startNodeIndex)
let (loudsNodeIndices, _) = louds.byfixNodeIndices(lookupGraph, startGraphNodeIndex: startNodeIndex ?? 0)
let dicdataWithIndex = values.dicdataStore.getDicdataFromLoudstxt3(identifier: "", indices: loudsNodeIndices, option: requestOptions())
@ -179,6 +172,38 @@ final class LookupGraphTests: XCTestCase {
)
}
func testByfixNodeIndices_みらい() throws {
let values = setup()
guard let louds = LOUDS.load("", option: requestOptions()) else {
XCTFail()
return
}
let correctGraph = CorrectGraph.build(input: [
.init(character: "", inputStyle: .direct),
.init(character: "", inputStyle: .direct),
.init(character: "", inputStyle: .direct),
])
let inputGraph = InputGraph.build(input: correctGraph)
let lookupGraph = LookupGraph.build(input: inputGraph, character2CharId: values.character2CharId)
let startNodeIndex = lookupGraph.allowedNextIndex[0, default: IndexSet()].first(where: { lookupGraph.nodes[$0].character == "" })
XCTAssertNotNil(startNodeIndex)
let (loudsNodeIndices, _) = louds.byfixNodeIndices(lookupGraph, startGraphNodeIndex: startNodeIndex ?? 0)
let dicdataWithIndex = values.dicdataStore.getDicdataFromLoudstxt3(identifier: "", indices: loudsNodeIndices, option: requestOptions())
let dicdata = dicdataWithIndex.flatMapSet { $0.dicdata }
//
XCTAssertTrue(dicdata.contains {$0.word == ""})
//
XCTAssertTrue(dicdata.contains {$0.word == "ミラ"})
//
XCTAssertTrue(dicdata.contains {$0.word == "未来"})
// all keys
XCTAssertEqual(
dicdata.mapSet {$0.ruby}.symmetricDifference(["", "ミラ", "ミライ"]),
[]
)
}
func testByfixNodeIndices_たいかく() throws {
let values = setup()
guard let louds = LOUDS.load("", option: requestOptions()) else {
@ -193,7 +218,7 @@ final class LookupGraphTests: XCTestCase {
])
let inputGraph = InputGraph.build(input: correctGraph)
let lookupGraph = LookupGraph.build(input: inputGraph, character2CharId: values.character2CharId)
let startNodeIndex = lookupGraph.nextIndices(for: lookupGraph.root).first(where: { lookupGraph.nodes[$0].character == "" })
let startNodeIndex = lookupGraph.allowedNextIndex[0, default: IndexSet()].first(where: { lookupGraph.nodes[$0].character == "" })
XCTAssertNotNil(startNodeIndex)
let (loudsNodeIndices, _) = louds.byfixNodeIndices(lookupGraph, startGraphNodeIndex: startNodeIndex ?? 0)
let dicdataWithIndex = values.dicdataStore.getDicdataFromLoudstxt3(identifier: "", indices: loudsNodeIndices, option: requestOptions())
@ -235,7 +260,7 @@ final class LookupGraphTests: XCTestCase {
])
let inputGraph = InputGraph.build(input: correctGraph)
let lookupGraph = LookupGraph.build(input: inputGraph, character2CharId: values.character2CharId)
let startNodeIndex = lookupGraph.nextIndices(for: lookupGraph.root).first(where: { lookupGraph.nodes[$0].character == "" })
let startNodeIndex = lookupGraph.allowedNextIndex[0, default: IndexSet()].first(where: { lookupGraph.nodes[$0].character == "" })
XCTAssertNotNil(startNodeIndex)
let (loudsNodeIndices, _) = louds.byfixNodeIndices(lookupGraph, startGraphNodeIndex: startNodeIndex ?? 0)
let dicdataWithIndex = values.dicdataStore.getDicdataFromLoudstxt3(identifier: "", indices: loudsNodeIndices, option: requestOptions())
@ -272,7 +297,7 @@ final class LookupGraphTests: XCTestCase {
])
let inputGraph = InputGraph.build(input: correctGraph)
let lookupGraph = LookupGraph.build(input: inputGraph, character2CharId: values.character2CharId)
let startNodeIndex = lookupGraph.nextIndices(for: lookupGraph.root).first(where: { lookupGraph.nodes[$0].character == "" })
let startNodeIndex = lookupGraph.allowedNextIndex[0, default: IndexSet()].first(where: { lookupGraph.nodes[$0].character == "" })
XCTAssertNotNil(startNodeIndex)
let (loudsNodeIndices, _) = louds.byfixNodeIndices(lookupGraph, startGraphNodeIndex: startNodeIndex ?? 0)
let dicdataWithIndex = values.dicdataStore.getDicdataFromLoudstxt3(identifier: "", indices: loudsNodeIndices, option: requestOptions())

View File

@ -12,7 +12,7 @@ import XCTest
// prefix tree
enum ReplacePrefixTree {
static var characterNodes: [InputGraph.InputStyle.ID: [Character: [Node]]] = [:]
static var characterNodes: [InputGraphInputStyle.ID: [Character: [Node]]] = [:]
final class Node {
init(_ children: [Character: Node] = [:], character: Character = "\0", value: String? = nil, parent: Node? = nil) {
@ -28,7 +28,7 @@ enum ReplacePrefixTree {
func find(key: Character) -> Node? {
return children[key]
}
func insert(route: some Collection<Character>, value: consuming String, inputStyle: InputGraph.InputStyle.ID) {
func insert(route: some Collection<Character>, value: consuming String, inputStyle: InputGraphInputStyle.ID) {
if let first = route.first {
if let tree = self.children[first] {
tree.insert(route: route.dropFirst(), value: consume value, inputStyle: inputStyle)
@ -77,7 +77,7 @@ enum ReplaceSuffixTree {
func find(key: Character) -> Node? {
return children[key]
}
func insert(route: some Collection<Character>, value: consuming String, inputStyle: InputGraph.InputStyle.ID) {
func insert(route: some Collection<Character>, value: consuming String, inputStyle: InputGraphInputStyle.ID) {
if let first = route.first {
if let tree = self.children[first] {
tree.insert(route: route.dropFirst(), value: consume value, inputStyle: inputStyle)

View File

@ -62,6 +62,39 @@ final class ExperimentalConversionTests: XCTestCase {
)
}
func testConversion_たい() throws {
let dicdataStore = DicdataStore(requestOptions: requestOptions())
let kana2kanji = Kana2Kanji(dicdataStore: dicdataStore)
var c = ComposingText()
c.insertAtCursorPosition("たい", inputStyle: .direct)
let result = kana2kanji._experimental_all(c, option: requestOptions())
XCTAssertTrue(result.joinedPrevs().contains("タイ")) //
XCTAssertTrue(result.joinedPrevs().contains("")) //
}
func testConversion_いか() throws {
let dicdataStore = DicdataStore(requestOptions: requestOptions())
let kana2kanji = Kana2Kanji(dicdataStore: dicdataStore)
var c = ComposingText()
c.insertAtCursorPosition("いか", inputStyle: .direct)
let result = kana2kanji._experimental_all(c, option: requestOptions())
XCTAssertTrue(result.joinedPrevs().contains("以下")) //
XCTAssertTrue(result.joinedPrevs().contains("伊賀")) //
print(result.joinedPrevs())
}
func testConversion_たいか() throws {
let dicdataStore = DicdataStore(requestOptions: requestOptions())
let kana2kanji = Kana2Kanji(dicdataStore: dicdataStore)
var c = ComposingText()
c.insertAtCursorPosition("たいか", inputStyle: .direct)
let result = kana2kanji._experimental_all(c, option: requestOptions())
XCTAssertTrue(result.joinedPrevs().contains("対価")) //
XCTAssertTrue(result.joinedPrevs().contains("大河")) //
// FIXME:
print(result.joinedPrevs())
}
func testConversion_たいかく() throws {
let dicdataStore = DicdataStore(requestOptions: requestOptions())
let kana2kanji = Kana2Kanji(dicdataStore: dicdataStore)
@ -106,7 +139,7 @@ final class ExperimentalConversionTests: XCTestCase {
XCTAssertTrue(result.joinedPrevs().contains("幼少期")) //
}
func testConversion() throws {
func testConversion_みらいえいが() throws {
let dicdataStore = DicdataStore(requestOptions: requestOptions())
let kana2kanji = Kana2Kanji(dicdataStore: dicdataStore)
do {
@ -121,6 +154,11 @@ final class ExperimentalConversionTests: XCTestCase {
let result = kana2kanji._experimental_all(c, option: requestOptions())
XCTAssertTrue(result.joinedPrevs().contains("未来映画"))
}
}
func testConversion() throws {
let dicdataStore = DicdataStore(requestOptions: requestOptions())
let kana2kanji = Kana2Kanji(dicdataStore: dicdataStore)
do {
var c = ComposingText()
c.insertAtCursorPosition("sitta", inputStyle: .roman2kana)