diff --git a/Packages/vChewing_Megrez/Sources/Megrez/1_Compositor.swift b/Packages/vChewing_Megrez/Sources/Megrez/1_Compositor.swift index c1e6501a..465ae409 100644 --- a/Packages/vChewing_Megrez/Sources/Megrez/1_Compositor.swift +++ b/Packages/vChewing_Megrez/Sources/Megrez/1_Compositor.swift @@ -21,7 +21,7 @@ extension Megrez { /// 該軌格內可以允許的最大幅位長度。 public static var maxSpanLength: Int = 10 { didSet { maxSpanLength = max(6, maxSpanLength) } } /// 公開:多字讀音鍵當中用以分割漢字讀音的記號的預設值,是「-」。 - public static let kDefaultSeparator: String = "-" + public static var theSeparator: String = "-" /// 該組字器的游標位置。 public var cursor: Int = 0 { didSet { @@ -33,7 +33,12 @@ extension Megrez { /// 該組字器的標記器位置。 public var marker: Int = 0 { didSet { marker = max(0, min(marker, length)) } } /// 公開:多字讀音鍵當中用以分割漢字讀音的記號,預設為「-」。 - public var separator = kDefaultSeparator + public var separator = theSeparator { + didSet { + Self.theSeparator = separator + } + } + /// 公開:組字器內已經插入的單筆索引鍵的數量。 public var width: Int { keys.count } /// 公開:最近一次爬軌結果。 @@ -71,7 +76,7 @@ extension Megrez { /// - Parameter key: 要插入的索引鍵。 /// - Returns: 該操作是否成功執行。 @discardableResult public mutating func insertKey(_ key: String) -> Bool { - guard !key.isEmpty, key != separator, langModel.hasUnigramsFor(key: key) else { return false } + guard !key.isEmpty, key != separator, langModel.hasUnigramsFor(keyArray: [key]) else { return false } keys.insert(key, at: cursor) let gridBackup = spans resizeGrid(at: cursor, do: .expand) @@ -242,7 +247,7 @@ extension Megrez.Compositor { return true } - func getJointKeyArray(range: Range) -> [String] { + func getJoinedKeyArray(range: Range) -> [String] { // 下面這句不能用 contains,不然會要求至少 macOS 13 Ventura。 guard range.upperBound <= keys.count, range.lowerBound >= 0 else { return [] } return keys[range].map { String($0) } @@ -262,11 +267,10 @@ extension Megrez.Compositor { var nodesChanged = 0 for position in range { for theLength in 1...min(maxSpanLength, range.upperBound - position) { - let jointKeyArray = getJointKeyArray(range: position..<(position + theLength)) - let jointKey = jointKeyArray.joined(separator: separator) - if let theNode = getNode(at: position, length: theLength, keyArray: jointKeyArray) { + let joinedKeyArray = getJoinedKeyArray(range: position..<(position + theLength)) + if let theNode = getNode(at: position, length: theLength, keyArray: joinedKeyArray) { if !updateExisting { continue } - let unigrams = langModel.unigramsFor(key: jointKey) + let unigrams = langModel.unigramsFor(keyArray: joinedKeyArray) // 自動銷毀無效的節點。 if unigrams.isEmpty { if theNode.keyArray.count == 1 { continue } @@ -277,10 +281,10 @@ extension Megrez.Compositor { nodesChanged += 1 continue } - let unigrams = langModel.unigramsFor(key: jointKey) + let unigrams = langModel.unigramsFor(keyArray: joinedKeyArray) guard !unigrams.isEmpty else { continue } insertNode( - .init(keyArray: jointKeyArray, spanLength: theLength, unigrams: unigrams, keySeparator: separator), + .init(keyArray: joinedKeyArray, spanLength: theLength, unigrams: unigrams), at: position ) nodesChanged += 1 diff --git a/Packages/vChewing_Megrez/Sources/Megrez/2_Walker.swift b/Packages/vChewing_Megrez/Sources/Megrez/2_Walker.swift index 385f837a..ee755c79 100644 --- a/Packages/vChewing_Megrez/Sources/Megrez/2_Walker.swift +++ b/Packages/vChewing_Megrez/Sources/Megrez/2_Walker.swift @@ -32,7 +32,7 @@ extension Megrez.Compositor { } } - let terminal = Vertex(node: .init(keyArray: ["_TERMINAL_"], keySeparator: separator)) + let terminal = Vertex(node: .init(keyArray: ["_TERMINAL_"])) for (i, vertexSpan) in vertexSpans.enumerated() { for vertex in vertexSpan { @@ -47,7 +47,7 @@ extension Megrez.Compositor { } } - let root = Vertex(node: .init(keyArray: ["_ROOT_"], keySeparator: separator)) + let root = Vertex(node: .init(keyArray: ["_ROOT_"])) root.distance = 0 root.edges.append(contentsOf: vertexSpans[0]) diff --git a/Packages/vChewing_Megrez/Sources/Megrez/3_KeyValuePaired.swift b/Packages/vChewing_Megrez/Sources/Megrez/3_KeyValuePaired.swift index 54a3223f..f9db2329 100644 --- a/Packages/vChewing_Megrez/Sources/Megrez/3_KeyValuePaired.swift +++ b/Packages/vChewing_Megrez/Sources/Megrez/3_KeyValuePaired.swift @@ -7,49 +7,66 @@ import Foundation extension Megrez.Compositor { public struct KeyValuePaired: Equatable, Hashable, Comparable, CustomStringConvertible { - /// 鍵。一般情況下用來放置讀音等可以用來作為索引的內容。 - public var key: String + /// 鍵陣列。一般情況下用來放置讀音等可以用來作為索引的內容。 + public var keyArray: [String] /// 資料值。 public var value: String /// 將當前鍵值列印成一個字串。 - public var description: String { "(" + key + "," + value + ")" } + public var description: String { "(" + keyArray.description + "," + value + ")" } /// 判斷當前鍵值配對是否合規。如果鍵與值有任一為空,則結果為 false。 - public var isValid: Bool { !key.isEmpty && !value.isEmpty } + public var isValid: Bool { !keyArray.joined().isEmpty && !value.isEmpty } /// 將當前鍵值列印成一個字串,但如果該鍵值配對為空的話則僅列印「()」。 - public var toNGramKey: String { !isValid ? "()" : "(" + key + "," + value + ")" } + public var toNGramKey: String { !isValid ? "()" : "(" + joinedKey() + "," + value + ")" } + + /// 初期化一組鍵值配對。 + /// - Parameters: + /// - key: 鍵陣列。一般情況下用來放置讀音等可以用來作為索引的內容。 + /// - value: 資料值。 + public init(keyArray: [String], value: String = "N/A") { + self.keyArray = keyArray.isEmpty ? ["N/A"] : keyArray + self.value = value.isEmpty ? "N/A" : value + } /// 初期化一組鍵值配對。 /// - Parameters: /// - key: 鍵。一般情況下用來放置讀音等可以用來作為索引的內容。 /// - value: 資料值。 - public init(key: String = "", value: String = "") { - self.key = key - self.value = value + public init(key: String = "N/A", value: String = "N/A") { + keyArray = key.isEmpty ? ["N/A"] : key.components(separatedBy: Megrez.Compositor.theSeparator) + self.value = value.isEmpty ? "N/A" : value } public func hash(into hasher: inout Hasher) { - hasher.combine(key) + hasher.combine(keyArray) hasher.combine(value) } + public func joinedKey(by separator: String = Megrez.Compositor.theSeparator) -> String { + keyArray.joined(separator: separator) + } + public static func == (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { - lhs.key == rhs.key && lhs.value == rhs.value + lhs.keyArray == rhs.keyArray && lhs.value == rhs.value } public static func < (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { - (lhs.key.count < rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value < rhs.value) + (lhs.keyArray.joined().count < rhs.keyArray.joined().count) + || (lhs.keyArray.joined().count == rhs.keyArray.joined().count && lhs.value < rhs.value) } public static func > (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { - (lhs.key.count > rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value > rhs.value) + (lhs.keyArray.joined().count > rhs.keyArray.joined().count) + || (lhs.keyArray.joined().count == rhs.keyArray.joined().count && lhs.value > rhs.value) } public static func <= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { - (lhs.key.count <= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value <= rhs.value) + (lhs.keyArray.joined().count <= rhs.keyArray.joined().count) + || (lhs.keyArray.joined().count == rhs.keyArray.joined().count && lhs.value <= rhs.value) } public static func >= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { - (lhs.key.count >= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value >= rhs.value) + (lhs.keyArray.joined().count >= rhs.keyArray.joined().count) + || (lhs.keyArray.joined().count == rhs.keyArray.joined().count && lhs.value >= rhs.value) } } @@ -70,7 +87,7 @@ extension Megrez.Compositor { } let keyAtCursor = keys[location] for theNode in anchors.map(\.node) { - if theNode.key.isEmpty { continue } + if theNode.keyArray.joined(separator: separator).isEmpty { continue } for gram in theNode.unigrams { switch filter { case .all: @@ -81,7 +98,7 @@ extension Megrez.Compositor { case .endAt: if theNode.keyArray.reversed()[0] != keyAtCursor { continue } } - result.append(.init(key: theNode.key, value: gram.value)) + result.append(.init(keyArray: theNode.keyArray, value: gram.value)) } } return result @@ -100,7 +117,7 @@ extension Megrez.Compositor { ) -> Bool { - overrideCandidateAgainst(key: candidate.key, at: location, value: candidate.value, type: overrideType) + overrideCandidateAgainst(keyArray: candidate.keyArray, at: location, value: candidate.value, type: overrideType) } /// 使用給定的候選字詞字串,將給定位置的節點的候選字詞改為與之一致的候選字詞。 @@ -115,7 +132,7 @@ extension Megrez.Compositor { _ candidate: String, at location: Int, overrideType: Node.OverrideType = .withHighScore ) -> Bool { - overrideCandidateAgainst(key: nil, at: location, value: candidate, type: overrideType) + overrideCandidateAgainst(keyArray: nil, at: location, value: candidate, type: overrideType) } // MARK: Internal implementations. @@ -127,14 +144,18 @@ extension Megrez.Compositor { /// - value: 資料值。 /// - type: 指定覆寫行為。 /// - Returns: 該操作是否成功執行。 - internal func overrideCandidateAgainst(key: String?, at location: Int, value: String, type: Node.OverrideType) + internal func overrideCandidateAgainst(keyArray: [String]?, at location: Int, value: String, type: Node.OverrideType) -> Bool { let location = max(min(location, keys.count), 0) // 防呆 var arrOverlappedNodes: [NodeAnchor] = fetchOverlappingNodes(at: min(keys.count - 1, location)) var overridden: NodeAnchor? for anchor in arrOverlappedNodes { - if let key = key, anchor.node.key != key { continue } + if let keyArray = keyArray, + anchor.node.keyArray.joined(separator: separator) != keyArray.joined(separator: separator) + { + continue + } if anchor.node.selectOverrideUnigram(value: value, type: type) { overridden = anchor break @@ -150,7 +171,9 @@ extension Megrez.Compositor { arrOverlappedNodes = fetchOverlappingNodes(at: i) for anchor in arrOverlappedNodes { if anchor.node == overridden.node { continue } - if !overridden.node.key.contains(anchor.node.key) || !overridden.node.value.contains(anchor.node.value) { + if !overridden.node.keyArray.joined(separator: separator).contains( + anchor.node.keyArray.joined(separator: separator)) || !overridden.node.value.contains(anchor.node.value) + { anchor.node.reset() continue } diff --git a/Packages/vChewing_Megrez/Sources/Megrez/6_Node.swift b/Packages/vChewing_Megrez/Sources/Megrez/6_Node.swift index 78fa31ea..a7d38a03 100644 --- a/Packages/vChewing_Megrez/Sources/Megrez/6_Node.swift +++ b/Packages/vChewing_Megrez/Sources/Megrez/6_Node.swift @@ -34,7 +34,8 @@ extension Megrez.Compositor { /// 數(比如野獸常數),以讓「c」更容易單獨被選中。 public var overridingScore: Double = 114_514 - public private(set) var key: String + // public var key: String { keyArray.joined(separator: Megrez.Compositor.theSeparator) } + public private(set) var keyArray: [String] public private(set) var spanLength: Int public private(set) var unigrams: [Megrez.Unigram] @@ -42,10 +43,10 @@ extension Megrez.Compositor { didSet { currentUnigramIndex = max(min(unigrams.count - 1, currentUnigramIndex), 0) } } - public var currentPair: Megrez.Compositor.KeyValuePaired { .init(key: key, value: value) } + public var currentPair: Megrez.Compositor.KeyValuePaired { .init(keyArray: keyArray, value: value) } public func hash(into hasher: inout Hasher) { - hasher.combine(key) + hasher.combine(keyArray) hasher.combine(spanLength) hasher.combine(unigrams) hasher.combine(currentUnigramIndex) @@ -68,14 +69,11 @@ extension Megrez.Compositor { public private(set) var overrideType: Node.OverrideType public static func == (lhs: Node, rhs: Node) -> Bool { - lhs.key == rhs.key && lhs.spanLength == rhs.spanLength + lhs.keyArray == rhs.keyArray && lhs.spanLength == rhs.spanLength && lhs.unigrams == rhs.unigrams && lhs.overrideType == rhs.overrideType } - public init( - keyArray: [String] = [], spanLength: Int = 0, unigrams: [Megrez.Unigram] = [], keySeparator: String = "" - ) { - key = keyArray.joined(separator: keySeparator) + public init(keyArray: [String] = [], spanLength: Int = 0, unigrams: [Megrez.Unigram] = []) { self.keyArray = keyArray self.spanLength = spanLength self.unigrams = unigrams @@ -112,6 +110,10 @@ extension Megrez.Compositor { overrideType = .withNoOverrides } + public func joinedKey(by separator: String = Megrez.Compositor.theSeparator) -> String { + keyArray.joined(separator: separator) + } + public func selectOverrideUnigram(value: String, type: Node.OverrideType) -> Bool { guard type != .withNoOverrides else { return false @@ -136,7 +138,7 @@ extension Megrez.Compositor { let spanIndex: Int // 幅位座標 var spanLength: Int { node.spanLength } var unigrams: [Megrez.Unigram] { node.unigrams } - var key: String { node.key } + var keyArray: [String] { node.keyArray } var value: String { node.value } /// 將該節錨雜湊化。 @@ -154,7 +156,12 @@ extension Array where Element == Megrez.Compositor.Node { public var values: [String] { map(\.value) } /// 從一個節點陣列當中取出目前的索引鍵陣列。 - public var keys: [String] { map(\.key) } + public func joinedKeys(by separator: String = Megrez.Compositor.theSeparator) -> [String] { + map { $0.keyArray.lazy.joined(separator: separator) } + } + + /// 從一個節點陣列當中取出目前的索引鍵陣列。 + public var keyArrays: [[String]] { map(\.keyArray) } /// 返回一連串的節點起點。結果為 (Result A, Result B) 辭典陣列 /// Result A 以索引查座標,Result B 以座標查索引。 diff --git a/Packages/vChewing_Megrez/Sources/Megrez/7_LangModel.swift b/Packages/vChewing_Megrez/Sources/Megrez/7_LangModel.swift index b08a6ed0..9c975148 100644 --- a/Packages/vChewing_Megrez/Sources/Megrez/7_LangModel.swift +++ b/Packages/vChewing_Megrez/Sources/Megrez/7_LangModel.swift @@ -5,10 +5,10 @@ /// 語言模組協定。 public protocol LangModelProtocol { - /// 給定鍵,讓語言模型找給一組單元圖陣列。 - func unigramsFor(key: String) -> [Megrez.Unigram] - /// 給定鍵,確認是否有單元圖記錄在庫。 - func hasUnigramsFor(key: String) -> Bool + /// 給定鍵陣列,讓語言模型找給一組單元圖陣列。 + func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] + /// 給定鍵陣列,確認是否有單元圖記錄在庫。 + func hasUnigramsFor(keyArray: [String]) -> Bool } extension Megrez.Compositor { @@ -24,15 +24,15 @@ extension Megrez.Compositor { /// 給定索引鍵,讓語言模型找給一組經過穩定排序的單元圖陣列。 /// - Parameter key: 給定的索引鍵字串。 /// - Returns: 對應的經過穩定排序的單元圖陣列。 - public func unigramsFor(key: String) -> [Megrez.Unigram] { - langModel.unigramsFor(key: key).stableSorted { $0.score > $1.score } + public func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] { + langModel.unigramsFor(keyArray: keyArray).stableSorted { $0.score > $1.score } } /// 根據給定的索引鍵來確認各個資料庫陣列內是否存在對應的資料。 /// - Parameter key: 索引鍵。 /// - Returns: 是否在庫。 - public func hasUnigramsFor(key: String) -> Bool { - langModel.hasUnigramsFor(key: key) + public func hasUnigramsFor(keyArray: [String]) -> Bool { + langModel.hasUnigramsFor(keyArray: keyArray) } } } diff --git a/Packages/vChewing_Megrez/Tests/MegrezTests/LMDataForTests.swift b/Packages/vChewing_Megrez/Tests/MegrezTests/LMDataForTests.swift index d89de536..6da9e48b 100644 --- a/Packages/vChewing_Megrez/Tests/MegrezTests/LMDataForTests.swift +++ b/Packages/vChewing_Megrez/Tests/MegrezTests/LMDataForTests.swift @@ -25,16 +25,16 @@ class SimpleLM: LangModelProtocol { } } - func unigramsFor(key: String) -> [Megrez.Unigram] { - if let f = mutDatabase[key] { + func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] { + if let f = mutDatabase[keyArray.joined()] { return f } else { return [Megrez.Unigram]().sorted { $0.score > $1.score } } } - func hasUnigramsFor(key: String) -> Bool { - mutDatabase.keys.contains(key) + func hasUnigramsFor(keyArray: [String]) -> Bool { + mutDatabase.keys.contains(keyArray.joined()) } func trim(key: String, value: String) { @@ -46,12 +46,12 @@ class SimpleLM: LangModelProtocol { } class MockLM: LangModelProtocol { - func unigramsFor(key: String) -> [Megrez.Unigram] { - [Megrez.Unigram(value: key, score: -1)] + func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] { + [Megrez.Unigram(value: keyArray.joined(), score: -1)] } - func hasUnigramsFor(key: String) -> Bool { - !key.isEmpty + func hasUnigramsFor(keyArray: [String]) -> Bool { + !keyArray.isEmpty } } diff --git a/Packages/vChewing_Megrez/Tests/MegrezTests/MegrezTests.swift b/Packages/vChewing_Megrez/Tests/MegrezTests/MegrezTests.swift index 8ec5c7a1..ad1077e9 100644 --- a/Packages/vChewing_Megrez/Tests/MegrezTests/MegrezTests.swift +++ b/Packages/vChewing_Megrez/Tests/MegrezTests/MegrezTests.swift @@ -12,9 +12,11 @@ final class MegrezTests: XCTestCase { func testSpan() throws { let langModel = SimpleLM(input: strSampleData) let span = Megrez.Compositor.Span() - let n1 = Megrez.Compositor.Node(keyArray: ["gao1"], spanLength: 1, unigrams: langModel.unigramsFor(key: "gao1")) + let n1 = Megrez.Compositor.Node( + keyArray: ["gao1"], spanLength: 1, unigrams: langModel.unigramsFor(keyArray: ["gao1"]) + ) let n3 = Megrez.Compositor.Node( - keyArray: ["gao1ke1ji4"], spanLength: 3, unigrams: langModel.unigramsFor(key: "gao1ke1ji4") + keyArray: ["gao1ke1ji4"], spanLength: 3, unigrams: langModel.unigramsFor(keyArray: ["gao1ke1ji4"]) ) XCTAssertEqual(span.maxLength, 0) span.append(node: n1) @@ -50,19 +52,19 @@ final class MegrezTests: XCTestCase { func testRankedLangModel() throws { class TestLM: LangModelProtocol { - func hasUnigramsFor(key: String) -> Bool { key == "foo" } - func unigramsFor(key: String) -> [Megrez.Unigram] { - key == "foo" + func hasUnigramsFor(keyArray: [String]) -> Bool { keyArray == ["foo"] } + func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] { + keyArray == ["foo"] ? [.init(value: "middle", score: -5), .init(value: "highest", score: -2), .init(value: "lowest", score: -10)] : .init() } } let lmRanked = Megrez.Compositor.LangModelRanked(withLM: TestLM()) - XCTAssertTrue(lmRanked.hasUnigramsFor(key: "foo")) - XCTAssertFalse(lmRanked.hasUnigramsFor(key: "bar")) - XCTAssertTrue(lmRanked.unigramsFor(key: "bar").isEmpty) - let unigrams = lmRanked.unigramsFor(key: "foo") + XCTAssertTrue(lmRanked.hasUnigramsFor(keyArray: ["foo"])) + XCTAssertFalse(lmRanked.hasUnigramsFor(keyArray: ["bar"])) + XCTAssertTrue(lmRanked.unigramsFor(keyArray: ["bar"]).isEmpty) + let unigrams = lmRanked.unigramsFor(keyArray: ["foo"]) XCTAssertEqual(unigrams.count, 3) XCTAssertEqual(unigrams[0].value, "highest") XCTAssertEqual(unigrams[0].score, -2) @@ -74,7 +76,7 @@ final class MegrezTests: XCTestCase { func testCompositor_BasicTests() throws { var compositor = Megrez.Compositor(with: MockLM()) - XCTAssertEqual(compositor.separator, Megrez.Compositor.kDefaultSeparator) + XCTAssertEqual(compositor.separator, Megrez.Compositor.theSeparator) XCTAssertEqual(compositor.cursor, 0) XCTAssertEqual(compositor.length, 0) @@ -87,7 +89,7 @@ final class MegrezTests: XCTestCase { print("fuckme") return } - XCTAssertEqual(zeroNode.key, "a") + XCTAssertEqual(zeroNode.keyArray.joined(separator: compositor.separator), "a") compositor.dropKey(direction: .rear) XCTAssertEqual(compositor.cursor, 0) @@ -97,9 +99,9 @@ final class MegrezTests: XCTestCase { func testCompositor_InvalidOperations() throws { class TestLM: LangModelProtocol { - func hasUnigramsFor(key: String) -> Bool { key == "foo" } - func unigramsFor(key: String) -> [Megrez.Unigram] { - key == "foo" ? [.init(value: "foo", score: -1)] : .init() + func hasUnigramsFor(keyArray: [String]) -> Bool { keyArray == ["foo"] } + func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] { + keyArray == ["foo"] ? [.init(value: "foo", score: -1)] : .init() } } @@ -146,14 +148,14 @@ final class MegrezTests: XCTestCase { XCTAssertEqual(compositor.length, 3) XCTAssertEqual(compositor.spans.count, 3) XCTAssertEqual(compositor.spans[0].maxLength, 3) - XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") - XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;b") - XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.key, "a;b;c") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;b") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.keyArray.joined(separator: compositor.separator), "a;b;c") XCTAssertEqual(compositor.spans[1].maxLength, 2) - XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "b") - XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.key, "b;c") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "b") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "b;c") XCTAssertEqual(compositor.spans[2].maxLength, 1) - XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.key, "c") + XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "c") } func testCompositor_SpanDeletionFromFront() throws { @@ -168,10 +170,10 @@ final class MegrezTests: XCTestCase { XCTAssertEqual(compositor.length, 2) XCTAssertEqual(compositor.spans.count, 2) XCTAssertEqual(compositor.spans[0].maxLength, 2) - XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") - XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;b") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;b") XCTAssertEqual(compositor.spans[1].maxLength, 1) - XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "b") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "b") } func testCompositor_SpanDeletionFromMiddle() throws { @@ -187,10 +189,10 @@ final class MegrezTests: XCTestCase { XCTAssertEqual(compositor.length, 2) XCTAssertEqual(compositor.spans.count, 2) XCTAssertEqual(compositor.spans[0].maxLength, 2) - XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") - XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;c") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;c") XCTAssertEqual(compositor.spans[1].maxLength, 1) - XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "c") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "c") compositor.clear() compositor.insertKey("a") @@ -203,10 +205,10 @@ final class MegrezTests: XCTestCase { XCTAssertEqual(compositor.length, 2) XCTAssertEqual(compositor.spans.count, 2) XCTAssertEqual(compositor.spans[0].maxLength, 2) - XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") - XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;c") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;c") XCTAssertEqual(compositor.spans[1].maxLength, 1) - XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "c") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "c") } func testCompositor_SpanDeletionFromRear() throws { @@ -223,10 +225,10 @@ final class MegrezTests: XCTestCase { XCTAssertEqual(compositor.length, 2) XCTAssertEqual(compositor.spans.count, 2) XCTAssertEqual(compositor.spans[0].maxLength, 2) - XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "b") - XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "b;c") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "b") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "b;c") XCTAssertEqual(compositor.spans[1].maxLength, 1) - XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "c") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "c") } func testCompositor_SpanInsertion() throws { @@ -242,19 +244,19 @@ final class MegrezTests: XCTestCase { XCTAssertEqual(compositor.length, 4) XCTAssertEqual(compositor.spans.count, 4) XCTAssertEqual(compositor.spans[0].maxLength, 4) - XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") - XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;X") - XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.key, "a;X;b") - XCTAssertEqual(compositor.spans[0].nodeOf(length: 4)?.key, "a;X;b;c") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;X") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.keyArray.joined(separator: compositor.separator), "a;X;b") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 4)?.keyArray.joined(separator: compositor.separator), "a;X;b;c") XCTAssertEqual(compositor.spans[1].maxLength, 3) - XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "X") - XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.key, "X;b") - XCTAssertEqual(compositor.spans[1].nodeOf(length: 3)?.key, "X;b;c") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "X") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "X;b") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 3)?.keyArray.joined(separator: compositor.separator), "X;b;c") XCTAssertEqual(compositor.spans[2].maxLength, 2) - XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.key, "b") - XCTAssertEqual(compositor.spans[2].nodeOf(length: 2)?.key, "b;c") + XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "b") + XCTAssertEqual(compositor.spans[2].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "b;c") XCTAssertEqual(compositor.spans[3].maxLength, 1) - XCTAssertEqual(compositor.spans[3].nodeOf(length: 1)?.key, "c") + XCTAssertEqual(compositor.spans[3].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "c") } func testCompositor_LongGridDeletion() throws { @@ -279,17 +281,17 @@ final class MegrezTests: XCTestCase { XCTAssertEqual(compositor.cursor, 6) XCTAssertEqual(compositor.length, 13) XCTAssertEqual(compositor.spans.count, 13) - XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.key, "abcdef") - XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.key, "bcdefh") - XCTAssertEqual(compositor.spans[1].nodeOf(length: 5)?.key, "bcdef") - XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.key, "cdefhi") - XCTAssertEqual(compositor.spans[2].nodeOf(length: 5)?.key, "cdefh") - XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.key, "defhij") - XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.key, "efhijk") - XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.key, "fhijkl") - XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.key, "hijklm") - XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.key, "ijklmn") - XCTAssertEqual(compositor.spans[8].nodeOf(length: 5)?.key, "jklmn") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "abcdef") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "bcdefh") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "bcdef") + XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "cdefhi") + XCTAssertEqual(compositor.spans[2].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "cdefh") + XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "defhij") + XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "efhijk") + XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "fhijkl") + XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "hijklm") + XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "ijklmn") + XCTAssertEqual(compositor.spans[8].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "jklmn") } func testCompositor_LongGridInsertion() throws { @@ -314,19 +316,19 @@ final class MegrezTests: XCTestCase { XCTAssertEqual(compositor.cursor, 8) XCTAssertEqual(compositor.length, 15) XCTAssertEqual(compositor.spans.count, 15) - XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.key, "abcdef") - XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.key, "bcdefg") - XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.key, "cdefgX") - XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.key, "defgXh") - XCTAssertEqual(compositor.spans[3].nodeOf(length: 5)?.key, "defgX") - XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.key, "efgXhi") - XCTAssertEqual(compositor.spans[4].nodeOf(length: 5)?.key, "efgXh") - XCTAssertEqual(compositor.spans[4].nodeOf(length: 4)?.key, "efgX") - XCTAssertEqual(compositor.spans[4].nodeOf(length: 3)?.key, "efg") - XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.key, "fgXhij") - XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.key, "gXhijk") - XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.key, "Xhijkl") - XCTAssertEqual(compositor.spans[8].nodeOf(length: 6)?.key, "hijklm") + XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "abcdef") + XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "bcdefg") + XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "cdefgX") + XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "defgXh") + XCTAssertEqual(compositor.spans[3].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "defgX") + XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "efgXhi") + XCTAssertEqual(compositor.spans[4].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "efgXh") + XCTAssertEqual(compositor.spans[4].nodeOf(length: 4)?.keyArray.joined(separator: compositor.separator), "efgX") + XCTAssertEqual(compositor.spans[4].nodeOf(length: 3)?.keyArray.joined(separator: compositor.separator), "efg") + XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "fgXhij") + XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "gXhijk") + XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "Xhijkl") + XCTAssertEqual(compositor.spans[8].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "hijklm") } func testCompositor_StressBench() throws { @@ -349,7 +351,7 @@ final class MegrezTests: XCTestCase { compositor.insertKey(String(i)) } let result = compositor.walk().0 - XCTAssertEqual(result.keys, ["高科技", "公司", "的", "年終", "獎金"]) + XCTAssertEqual(result.joinedKeys(by: ""), ["高科技", "公司", "的", "年終", "獎金"]) } func testCompositor_InputTestAndCursorJump() throws { @@ -510,11 +512,11 @@ final class MegrezTests: XCTestCase { XCTAssertEqual(result.values, ["高熱", "火焰", "危險"]) let location = 2 - XCTAssertTrue(compositor.overrideCandidate(.init(key: "huo3", value: "🔥"), at: location)) + XCTAssertTrue(compositor.overrideCandidate(.init(keyArray: ["huo3"], value: "🔥"), at: location)) result = compositor.walk().0 XCTAssertEqual(result.values, ["高熱", "🔥", "焰", "危險"]) - XCTAssertTrue(compositor.overrideCandidate(.init(key: "huo3yan4", value: "🔥"), at: location)) + XCTAssertTrue(compositor.overrideCandidate(.init(keyArray: ["huo3", "yan4"], value: "🔥"), at: location)) result = compositor.walk().0 XCTAssertEqual(result.values, ["高熱", "🔥", "危險"]) }