Remove unused impl and apply lint

This commit is contained in:
Miwa / Ensan
2024-03-17 10:38:45 +09:00
parent d33884fd31
commit f4e48610a3
9 changed files with 59 additions and 292 deletions

View File

@ -13,7 +13,7 @@ struct ConvertGraph {
struct Node {
var latticeNodes: [LatticeNode]
var inputElementsRange: InputGraphRange
var correction: CorrectGraph2.Correction = .none
var correction: CorrectGraph.Correction = .none
}
var nodes: [Node] = [

View File

@ -10,6 +10,24 @@ import Foundation
import XCTest
struct CorrectGraph {
enum Correction: CustomStringConvertible {
///
case none
///
case typo
var isTypo: Bool {
self == .typo
}
var description: String {
switch self {
case .none: "none"
case .typo: "typo"
}
}
}
var nodes: [Node] = [
// BOS
.init(inputElementsRange: .endIndex(0), inputStyle: .all, correction: .none, value: "\0")
@ -22,7 +40,7 @@ struct CorrectGraph {
struct Node: Equatable, Sendable {
var inputElementsRange: InputGraphRange
var inputStyle: InputGraphInputStyle.ID
var correction: CorrectGraph2.Correction
var correction: CorrectGraph.Correction
var value: Character
}
@ -94,7 +112,7 @@ struct CorrectGraph {
inputStyleId: InputGraphInputStyle.ID
)
var stack: [SearchItem] = [
(correctPrefixTree, index, [], .all),
(correctPrefixTree, index, [], .all)
]
while let (cNode, cIndex, cRoute, cInputStyleId) = stack.popLast() {
guard cIndex < input.endIndex else {
@ -114,7 +132,7 @@ struct CorrectGraph {
values: Array(value),
startIndex: index,
endIndex: index + cRoute.count + 1,
inputStyle: inputStyleId,
inputStyle: inputStyleId,
lastIndexSet: inputIndexToEndNodeIndices[index, default: IndexSet()]
)
inputIndexToEndNodeIndices[index + cRoute.count + 1, default: IndexSet()].insert(nodeIndex)
@ -138,179 +156,6 @@ struct CorrectGraph {
}
}
struct CorrectGraph2 {
var nodes: [Node] = []
/// NextIndex
var allowedNextIndex: [Int: Int] = [:]
/// prevIndex
var allowedPrevIndex: [Int: Int] = [:]
var inputElementsStartIndexToNodeIndices: [IndexSet] = []
var inputElementsEndIndexToNodeIndices: [IndexSet] = []
var groupIdIota: Iota = Iota()
func prevIndices(for nodeIndex: Int) -> IndexSet {
var indexSet = IndexSet()
if let startIndex = self.nodes[nodeIndex].inputElementsRange.startIndex,
startIndex < self.inputElementsEndIndexToNodeIndices.endIndex {
indexSet.formUnion(self.inputElementsEndIndexToNodeIndices[startIndex])
}
if let value = allowedPrevIndex[nodeIndex] {
indexSet.insert(value)
}
return indexSet
}
func nextIndices(for nodeIndex: Int) -> IndexSet {
var indexSet = IndexSet()
if let endIndex = self.nodes[nodeIndex].inputElementsRange.endIndex,
endIndex < self.inputElementsStartIndexToNodeIndices.endIndex {
indexSet.formUnion(self.inputElementsStartIndexToNodeIndices[endIndex])
}
if let value = allowedNextIndex[nodeIndex] {
indexSet.insert(value)
}
return indexSet
}
struct Node: Equatable, Sendable {
var inputElementsRange: InputGraphRange
var inputStyle: InputGraphInputStyle.ID
var correction: Correction
var value: Character
var groupId: Int?
}
enum Correction: CustomStringConvertible {
///
case none
///
case typo
var isTypo: Bool {
self == .typo
}
var description: String {
switch self {
case .none: "none"
case .typo: "typo"
}
}
}
@discardableResult
mutating func insert(_ node: consuming Node) -> Int {
let index = nodes.count
if let startIndex = node.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)
}
if let endIndex = node.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)
}
self.nodes.append(consume node)
return index
}
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: 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,
groupId: id
)
let index = self.insert(node)
indices.append(index)
}
// connect
for i in indices.indices.dropLast() {
self.allowedNextIndex[indices[i]] = indices[i+1]
self.allowedPrevIndex[indices[i+1]] = indices[i]
}
}
static func build(input: [ComposingText.InputElement]) -> Self {
var correctGraph = Self()
for (index, item) in zip(input.indices, input) {
correctGraph.insert(
Node(
inputElementsRange: .range(index, index + 1),
inputStyle: InputGraphInputStyle(from: input[index].inputStyle).id,
correction: .none,
value: item.character
)
)
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 {
correctGraph.insertConnectedTypoNodes(
values: Array(value),
startIndex: index,
endIndex: index + cRoute.count + 1,
inputStyle: inputStyleId
)
} else {
correctGraph.insert(
Node(
inputElementsRange: .range(index, index + cRoute.count + 1),
inputStyle: inputStyleId,
correction: .typo,
value: value.first!
)
)
}
}
}
}
}
return correctGraph
}
}
final class CorrectGraphTests: XCTestCase {
func testBuildSimpleDirectInput() throws {
let graph = CorrectGraph.build(input: [
@ -338,7 +183,7 @@ final class CorrectGraphTests: XCTestCase {
let graph = CorrectGraph.build(input: [
.init(character: "", inputStyle: .direct),
.init(character: "", inputStyle: .direct),
.init(character: "", inputStyle: .direct),
.init(character: "", inputStyle: .direct)
])
XCTAssertEqual(
graph.nodes.first(where: {$0.value == ""}),

View File

@ -53,7 +53,7 @@ enum CorrectPrefixTree {
"k": .terminal(["gi"]),
"l": .terminal(["go"]),
"p": .terminal(["go"]),
"j": .terminal(["gu"]),
"j": .terminal(["gu"])
]),
"m": Node([
"s": .terminal(["ma"]),
@ -64,7 +64,7 @@ enum CorrectPrefixTree {
"k": .terminal(["mi"]),
"l": .terminal(["mo"]),
"p": .terminal(["mo"]),
"j": .terminal(["mu"]),
"j": .terminal(["mu"])
]),
"t": Node([
"s": .terminal(["ta"]),
@ -75,8 +75,8 @@ enum CorrectPrefixTree {
"k": .terminal(["ti"]),
"l": .terminal(["to"]),
"p": .terminal(["to"]),
"j": .terminal(["tu"]),
]),
"j": .terminal(["tu"])
])
])
}()
static let direct: Node = {

View File

@ -11,12 +11,11 @@ import DequeModule
@testable import KanaKanjiConverterModule
import XCTest
struct InputGraph {
struct Node: Equatable, CustomStringConvertible {
var character: Character
var inputElementsRange: InputGraphRange
var correction: CorrectGraph2.Correction = .none
var correction: CorrectGraph.Correction = .none
var description: String {
let `is` = inputElementsRange.startIndex?.description ?? "?"
@ -36,7 +35,6 @@ struct InputGraph {
/// correctGraph
var nextCorrectNodeIndices: [Int: IndexSet] = [:]
mutating func update(_ correctGraph: CorrectGraph, nodeIndex: Int) {
let cgNode = correctGraph.nodes[nodeIndex]
//
@ -74,7 +72,7 @@ struct InputGraph {
route: [Int],
//
foundValue: Replacement?,
correction: CorrectGraph2.Correction
correction: CorrectGraph.Correction
)
typealias Match = (
//
@ -126,7 +124,7 @@ struct InputGraph {
let endIndex = self.nodes[replacement.route[replacement.route.endIndex - 1]].inputElementsRange.endIndex
let characters = Array(replacement.value)
let correction: CorrectGraph2.Correction = if replacement.route.allSatisfy({!self.nodes[$0].correction.isTypo}) {
let correction: CorrectGraph.Correction = if replacement.route.allSatisfy({!self.nodes[$0].correction.isTypo}) {
.none
} else {
.typo

View File

@ -41,7 +41,6 @@ enum InputGraphRange: Equatable, Sendable {
}
}
struct InputGraphInputStyle: Identifiable {
init(from deprecatedInputStyle: KanaKanjiConverterModule.InputStyle) {
switch deprecatedInputStyle {
@ -52,9 +51,9 @@ struct InputGraphInputStyle: Identifiable {
}
}
init(id: InputGraphInputStyle.ID, replacePrefixTree: ReplacePrefixTree.Node, correctPrefixTree: CorrectPrefixTree.Node) {
init(id: InputGraphInputStyle.ID, replaceSuffixTree: ReplaceSuffixTree.Node, correctPrefixTree: CorrectPrefixTree.Node) {
self.id = id
self.replacePrefixTree = replacePrefixTree
self.replaceSuffixTree = replaceSuffixTree
self.correctPrefixTree = correctPrefixTree
}
@ -88,23 +87,23 @@ struct InputGraphInputStyle: Identifiable {
}
static let all: Self = Self(
id: .all,
replacePrefixTree: ReplacePrefixTree.Node(),
replaceSuffixTree: ReplaceSuffixTree.Node(),
correctPrefixTree: CorrectPrefixTree.Node()
)
static let systemFlickDirect: Self = Self(
id: .systemFlickDirect,
replacePrefixTree: ReplacePrefixTree.direct,
replaceSuffixTree: ReplaceSuffixTree.direct,
correctPrefixTree: CorrectPrefixTree.direct
)
static let systemRomanKana: Self = Self(
id: .systemRomanKana,
replacePrefixTree: ReplacePrefixTree.roman2kana,
replaceSuffixTree: ReplaceSuffixTree.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 replaceSuffixTree: ReplaceSuffixTree.Node
var correctPrefixTree: CorrectPrefixTree.Node
}

View File

@ -10,7 +10,6 @@ import Foundation
@testable import KanaKanjiConverterModule
import XCTest
final class InputGraphTests: XCTestCase {
func testBuildSimpleDirectInput() throws {
let correctGraph = CorrectGraph.build(input: [
@ -43,7 +42,7 @@ final class InputGraphTests: XCTestCase {
func testBuildSimpleRoman2KanaInput_1文字だけ() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "i", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -54,7 +53,7 @@ final class InputGraphTests: XCTestCase {
func testBuildSimpleRoman2KanaInput_2文字_it() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -70,7 +69,7 @@ final class InputGraphTests: XCTestCase {
let correctGraph = CorrectGraph.build(input: [
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -87,7 +86,7 @@ final class InputGraphTests: XCTestCase {
.init(character: "s", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -107,7 +106,7 @@ final class InputGraphTests: XCTestCase {
let correctGraph = CorrectGraph.build(input: [
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -136,7 +135,7 @@ final class InputGraphTests: XCTestCase {
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -181,7 +180,7 @@ final class InputGraphTests: XCTestCase {
.init(character: "s", inputStyle: .roman2kana),
.init(character: "h", inputStyle: .roman2kana),
.init(character: "o", inputStyle: .roman2kana),
.init(character: "u", inputStyle: .roman2kana),
.init(character: "u", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -210,7 +209,7 @@ final class InputGraphTests: XCTestCase {
func testBuildSimpleRoman2KanaInput_2文字_tt() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -229,7 +228,7 @@ final class InputGraphTests: XCTestCase {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -245,7 +244,7 @@ final class InputGraphTests: XCTestCase {
let correctGraph = CorrectGraph.build(input: [
.init(character: "n", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -262,7 +261,7 @@ final class InputGraphTests: XCTestCase {
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -285,7 +284,7 @@ final class InputGraphTests: XCTestCase {
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -307,7 +306,7 @@ final class InputGraphTests: XCTestCase {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(
@ -332,7 +331,7 @@ final class InputGraphTests: XCTestCase {
func testBuildMixedInput_2文字_ts() throws {
let correctGraph = CorrectGraph.build(input: [
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .direct),
.init(character: "s", inputStyle: .direct)
])
let inputGraph = InputGraph.build(input: correctGraph)
XCTAssertEqual(

View File

@ -1,21 +0,0 @@
//
// Iota.swift
//
//
// Created by miwa on 2024/02/25.
//
import Foundation
struct Iota: CustomStringConvertible {
private var next: Int = 0
mutating func new() -> Int {
let result = next
next += 1
return result
}
var description: String {
"Iota()"
}
}

View File

@ -15,7 +15,7 @@ struct LookupGraph {
var charId: UInt8
var loudsNodeIndices: Set<Int> = []
var inputElementsRange: InputGraphRange
var correction: CorrectGraph2.Correction = .none
var correction: CorrectGraph.Correction = .none
}
var nodes: [Node] = [
@ -35,7 +35,6 @@ struct LookupGraph {
}
}
extension LOUDS {
func byfixNodeIndices(_ lookupGraph: LookupGraph, startGraphNodeIndex: Int = 0) -> (IndexSet, [Int: [Int]]) {
var indexSet = IndexSet(integer: 1)
@ -76,7 +75,7 @@ extension LOUDS {
extension DicdataStore {
func buildConvertGraph(inputGraph: consuming InputGraph, option: ConvertRequestOptions) -> ConvertGraph {
let lookupGraph = LookupGraph.build(input: consume inputGraph, character2CharId: { self.character2charId($0.toKatakana()) } )
let lookupGraph = LookupGraph.build(input: consume inputGraph, character2CharId: { self.character2charId($0.toKatakana()) })
var stack = Array(lookupGraph.allowedNextIndex[0, default: []])
var graphNodeIndex2LatticeNodes: [Int: [ConvertGraph.LatticeNode]] = [:]
while let graphNodeIndex = stack.popLast() {
@ -143,7 +142,7 @@ final class LookupGraphTests: XCTestCase {
let correctGraph = CorrectGraph.build(input: [
.init(character: "", inputStyle: .direct),
.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)
@ -184,7 +183,7 @@ final class LookupGraphTests: XCTestCase {
let correctGraph = CorrectGraph.build(input: [
.init(character: "", inputStyle: .direct),
.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)
@ -217,7 +216,7 @@ final class LookupGraphTests: XCTestCase {
.init(character: "", inputStyle: .direct),
.init(character: "", inputStyle: .direct),
.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)
@ -259,7 +258,7 @@ final class LookupGraphTests: XCTestCase {
.init(character: "t", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "a", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
let lookupGraph = LookupGraph.build(input: inputGraph, character2CharId: values.character2CharId)
@ -296,7 +295,7 @@ final class LookupGraphTests: XCTestCase {
.init(character: "i", inputStyle: .roman2kana),
.init(character: "t", inputStyle: .roman2kana),
.init(character: "s", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana),
.init(character: "i", inputStyle: .roman2kana)
])
let inputGraph = InputGraph.build(input: correctGraph)
let lookupGraph = LookupGraph.build(input: inputGraph, character2CharId: values.character2CharId)

View File

@ -1,5 +1,5 @@
//
// ReplacePrefixTree.swift
// ReplaceSuffixTree.swift
//
//
// Created by miwa on 2024/02/23.
@ -10,59 +10,7 @@ import Foundation
@testable import KanaKanjiConverterModule
import XCTest
// prefix tree
enum ReplacePrefixTree {
static var characterNodes: [InputGraphInputStyle.ID: [Character: [Node]]] = [:]
final class Node {
init(_ children: [Character: Node] = [:], character: Character = "\0", value: String? = nil, parent: Node? = nil) {
self.children = children
self.value = value
self.character = character
self.parent = parent
}
var parent: Node?
var children: [Character: Node] = [:]
var character: Character
var value: String?
func find(key: Character) -> Node? {
return children[key]
}
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)
} else {
let tree = Node(character: first, parent: self)
tree.insert(route: route.dropFirst(), value: consume value, inputStyle: inputStyle)
self.children[first] = tree
ReplacePrefixTree.characterNodes[inputStyle, default: [:]][first, default: []].append(tree)
}
} else {
self.value = consume value
}
}
}
static let roman2kana: Node = {
var tree = Node()
for item in KanaKanjiConverterModule.Roman2Kana.hiraganaChanges {
tree.insert(route: item.key, value: String(item.value), inputStyle: .systemRomanKana)
}
// additionals
for item in ["bb", "cc", "dd", "ff", "gg", "hh", "jj", "kk", "ll", "mm", "pp", "qq", "rr", "ss", "tt", "vv", "ww", "xx", "yy", "zz"] {
tree.insert(route: Array(item), value: "" + String(item.last!), inputStyle: .systemRomanKana)
}
// additionals
for item in ["nb", "nc", "nd", "nf", "ng", "nh", "nj", "nk", "nl", "nm", "np", "nq", "nr", "ns", "nt", "nv", "nw", "nx", "nz"] {
tree.insert(route: Array(item), value: "" + String(item.last!), inputStyle: .systemRomanKana)
}
return tree
}()
static let direct: Node = Node()
}
// prefix tree
// suffix tree
enum ReplaceSuffixTree {
final class Node {