Megrez // Use [String] as key in lieu of String.

This commit is contained in:
ShikiSuen 2022-12-06 20:20:10 +08:00
parent ebaf44c7dc
commit 9b78be6d3a
7 changed files with 164 additions and 128 deletions

View File

@ -21,7 +21,7 @@ extension Megrez {
/// ///
public static var maxSpanLength: Int = 10 { didSet { maxSpanLength = max(6, maxSpanLength) } } 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 { public var cursor: Int = 0 {
didSet { didSet {
@ -33,7 +33,12 @@ extension Megrez {
/// ///
public var marker: Int = 0 { didSet { marker = max(0, min(marker, length)) } } 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 } public var width: Int { keys.count }
/// ///
@ -71,7 +76,7 @@ extension Megrez {
/// - Parameter key: /// - Parameter key:
/// - Returns: /// - Returns:
@discardableResult public mutating func insertKey(_ key: String) -> Bool { @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) keys.insert(key, at: cursor)
let gridBackup = spans let gridBackup = spans
resizeGrid(at: cursor, do: .expand) resizeGrid(at: cursor, do: .expand)
@ -242,7 +247,7 @@ extension Megrez.Compositor {
return true return true
} }
func getJointKeyArray(range: Range<Int>) -> [String] { func getJoinedKeyArray(range: Range<Int>) -> [String] {
// contains macOS 13 Ventura // contains macOS 13 Ventura
guard range.upperBound <= keys.count, range.lowerBound >= 0 else { return [] } guard range.upperBound <= keys.count, range.lowerBound >= 0 else { return [] }
return keys[range].map { String($0) } return keys[range].map { String($0) }
@ -262,11 +267,10 @@ extension Megrez.Compositor {
var nodesChanged = 0 var nodesChanged = 0
for position in range { for position in range {
for theLength in 1...min(maxSpanLength, range.upperBound - position) { for theLength in 1...min(maxSpanLength, range.upperBound - position) {
let jointKeyArray = getJointKeyArray(range: position..<(position + theLength)) let joinedKeyArray = getJoinedKeyArray(range: position..<(position + theLength))
let jointKey = jointKeyArray.joined(separator: separator) if let theNode = getNode(at: position, length: theLength, keyArray: joinedKeyArray) {
if let theNode = getNode(at: position, length: theLength, keyArray: jointKeyArray) {
if !updateExisting { continue } if !updateExisting { continue }
let unigrams = langModel.unigramsFor(key: jointKey) let unigrams = langModel.unigramsFor(keyArray: joinedKeyArray)
// //
if unigrams.isEmpty { if unigrams.isEmpty {
if theNode.keyArray.count == 1 { continue } if theNode.keyArray.count == 1 { continue }
@ -277,10 +281,10 @@ extension Megrez.Compositor {
nodesChanged += 1 nodesChanged += 1
continue continue
} }
let unigrams = langModel.unigramsFor(key: jointKey) let unigrams = langModel.unigramsFor(keyArray: joinedKeyArray)
guard !unigrams.isEmpty else { continue } guard !unigrams.isEmpty else { continue }
insertNode( insertNode(
.init(keyArray: jointKeyArray, spanLength: theLength, unigrams: unigrams, keySeparator: separator), .init(keyArray: joinedKeyArray, spanLength: theLength, unigrams: unigrams),
at: position at: position
) )
nodesChanged += 1 nodesChanged += 1

View File

@ -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 (i, vertexSpan) in vertexSpans.enumerated() {
for vertex in vertexSpan { 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.distance = 0
root.edges.append(contentsOf: vertexSpans[0]) root.edges.append(contentsOf: vertexSpans[0])

View File

@ -7,49 +7,66 @@ import Foundation
extension Megrez.Compositor { extension Megrez.Compositor {
public struct KeyValuePaired: Equatable, Hashable, Comparable, CustomStringConvertible { public struct KeyValuePaired: Equatable, Hashable, Comparable, CustomStringConvertible {
/// ///
public var key: String public var keyArray: [String]
/// ///
public var value: String public var value: String
/// ///
public var description: String { "(" + key + "," + value + ")" } public var description: String { "(" + keyArray.description + "," + value + ")" }
/// false /// 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: /// - Parameters:
/// - key: /// - key:
/// - value: /// - value:
public init(key: String = "", value: String = "") { public init(key: String = "N/A", value: String = "N/A") {
self.key = key keyArray = key.isEmpty ? ["N/A"] : key.components(separatedBy: Megrez.Compositor.theSeparator)
self.value = value self.value = value.isEmpty ? "N/A" : value
} }
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
hasher.combine(key) hasher.combine(keyArray)
hasher.combine(value) 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 { 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 { 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 { 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 { 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 { 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] let keyAtCursor = keys[location]
for theNode in anchors.map(\.node) { for theNode in anchors.map(\.node) {
if theNode.key.isEmpty { continue } if theNode.keyArray.joined(separator: separator).isEmpty { continue }
for gram in theNode.unigrams { for gram in theNode.unigrams {
switch filter { switch filter {
case .all: case .all:
@ -81,7 +98,7 @@ extension Megrez.Compositor {
case .endAt: case .endAt:
if theNode.keyArray.reversed()[0] != keyAtCursor { continue } 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 return result
@ -100,7 +117,7 @@ extension Megrez.Compositor {
) )
-> Bool -> 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, _ candidate: String,
at location: Int, overrideType: Node.OverrideType = .withHighScore at location: Int, overrideType: Node.OverrideType = .withHighScore
) -> Bool { ) -> Bool {
overrideCandidateAgainst(key: nil, at: location, value: candidate, type: overrideType) overrideCandidateAgainst(keyArray: nil, at: location, value: candidate, type: overrideType)
} }
// MARK: Internal implementations. // MARK: Internal implementations.
@ -127,14 +144,18 @@ extension Megrez.Compositor {
/// - value: /// - value:
/// - type: /// - type:
/// - Returns: /// - 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 -> Bool
{ {
let location = max(min(location, keys.count), 0) // let location = max(min(location, keys.count), 0) //
var arrOverlappedNodes: [NodeAnchor] = fetchOverlappingNodes(at: min(keys.count - 1, location)) var arrOverlappedNodes: [NodeAnchor] = fetchOverlappingNodes(at: min(keys.count - 1, location))
var overridden: NodeAnchor? var overridden: NodeAnchor?
for anchor in arrOverlappedNodes { 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) { if anchor.node.selectOverrideUnigram(value: value, type: type) {
overridden = anchor overridden = anchor
break break
@ -150,7 +171,9 @@ extension Megrez.Compositor {
arrOverlappedNodes = fetchOverlappingNodes(at: i) arrOverlappedNodes = fetchOverlappingNodes(at: i)
for anchor in arrOverlappedNodes { for anchor in arrOverlappedNodes {
if anchor.node == overridden.node { continue } 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() anchor.node.reset()
continue continue
} }

View File

@ -34,7 +34,8 @@ extension Megrez.Compositor {
/// c /// c
public var overridingScore: Double = 114_514 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 keyArray: [String]
public private(set) var spanLength: Int public private(set) var spanLength: Int
public private(set) var unigrams: [Megrez.Unigram] public private(set) var unigrams: [Megrez.Unigram]
@ -42,10 +43,10 @@ extension Megrez.Compositor {
didSet { currentUnigramIndex = max(min(unigrams.count - 1, currentUnigramIndex), 0) } 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) { public func hash(into hasher: inout Hasher) {
hasher.combine(key) hasher.combine(keyArray)
hasher.combine(spanLength) hasher.combine(spanLength)
hasher.combine(unigrams) hasher.combine(unigrams)
hasher.combine(currentUnigramIndex) hasher.combine(currentUnigramIndex)
@ -68,14 +69,11 @@ extension Megrez.Compositor {
public private(set) var overrideType: Node.OverrideType public private(set) var overrideType: Node.OverrideType
public static func == (lhs: Node, rhs: Node) -> Bool { 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 && lhs.unigrams == rhs.unigrams && lhs.overrideType == rhs.overrideType
} }
public init( public init(keyArray: [String] = [], spanLength: Int = 0, unigrams: [Megrez.Unigram] = []) {
keyArray: [String] = [], spanLength: Int = 0, unigrams: [Megrez.Unigram] = [], keySeparator: String = ""
) {
key = keyArray.joined(separator: keySeparator)
self.keyArray = keyArray self.keyArray = keyArray
self.spanLength = spanLength self.spanLength = spanLength
self.unigrams = unigrams self.unigrams = unigrams
@ -112,6 +110,10 @@ extension Megrez.Compositor {
overrideType = .withNoOverrides 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 { public func selectOverrideUnigram(value: String, type: Node.OverrideType) -> Bool {
guard type != .withNoOverrides else { guard type != .withNoOverrides else {
return false return false
@ -136,7 +138,7 @@ extension Megrez.Compositor {
let spanIndex: Int // let spanIndex: Int //
var spanLength: Int { node.spanLength } var spanLength: Int { node.spanLength }
var unigrams: [Megrez.Unigram] { node.unigrams } var unigrams: [Megrez.Unigram] { node.unigrams }
var key: String { node.key } var keyArray: [String] { node.keyArray }
var value: String { node.value } var value: String { node.value }
/// ///
@ -154,7 +156,12 @@ extension Array where Element == Megrez.Compositor.Node {
public var values: [String] { map(\.value) } 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)
/// Result A Result B /// Result A Result B

View File

@ -5,10 +5,10 @@
/// ///
public protocol LangModelProtocol { public protocol LangModelProtocol {
/// ///
func unigramsFor(key: String) -> [Megrez.Unigram] func unigramsFor(keyArray: [String]) -> [Megrez.Unigram]
/// ///
func hasUnigramsFor(key: String) -> Bool func hasUnigramsFor(keyArray: [String]) -> Bool
} }
extension Megrez.Compositor { extension Megrez.Compositor {
@ -24,15 +24,15 @@ extension Megrez.Compositor {
/// ///
/// - Parameter key: /// - Parameter key:
/// - Returns: /// - Returns:
public func unigramsFor(key: String) -> [Megrez.Unigram] { public func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] {
langModel.unigramsFor(key: key).stableSorted { $0.score > $1.score } langModel.unigramsFor(keyArray: keyArray).stableSorted { $0.score > $1.score }
} }
/// ///
/// - Parameter key: /// - Parameter key:
/// - Returns: /// - Returns:
public func hasUnigramsFor(key: String) -> Bool { public func hasUnigramsFor(keyArray: [String]) -> Bool {
langModel.hasUnigramsFor(key: key) langModel.hasUnigramsFor(keyArray: keyArray)
} }
} }
} }

View File

@ -25,16 +25,16 @@ class SimpleLM: LangModelProtocol {
} }
} }
func unigramsFor(key: String) -> [Megrez.Unigram] { func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] {
if let f = mutDatabase[key] { if let f = mutDatabase[keyArray.joined()] {
return f return f
} else { } else {
return [Megrez.Unigram]().sorted { $0.score > $1.score } return [Megrez.Unigram]().sorted { $0.score > $1.score }
} }
} }
func hasUnigramsFor(key: String) -> Bool { func hasUnigramsFor(keyArray: [String]) -> Bool {
mutDatabase.keys.contains(key) mutDatabase.keys.contains(keyArray.joined())
} }
func trim(key: String, value: String) { func trim(key: String, value: String) {
@ -46,12 +46,12 @@ class SimpleLM: LangModelProtocol {
} }
class MockLM: LangModelProtocol { class MockLM: LangModelProtocol {
func unigramsFor(key: String) -> [Megrez.Unigram] { func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] {
[Megrez.Unigram(value: key, score: -1)] [Megrez.Unigram(value: keyArray.joined(), score: -1)]
} }
func hasUnigramsFor(key: String) -> Bool { func hasUnigramsFor(keyArray: [String]) -> Bool {
!key.isEmpty !keyArray.isEmpty
} }
} }

View File

@ -12,9 +12,11 @@ final class MegrezTests: XCTestCase {
func testSpan() throws { func testSpan() throws {
let langModel = SimpleLM(input: strSampleData) let langModel = SimpleLM(input: strSampleData)
let span = Megrez.Compositor.Span() 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( 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) XCTAssertEqual(span.maxLength, 0)
span.append(node: n1) span.append(node: n1)
@ -50,19 +52,19 @@ final class MegrezTests: XCTestCase {
func testRankedLangModel() throws { func testRankedLangModel() throws {
class TestLM: LangModelProtocol { class TestLM: LangModelProtocol {
func hasUnigramsFor(key: String) -> Bool { key == "foo" } func hasUnigramsFor(keyArray: [String]) -> Bool { keyArray == ["foo"] }
func unigramsFor(key: String) -> [Megrez.Unigram] { func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] {
key == "foo" keyArray == ["foo"]
? [.init(value: "middle", score: -5), .init(value: "highest", score: -2), .init(value: "lowest", score: -10)] ? [.init(value: "middle", score: -5), .init(value: "highest", score: -2), .init(value: "lowest", score: -10)]
: .init() : .init()
} }
} }
let lmRanked = Megrez.Compositor.LangModelRanked(withLM: TestLM()) let lmRanked = Megrez.Compositor.LangModelRanked(withLM: TestLM())
XCTAssertTrue(lmRanked.hasUnigramsFor(key: "foo")) XCTAssertTrue(lmRanked.hasUnigramsFor(keyArray: ["foo"]))
XCTAssertFalse(lmRanked.hasUnigramsFor(key: "bar")) XCTAssertFalse(lmRanked.hasUnigramsFor(keyArray: ["bar"]))
XCTAssertTrue(lmRanked.unigramsFor(key: "bar").isEmpty) XCTAssertTrue(lmRanked.unigramsFor(keyArray: ["bar"]).isEmpty)
let unigrams = lmRanked.unigramsFor(key: "foo") let unigrams = lmRanked.unigramsFor(keyArray: ["foo"])
XCTAssertEqual(unigrams.count, 3) XCTAssertEqual(unigrams.count, 3)
XCTAssertEqual(unigrams[0].value, "highest") XCTAssertEqual(unigrams[0].value, "highest")
XCTAssertEqual(unigrams[0].score, -2) XCTAssertEqual(unigrams[0].score, -2)
@ -74,7 +76,7 @@ final class MegrezTests: XCTestCase {
func testCompositor_BasicTests() throws { func testCompositor_BasicTests() throws {
var compositor = Megrez.Compositor(with: MockLM()) var compositor = Megrez.Compositor(with: MockLM())
XCTAssertEqual(compositor.separator, Megrez.Compositor.kDefaultSeparator) XCTAssertEqual(compositor.separator, Megrez.Compositor.theSeparator)
XCTAssertEqual(compositor.cursor, 0) XCTAssertEqual(compositor.cursor, 0)
XCTAssertEqual(compositor.length, 0) XCTAssertEqual(compositor.length, 0)
@ -87,7 +89,7 @@ final class MegrezTests: XCTestCase {
print("fuckme") print("fuckme")
return return
} }
XCTAssertEqual(zeroNode.key, "a") XCTAssertEqual(zeroNode.keyArray.joined(separator: compositor.separator), "a")
compositor.dropKey(direction: .rear) compositor.dropKey(direction: .rear)
XCTAssertEqual(compositor.cursor, 0) XCTAssertEqual(compositor.cursor, 0)
@ -97,9 +99,9 @@ final class MegrezTests: XCTestCase {
func testCompositor_InvalidOperations() throws { func testCompositor_InvalidOperations() throws {
class TestLM: LangModelProtocol { class TestLM: LangModelProtocol {
func hasUnigramsFor(key: String) -> Bool { key == "foo" } func hasUnigramsFor(keyArray: [String]) -> Bool { keyArray == ["foo"] }
func unigramsFor(key: String) -> [Megrez.Unigram] { func unigramsFor(keyArray: [String]) -> [Megrez.Unigram] {
key == "foo" ? [.init(value: "foo", score: -1)] : .init() keyArray == ["foo"] ? [.init(value: "foo", score: -1)] : .init()
} }
} }
@ -146,14 +148,14 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(compositor.length, 3) XCTAssertEqual(compositor.length, 3)
XCTAssertEqual(compositor.spans.count, 3) XCTAssertEqual(compositor.spans.count, 3)
XCTAssertEqual(compositor.spans[0].maxLength, 3) XCTAssertEqual(compositor.spans[0].maxLength, 3)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;b") XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;b")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.key, "a;b;c") 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].maxLength, 2)
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "b") XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "b")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.key, "b;c") XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "b;c")
XCTAssertEqual(compositor.spans[2].maxLength, 1) 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 { func testCompositor_SpanDeletionFromFront() throws {
@ -168,10 +170,10 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(compositor.length, 2) XCTAssertEqual(compositor.length, 2)
XCTAssertEqual(compositor.spans.count, 2) XCTAssertEqual(compositor.spans.count, 2)
XCTAssertEqual(compositor.spans[0].maxLength, 2) XCTAssertEqual(compositor.spans[0].maxLength, 2)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;b") XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;b")
XCTAssertEqual(compositor.spans[1].maxLength, 1) 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 { func testCompositor_SpanDeletionFromMiddle() throws {
@ -187,10 +189,10 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(compositor.length, 2) XCTAssertEqual(compositor.length, 2)
XCTAssertEqual(compositor.spans.count, 2) XCTAssertEqual(compositor.spans.count, 2)
XCTAssertEqual(compositor.spans[0].maxLength, 2) XCTAssertEqual(compositor.spans[0].maxLength, 2)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;c") XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;c")
XCTAssertEqual(compositor.spans[1].maxLength, 1) 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.clear()
compositor.insertKey("a") compositor.insertKey("a")
@ -203,10 +205,10 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(compositor.length, 2) XCTAssertEqual(compositor.length, 2)
XCTAssertEqual(compositor.spans.count, 2) XCTAssertEqual(compositor.spans.count, 2)
XCTAssertEqual(compositor.spans[0].maxLength, 2) XCTAssertEqual(compositor.spans[0].maxLength, 2)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;c") XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;c")
XCTAssertEqual(compositor.spans[1].maxLength, 1) 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 { func testCompositor_SpanDeletionFromRear() throws {
@ -223,10 +225,10 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(compositor.length, 2) XCTAssertEqual(compositor.length, 2)
XCTAssertEqual(compositor.spans.count, 2) XCTAssertEqual(compositor.spans.count, 2)
XCTAssertEqual(compositor.spans[0].maxLength, 2) XCTAssertEqual(compositor.spans[0].maxLength, 2)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "b") XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "b")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "b;c") XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "b;c")
XCTAssertEqual(compositor.spans[1].maxLength, 1) 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 { func testCompositor_SpanInsertion() throws {
@ -242,19 +244,19 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(compositor.length, 4) XCTAssertEqual(compositor.length, 4)
XCTAssertEqual(compositor.spans.count, 4) XCTAssertEqual(compositor.spans.count, 4)
XCTAssertEqual(compositor.spans[0].maxLength, 4) XCTAssertEqual(compositor.spans[0].maxLength, 4)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a") XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;X") XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "a;X")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.key, "a;X;b") XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.keyArray.joined(separator: compositor.separator), "a;X;b")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 4)?.key, "a;X;b;c") 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].maxLength, 3)
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "X") XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "X")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.key, "X;b") XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "X;b")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 3)?.key, "X;b;c") 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].maxLength, 2)
XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.key, "b") XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.keyArray.joined(separator: compositor.separator), "b")
XCTAssertEqual(compositor.spans[2].nodeOf(length: 2)?.key, "b;c") XCTAssertEqual(compositor.spans[2].nodeOf(length: 2)?.keyArray.joined(separator: compositor.separator), "b;c")
XCTAssertEqual(compositor.spans[3].maxLength, 1) 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 { func testCompositor_LongGridDeletion() throws {
@ -279,17 +281,17 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(compositor.cursor, 6) XCTAssertEqual(compositor.cursor, 6)
XCTAssertEqual(compositor.length, 13) XCTAssertEqual(compositor.length, 13)
XCTAssertEqual(compositor.spans.count, 13) XCTAssertEqual(compositor.spans.count, 13)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.key, "abcdef") XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "abcdef")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.key, "bcdefh") XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "bcdefh")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 5)?.key, "bcdef") XCTAssertEqual(compositor.spans[1].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "bcdef")
XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.key, "cdefhi") XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "cdefhi")
XCTAssertEqual(compositor.spans[2].nodeOf(length: 5)?.key, "cdefh") XCTAssertEqual(compositor.spans[2].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "cdefh")
XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.key, "defhij") XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "defhij")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.key, "efhijk") XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "efhijk")
XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.key, "fhijkl") XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "fhijkl")
XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.key, "hijklm") XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "hijklm")
XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.key, "ijklmn") XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "ijklmn")
XCTAssertEqual(compositor.spans[8].nodeOf(length: 5)?.key, "jklmn") XCTAssertEqual(compositor.spans[8].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "jklmn")
} }
func testCompositor_LongGridInsertion() throws { func testCompositor_LongGridInsertion() throws {
@ -314,19 +316,19 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(compositor.cursor, 8) XCTAssertEqual(compositor.cursor, 8)
XCTAssertEqual(compositor.length, 15) XCTAssertEqual(compositor.length, 15)
XCTAssertEqual(compositor.spans.count, 15) XCTAssertEqual(compositor.spans.count, 15)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.key, "abcdef") XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "abcdef")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.key, "bcdefg") XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "bcdefg")
XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.key, "cdefgX") XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "cdefgX")
XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.key, "defgXh") XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "defgXh")
XCTAssertEqual(compositor.spans[3].nodeOf(length: 5)?.key, "defgX") XCTAssertEqual(compositor.spans[3].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "defgX")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.key, "efgXhi") XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "efgXhi")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 5)?.key, "efgXh") XCTAssertEqual(compositor.spans[4].nodeOf(length: 5)?.keyArray.joined(separator: compositor.separator), "efgXh")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 4)?.key, "efgX") XCTAssertEqual(compositor.spans[4].nodeOf(length: 4)?.keyArray.joined(separator: compositor.separator), "efgX")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 3)?.key, "efg") XCTAssertEqual(compositor.spans[4].nodeOf(length: 3)?.keyArray.joined(separator: compositor.separator), "efg")
XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.key, "fgXhij") XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "fgXhij")
XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.key, "gXhijk") XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "gXhijk")
XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.key, "Xhijkl") XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "Xhijkl")
XCTAssertEqual(compositor.spans[8].nodeOf(length: 6)?.key, "hijklm") XCTAssertEqual(compositor.spans[8].nodeOf(length: 6)?.keyArray.joined(separator: compositor.separator), "hijklm")
} }
func testCompositor_StressBench() throws { func testCompositor_StressBench() throws {
@ -349,7 +351,7 @@ final class MegrezTests: XCTestCase {
compositor.insertKey(String(i)) compositor.insertKey(String(i))
} }
let result = compositor.walk().0 let result = compositor.walk().0
XCTAssertEqual(result.keys, ["高科技", "公司", "", "年終", "獎金"]) XCTAssertEqual(result.joinedKeys(by: ""), ["高科技", "公司", "", "年終", "獎金"])
} }
func testCompositor_InputTestAndCursorJump() throws { func testCompositor_InputTestAndCursorJump() throws {
@ -510,11 +512,11 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(result.values, ["高熱", "火焰", "危險"]) XCTAssertEqual(result.values, ["高熱", "火焰", "危險"])
let location = 2 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 result = compositor.walk().0
XCTAssertEqual(result.values, ["高熱", "🔥", "", "危險"]) 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 result = compositor.walk().0
XCTAssertEqual(result.values, ["高熱", "🔥", "危險"]) XCTAssertEqual(result.values, ["高熱", "🔥", "危險"])
} }