Megrez // v1.2.8 update + UOM punctuation conditioning fix.

This commit is contained in:
ShikiSuen 2022-07-09 10:15:12 +08:00
parent 3db04310a2
commit 4ff9051c17
13 changed files with 450 additions and 419 deletions

View File

@ -56,7 +56,7 @@ class KeyHandler {
var compositor: Megrez.Compositor //
var currentLM: vChewing.LMInstantiator = .init() //
var currentUOM: vChewing.LMUserOverride = .init() //
var walkedAnchors: [Megrez.NodeAnchor] = [] //
var walkedAnchors: [Megrez.NodeAnchor] { compositor.walkedAnchors } //
/// (ctlInputMethod)便
var delegate: KeyHandlerDelegate?
@ -95,7 +95,6 @@ class KeyHandler {
func clear() {
composer.clear()
compositor.clear()
walkedAnchors.removeAll()
}
// MARK: - Functions dealing with Megrez.
@ -103,7 +102,7 @@ class KeyHandler {
/// Megrez 使便
///
/// 使 Node Crossing
var actualCandidateCursorIndex: Int {
var actualCandidateCursor: Int {
mgrPrefs.useRearCursorMode ? min(compositorCursorIndex, compositorLength - 1) : max(compositorCursorIndex, 1)
}
@ -113,11 +112,11 @@ class KeyHandler {
///
///
func walk() {
walkedAnchors = compositor.walk()
compositor.walk()
// GraphViz
if mgrPrefs.isDebugModeEnabled {
let result = compositor.grid.dumpDOT
let result = compositor.dumpDOT
do {
try result.write(
toFile: "/private/var/tmp/vChewing-visualization.dot",
@ -137,12 +136,10 @@ class KeyHandler {
///
var commitOverflownCompositionAndWalk: String {
var textToCommit = ""
if compositor.grid.width > mgrPrefs.composingBufferSize, !walkedAnchors.isEmpty {
if compositor.width > mgrPrefs.composingBufferSize, !walkedAnchors.isEmpty {
let anchor: Megrez.NodeAnchor = walkedAnchors[0]
if let theNode = anchor.node {
textToCommit = theNode.currentKeyValue.value
}
compositor.removeHeadReadings(count: anchor.spanningLength)
textToCommit = anchor.node.currentPair.value
compositor.removeHeadReadings(count: anchor.spanLength)
}
walk()
return textToCommit
@ -166,34 +163,30 @@ class KeyHandler {
/// - value:
/// - respectCursorPushing: true
func fixNode(value: String, respectCursorPushing: Bool = true) {
let adjustedIndex = max(0, min(actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength))
let adjustedCursor = max(0, min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength))
//
let selectedNode: Megrez.NodeAnchor = compositor.grid.fixNodeSelectedCandidate(
location: adjustedIndex, value: value
)
let selectedNode: Megrez.NodeAnchor = compositor.fixNodeSelectedCandidate(value, at: adjustedCursor)
//
if !mgrPrefs.useSCPCTypingMode {
var addToUserOverrideModel = true
//
if selectedNode.spanningLength != value.count {
if selectedNode.spanLength != value.count {
IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.")
addToUserOverrideModel = false
}
if addToUserOverrideModel {
if let theNode = selectedNode.node {
// SymbolLM Score -12
if theNode.scoreFor(candidate: value) <= -12 {
if selectedNode.node.scoreFor(candidate: value) <= -12 {
IME.prtDebugIntel("UOM: Score <= -12, dismissing.")
addToUserOverrideModel = false
}
}
}
if addToUserOverrideModel {
IME.prtDebugIntel("UOM: Start Observation.")
//
//
currentUOM.observe(
walkedAnchors: walkedAnchors, cursorIndex: adjustedIndex, candidate: value,
walkedAnchors: walkedAnchors, cursorIndex: adjustedCursor, candidate: value,
timestamp: NSDate().timeIntervalSince1970
)
}
@ -206,8 +199,8 @@ class KeyHandler {
if mgrPrefs.moveCursorAfterSelectingCandidate, respectCursorPushing {
var nextPosition = 0
for theAnchor in walkedAnchors {
if nextPosition >= adjustedIndex { break }
nextPosition += theAnchor.spanningLength
if nextPosition >= adjustedCursor { break }
nextPosition += theAnchor.spanLength
}
if nextPosition <= compositorLength {
compositorCursorIndex = nextPosition
@ -217,20 +210,17 @@ class KeyHandler {
///
func markNodesFixedIfNecessary() {
let width = compositor.grid.width
let width = compositor.width
if width <= kMaxComposingBufferNeedsToWalkSize {
return
}
var index = 0
for anchor in walkedAnchors {
guard let node = anchor.node else { break }
if index >= width - kMaxComposingBufferNeedsToWalkSize { break }
if node.score < node.kSelectedCandidateScore {
compositor.grid.fixNodeSelectedCandidate(
location: index + anchor.spanningLength, value: node.currentKeyValue.value
)
if anchor.node.score < Megrez.Node.kSelectedCandidateScore {
compositor.fixNodeSelectedCandidate(anchor.node.currentPair.value, at: index + anchor.spanLength)
}
index += anchor.spanningLength
index += anchor.spanLength
}
}
@ -248,15 +238,12 @@ class KeyHandler {
arrAnchors = arrAnchors.stableSort { $0.keyLength > $1.keyLength }
//
for currentNodeAnchor in arrAnchors {
guard let currentNode = currentNodeAnchor.node else { continue }
for currentCandidate in currentNode.candidates {
for currentCandidate in arrAnchors.map(\.node.candidates).joined() {
// / JIS
//
//
arrCandidates.append(currentCandidate.value)
}
}
// 調
if !mgrPrefs.fetchSuggestionsFromUserOverrideModel || mgrPrefs.useSCPCTypingMode || fixOrder {
return arrCandidates
@ -291,8 +278,8 @@ class KeyHandler {
if !overrideValue.isEmpty {
IME.prtDebugIntel(
"UOM: Suggestion retrieved, overriding the node score of the selected candidate.")
compositor.grid.overrideNodeScoreForSelectedCandidate(
location: min(actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength),
compositor.overrideNodeScoreForSelectedCandidate(
location: min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength),
value: overrideValue,
overridingScore: findHighestScore(nodeAnchors: rawAnchorsOfNodes, epsilon: kEpsilon)
)
@ -307,7 +294,7 @@ class KeyHandler {
/// - epsilon:
/// - Returns:
func findHighestScore(nodeAnchors: [Megrez.NodeAnchor], epsilon: Double) -> Double {
return nodeAnchors.compactMap(\.node?.highestUnigramScore).max() ?? 0 + epsilon
return nodeAnchors.map(\.node.highestUnigramScore).max() ?? 0 + epsilon
}
// MARK: - Extracted methods and functions (Tekkon).
@ -363,8 +350,8 @@ class KeyHandler {
/// 使 nodesCrossing macOS
/// nodeCrossing
mgrPrefs.useRearCursorMode
? compositor.grid.nodesBeginningAt(location: actualCandidateCursorIndex)
: compositor.grid.nodesEndingAt(location: actualCandidateCursorIndex)
? compositor.nodesBeginningAt(location: actualCandidateCursor)
: compositor.nodesEndingAt(location: actualCandidateCursor)
}
///
@ -390,7 +377,7 @@ class KeyHandler {
///
func insertToCompositorAtCursor(reading: String) {
compositor.insertReadingAtCursor(reading: reading)
compositor.insertReading(reading)
}
///
@ -408,28 +395,27 @@ class KeyHandler {
///
/// Rear
func deleteCompositorReadingAtTheRearOfCursor() {
compositor.deleteReadingAtTheRearOfCursor()
compositor.dropReading(direction: .rear)
}
///
///
/// Front
func deleteCompositorReadingToTheFrontOfCursor() {
compositor.deleteReadingToTheFrontOfCursor()
compositor.dropReading(direction: .front)
}
///
/// - Returns:
var keyLengthAtCurrentIndex: Int {
guard let node = walkedAnchors[compositorCursorIndex].node else { return 0 }
return node.key.split(separator: "-").count
walkedAnchors[compositorCursorIndex].node.key.split(separator: "-").count
}
var nextPhrasePosition: Int {
var nextPosition = 0
for theAnchor in walkedAnchors {
if nextPosition > actualCandidateCursorIndex { break }
nextPosition += theAnchor.spanningLength
if nextPosition > actualCandidateCursor { break }
nextPosition += theAnchor.spanLength
}
return min(nextPosition, compositorLength)
}

View File

@ -45,15 +45,15 @@ extension KeyHandler {
/// Swift.utf16NSString.length()
///
for theAnchor in walkedAnchors {
guard let theNode = theAnchor.node else { continue }
let strNodeValue = theNode.currentKeyValue.value
let theNode = theAnchor.node
let strNodeValue = theNode.currentPair.value
composingBuffer += strNodeValue
let arrSplit: [String] = Array(strNodeValue).map { String($0) }
let codepointCount = arrSplit.count
///
/// NodeAnchorspanningLength
///
let spanningLength: Int = theAnchor.spanningLength
let spanningLength: Int = theAnchor.spanLength
if readingCursorIndex + spanningLength <= compositorCursorIndex {
composedStringCursorIndex += strNodeValue.utf16.count
readingCursorIndex += spanningLength
@ -406,8 +406,7 @@ extension KeyHandler {
var composed = ""
for theAnchor in walkedAnchors {
if let node = theAnchor.node {
for node in walkedAnchors.map(\.node) {
var key = node.key
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
key = Tekkon.restoreToneOneInZhuyinKey(target: key) //
@ -418,11 +417,10 @@ extension KeyHandler {
key = Tekkon.cnvZhuyinChainToTextbookReading(target: key, newSeparator: " ")
}
let value = node.currentKeyValue.value
let value = node.currentPair.value
//
composed += key.contains("_") ? value : "<ruby>\(value)<rp>(</rp><rt>\(key)</rt><rp>)</rp></ruby>"
}
}
clear()
@ -796,26 +794,21 @@ extension KeyHandler {
var length = 0
var currentAnchor = Megrez.NodeAnchor()
let cursorIndex = min(
actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength
actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength
)
for anchor in walkedAnchors {
length += anchor.spanningLength
length += anchor.spanLength
if length >= cursorIndex {
currentAnchor = anchor
break
}
}
guard let currentNode = currentAnchor.node else {
IME.prtDebugIntel("4F2DEC2F")
errorCallback()
return true
}
let currentValue = currentNode.currentKeyValue.value
let currentNode = currentAnchor.node
let currentValue = currentNode.currentPair.value
var currentIndex = 0
if currentNode.score < currentNode.kSelectedCandidateScore {
if currentNode.score < Megrez.Node.kSelectedCandidateScore {
/// 使
/// 使
/// 2 使

View File

@ -28,7 +28,7 @@ import Foundation
extension vChewing {
/// LMInstantiatorLMI
/// LanguageModelProtocol 使
/// LangModelProtocol 使
///
///
/// LMI 調
@ -44,7 +44,7 @@ extension vChewing {
///
/// LMI LMI
///
public class LMInstantiator: LanguageModelProtocol {
public class LMInstantiator: LangModelProtocol {
//
public var isPhraseReplacementEnabled = false
public var isCNSEnabled = false
@ -256,7 +256,7 @@ extension vChewing {
lmAssociates.hasValuesFor(key: key)
}
/// 滿 LanguageModelProtocol
/// 滿 LangModelProtocol
public func bigramsForKeys(precedingKey _: String, key _: String) -> [Megrez.Bigram] { .init() }
// MARK: -

View File

@ -130,13 +130,12 @@ extension vChewing {
func convertKeyFrom(
walkedAnchors: [Megrez.NodeAnchor], cursorIndex: Int, readingOnly: Bool = false
) -> String {
let arrEndingPunctuation = ["", "", "", "", "", "", "", ""]
let whiteList = "你他妳她祢衪它牠再在"
var arrNodes: [Megrez.NodeAnchor] = []
var intLength = 0
for theNodeAnchor in walkedAnchors {
arrNodes.append(theNodeAnchor)
intLength += theNodeAnchor.spanningLength
intLength += theNodeAnchor.spanLength
if intLength >= cursorIndex {
break
}
@ -146,9 +145,8 @@ extension vChewing {
arrNodes = Array(arrNodes.reversed())
guard let kvCurrent = arrNodes[0].node?.currentKeyValue,
!arrEndingPunctuation.contains(kvCurrent.value)
else {
let kvCurrent = arrNodes[0].node.currentPair
guard !kvCurrent.key.contains("_") else {
return ""
}
@ -173,20 +171,18 @@ extension vChewing {
}
if arrNodes.count >= 2,
let kvPreviousThisOne = arrNodes[1].node?.currentKeyValue,
!arrEndingPunctuation.contains(kvPrevious.value),
!kvPrevious.key.contains("_"),
kvPrevious.key.split(separator: "-").count == kvPrevious.value.count
{
kvPrevious = kvPreviousThisOne
kvPrevious = arrNodes[1].node.currentPair
readingStack = kvPrevious.key + readingStack
}
if arrNodes.count >= 3,
let kvAnteriorThisOne = arrNodes[2].node?.currentKeyValue,
!arrEndingPunctuation.contains(kvAnterior.value),
!kvAnterior.key.contains("_"),
kvAnterior.key.split(separator: "-").count == kvAnterior.value.count
{
kvAnterior = kvAnteriorThisOne
kvAnterior = arrNodes[2].node.currentPair
readingStack = kvAnterior.key + readingStack
}

View File

@ -25,89 +25,106 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez {
///
public class Compositor {
public class Compositor: Grid {
///
public enum TypingDirection { case front, rear }
///
private let kDroppedPathScore: Double = -999
///
private var mutCursorIndex: Int = 0
public var cursor: Int = 0 { didSet { cursor = max(0, min(cursor, readings.count)) } }
///
private var mutReadings: [String] = []
///
private var mutGrid: Grid = .init()
private(set) var readings: [String] = []
/// 使
private var mutLM: LanguageModelProtocol
private var langModel: LangModelProtocol
/// 0
private(set) var cursorRegionMap: [Int: Int] = .init()
private(set) var walkedAnchors: [Megrez.NodeAnchor] = [] //
///
public var maxBuildSpanLength: Int { mutGrid.maxBuildSpanLength }
///
public var joinSeparator: String = ""
///
public var cursorIndex: Int {
get { mutCursorIndex }
set { mutCursorIndex = (newValue < 0) ? 0 : min(newValue, mutReadings.count) }
}
public var joinSeparator: String = "-"
///
public var isEmpty: Bool { mutGrid.isEmpty }
///
public var grid: Grid { mutGrid }
///
public var length: Int { mutReadings.count }
///
public var readings: [String] { mutReadings }
public var length: Int { readings.count }
///
/// - Parameter direction:
/// - Returns:
@discardableResult public func jumpCursorBySpan(to direction: TypingDirection) -> Bool {
switch direction {
case .front:
if cursor == width { return false }
case .rear:
if cursor == 0 { return false }
}
guard let currentRegion = cursorRegionMap[cursor] else { return false }
let aRegionForward = max(currentRegion - 1, 0)
let currentRegionBorderRear: Int = walkedAnchors[0..<currentRegion].map(\.spanLength).reduce(0, +)
switch cursor {
case currentRegionBorderRear:
switch direction {
case .front:
cursor =
(currentRegion > walkedAnchors.count)
? readings.count : walkedAnchors[0...currentRegion].map(\.spanLength).reduce(0, +)
case .rear:
cursor = walkedAnchors[0..<aRegionForward].map(\.spanLength).reduce(0, +)
}
default:
switch direction {
case .front:
cursor = currentRegionBorderRear + walkedAnchors[currentRegion].spanLength
case .rear:
cursor = currentRegionBorderRear
}
}
return true
}
///
/// - Parameters:
/// - lm: Megrez.LanguageModel
/// - lm: Megrez.LangModel
/// - length: 10
/// - separator:
public init(lm: LanguageModelProtocol, length: Int = 10, separator: String = "") {
mutLM = lm
mutGrid = .init(spanLength: abs(length)) //
public init(lm: LangModelProtocol, length: Int = 10, separator: String = "-") {
langModel = lm
super.init(spanLength: abs(length)) //
joinSeparator = separator
}
///
public func clear() {
mutCursorIndex = 0
mutReadings.removeAll()
mutGrid.clear()
override public func clear() {
super.clear()
cursor = 0
readings.removeAll()
walkedAnchors.removeAll()
}
///
/// - Parameters:
/// - reading:
public func insertReadingAtCursor(reading: String) {
mutReadings.insert(reading, at: mutCursorIndex)
mutGrid.expandGridByOneAt(location: mutCursorIndex)
build()
mutCursorIndex += 1
}
///
/// Rear
@discardableResult public func deleteReadingAtTheRearOfCursor() -> Bool {
if mutCursorIndex == 0 {
return false
}
mutReadings.remove(at: mutCursorIndex - 1)
mutCursorIndex -= 1
mutGrid.shrinkGridByOneAt(location: mutCursorIndex)
@discardableResult public func insertReading(_ reading: String) -> Bool {
guard !reading.isEmpty, langModel.hasUnigramsFor(key: reading) else { return false }
readings.insert(reading, at: cursor)
resizeGridByOneAt(location: cursor, to: .expand)
build()
cursor += 1
return true
}
///
/// Front
@discardableResult public func deleteReadingToTheFrontOfCursor() -> Bool {
if mutCursorIndex == mutReadings.count {
///
///
/// RearFront
/// - Parameter direction:
/// - Returns:
@discardableResult public func dropReading(direction: TypingDirection) -> Bool {
let isBackSpace = direction == .rear
if cursor == (isBackSpace ? 0 : readings.count) {
return false
}
mutReadings.remove(at: mutCursorIndex)
mutGrid.shrinkGridByOneAt(location: mutCursorIndex)
readings.remove(at: cursor - (isBackSpace ? 1 : 0))
cursor -= (isBackSpace ? 1 : 0)
resizeGridByOneAt(location: cursor, to: .shrink)
build()
return true
}
@ -118,98 +135,84 @@ extension Megrez {
///
@discardableResult public func removeHeadReadings(count: Int) -> Bool {
let count = abs(count) //
if count > length {
return false
}
if count > length { return false }
for _ in 0..<count {
if mutCursorIndex > 0 {
mutCursorIndex -= 1
}
if !mutReadings.isEmpty {
mutReadings.removeFirst()
mutGrid.shrinkGridByOneAt(location: 0)
cursor = max(cursor - 1, 0)
if !readings.isEmpty {
readings.removeFirst()
resizeGridByOneAt(location: 0, to: .shrink)
}
build()
}
return true
}
// MARK: - Walker
///
/// - Parameters:
/// - location:
/// - accumulatedScore: 0
/// - joinedPhrase: 使
/// - longPhrases: 使
public func walk(
at location: Int = 0,
score accumulatedScore: Double = 0.0,
joinedPhrase: String = "",
longPhrases: [String] = .init()
) -> [NodeAnchor] {
let newLocation = (mutGrid.width) - abs(location) //
return Array(
reverseWalk(
at: newLocation, score: accumulatedScore,
joinedPhrase: joinedPhrase, longPhrases: longPhrases
).reversed())
/// - Returns:
@discardableResult public func walk() -> [NodeAnchor] {
let newLocation = width
//
walkedAnchors = Array(
reverseWalk(at: newLocation).reversed()
).lazy.filter { !$0.isEmpty }
updateCursorJumpingTables(walkedAnchors)
return walkedAnchors
}
///
// MARK: - Private functions
///
/// - Parameters:
/// - location:
/// - accumulatedScore: 0
/// - mass: 0
/// - joinedPhrase: 使
/// - longPhrases: 使
public func reverseWalk(
/// - Returns:
private func reverseWalk(
at location: Int,
score accumulatedScore: Double = 0.0,
mass: Double = 0.0,
joinedPhrase: String = "",
longPhrases: [String] = .init()
) -> [NodeAnchor] {
let location = abs(location) //
if location == 0 || location > mutGrid.width {
if location == 0 || location > width {
return .init()
}
var paths = [[NodeAnchor]]()
var nodes = mutGrid.nodesEndingAt(location: location)
nodes = nodes.stableSorted {
let nodes = nodesEndingAt(location: location).stableSorted {
$0.scoreForSort > $1.scoreForSort
}
if let nodeZero = nodes[0].node, nodeZero.score >= nodeZero.kSelectedCandidateScore {
guard !nodes.isEmpty else { return .init() } //
if nodes[0].node.score >= Node.kSelectedCandidateScore {
// 使
var anchorZero = nodes[0]
anchorZero.accumulatedScore = accumulatedScore + nodeZero.score
var theAnchor = nodes[0]
theAnchor.mass = mass + nodes[0].node.score
var path: [NodeAnchor] = reverseWalk(
at: location - anchorZero.spanningLength, score: anchorZero.accumulatedScore
at: location - theAnchor.spanLength, mass: theAnchor.mass
)
path.insert(anchorZero, at: 0)
path.insert(theAnchor, at: 0)
paths.append(path)
} else if !longPhrases.isEmpty {
var path = [NodeAnchor]()
for theAnchor in nodes {
guard let theNode = theAnchor.node else { continue }
var theAnchor = theAnchor
let joinedValue = theNode.currentKeyValue.value + joinedPhrase
let joinedValue = theAnchor.node.currentPair.value + joinedPhrase
//
// /////////使
//
if longPhrases.contains(joinedValue) {
theAnchor.accumulatedScore = kDroppedPathScore
theAnchor.mass = kDroppedPathScore
path.insert(theAnchor, at: 0)
paths.append(path)
continue
}
theAnchor.accumulatedScore = accumulatedScore + theNode.score
theAnchor.mass = mass + theAnchor.node.score
path = reverseWalk(
at: location - theAnchor.spanningLength,
score: theAnchor.accumulatedScore,
at: location - theAnchor.spanLength,
mass: theAnchor.mass,
joinedPhrase: (joinedValue.count >= longPhrases[0].count) ? "" : joinedValue,
longPhrases: .init()
)
@ -219,9 +222,8 @@ extension Megrez {
} else {
//
var longPhrases = [String]()
for theAnchor in nodes.lazy.filter({ $0.spanningLength > 1 }) {
guard let theNode = theAnchor.node else { continue }
longPhrases.append(theNode.currentKeyValue.value)
for theAnchor in nodes.lazy.filter({ $0.spanLength > 1 }) {
longPhrases.append(theAnchor.node.currentPair.value)
}
longPhrases = longPhrases.stableSorted {
@ -229,12 +231,11 @@ extension Megrez {
}
for theAnchor in nodes {
var theAnchor = theAnchor
guard let theNode = theAnchor.node else { continue }
theAnchor.accumulatedScore = accumulatedScore + theNode.score
theAnchor.mass = mass + theAnchor.node.score
var path = [NodeAnchor]()
path = reverseWalk(
at: location - theAnchor.spanningLength, score: theAnchor.accumulatedScore,
joinedPhrase: (theAnchor.spanningLength > 1) ? "" : theNode.currentKeyValue.value,
at: location - theAnchor.spanLength, mass: theAnchor.mass,
joinedPhrase: (theAnchor.spanLength > 1) ? "" : theAnchor.node.currentPair.value,
longPhrases: .init()
)
path.insert(theAnchor, at: 0)
@ -248,31 +249,29 @@ extension Megrez {
var result: [NodeAnchor] = paths[0]
for neta in paths.lazy.filter({
$0.last!.accumulatedScore > result.last!.accumulatedScore
$0.last!.mass > result.last!.mass
}) {
result = neta
}
return result
return result // walk()
}
// MARK: - Private functions
private func build() {
let itrBegin: Int =
(mutCursorIndex < maxBuildSpanLength) ? 0 : mutCursorIndex - maxBuildSpanLength
let itrEnd: Int = min(mutCursorIndex + maxBuildSpanLength, mutReadings.count)
(cursor < maxBuildSpanLength) ? 0 : cursor - maxBuildSpanLength
let itrEnd: Int = min(cursor + maxBuildSpanLength, readings.count)
for p in itrBegin..<itrEnd {
for q in 1..<maxBuildSpanLength {
if p + q > itrEnd { break }
let arrSlice = mutReadings[p..<(p + q)]
let arrSlice = readings[p..<(p + q)]
let combinedReading: String = join(slice: arrSlice, separator: joinSeparator)
if mutGrid.hasMatchedNode(location: p, spanningLength: q, key: combinedReading) { continue }
let unigrams: [Unigram] = mutLM.unigramsFor(key: combinedReading)
if hasMatchedNode(location: p, spanLength: q, key: combinedReading) { continue }
let unigrams: [Unigram] = langModel.unigramsFor(key: combinedReading)
if unigrams.isEmpty { continue }
let n = Node(key: combinedReading, unigrams: unigrams)
mutGrid.insertNode(node: n, location: p, spanningLength: q)
insertNode(node: n, location: p, spanLength: q)
}
}
}
@ -280,6 +279,20 @@ extension Megrez {
private func join(slice arrSlice: ArraySlice<String>, separator: String) -> String {
arrSlice.joined(separator: separator)
}
internal func updateCursorJumpingTables(_ anchors: [NodeAnchor]) {
var cursorRegionMapDict = [Int: Int]()
var counter = 0
for (i, anchor) in anchors.enumerated() {
for _ in 0..<anchor.spanLength {
cursorRegionMapDict[counter] = i
counter += 1
}
}
cursorRegionMapDict[counter] = anchors.count
cursorRegionMapDict[-1] = 0 //
cursorRegionMap = cursorRegionMapDict
}
}
}

View File

@ -24,93 +24,82 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
extension Megrez {
///
///
public class Grid {
///
public enum ResizeBehavior { case expand, shrink }
///
private var mutSpans: [Megrez.Span]
private(set) var spans: [Megrez.SpanUnit]
///
private var mutMaxBuildSpanLength = 10
///
public var maxBuildSpanLength: Int { mutMaxBuildSpanLength }
///
private(set) var maxBuildSpanLength = 10
///
public var width: Int { mutSpans.count }
public var width: Int { spans.count }
///
public var isEmpty: Bool { mutSpans.isEmpty }
public var isEmpty: Bool { spans.isEmpty }
///
public init(spanLength: Int = 10) {
mutMaxBuildSpanLength = spanLength
mutSpans = [Megrez.Span]()
maxBuildSpanLength = spanLength
spans = [Megrez.SpanUnit]()
}
///
public func clear() {
mutSpans.removeAll()
spans.removeAll()
}
///
/// - Parameters:
/// - node:
/// - location:
/// - spanningLength:
public func insertNode(node: Node, location: Int, spanningLength: Int) {
/// - spanLength:
public func insertNode(node: Node, location: Int, spanLength: Int) {
let location = abs(location) //
let spanningLength = abs(spanningLength) //
if location >= mutSpans.count {
let diff = location - mutSpans.count + 1
let spanLength = abs(spanLength) //
if location >= spans.count {
let diff = location - spans.count + 1
for _ in 0..<diff {
mutSpans.append(Span())
spans.append(SpanUnit())
}
}
mutSpans[location].insert(node: node, length: spanningLength)
spans[location].insert(node: node, length: spanLength)
}
///
/// - Parameters:
/// - location:
/// - spanningLength:
/// - spanLength:
/// - key:
public func hasMatchedNode(location: Int, spanningLength: Int, key: String) -> Bool {
public func hasMatchedNode(location: Int, spanLength: Int, key: String) -> Bool {
let location = abs(location) //
let spanningLength = abs(spanningLength) //
if location > mutSpans.count {
let spanLength = abs(spanLength) //
if location > spans.count {
return false
}
let n = mutSpans[location].node(length: spanningLength)
let n = spans[location].nodeOf(length: spanLength)
return n != nil && key == n?.key
}
///
///
/// - Parameters:
/// - location:
public func expandGridByOneAt(location: Int) {
let location = abs(location) //
mutSpans.insert(Span(), at: location)
if location == 0 || location == mutSpans.count { return }
public func resizeGridByOneAt(location: Int, to behavior: ResizeBehavior) {
let location = max(0, min(width, location)) //
switch behavior {
case .expand:
spans.insert(SpanUnit(), at: location)
if [spans.count, 0].contains(location) { return }
case .shrink:
if location >= spans.count { return }
spans.remove(at: location)
}
for i in 0..<location {
// zaps overlapping spans
mutSpans[i].removeNodeOfLengthGreaterThan(location - i)
}
}
///
/// - Parameters:
/// - location:
public func shrinkGridByOneAt(location: Int) {
let location = abs(location) //
if location >= mutSpans.count {
return
}
mutSpans.remove(at: location)
for i in 0..<location {
// zaps overlapping spans
mutSpans[i].removeNodeOfLengthGreaterThan(location - i)
spans[i].dropNodesBeyond(length: location - i)
}
}
@ -120,21 +109,21 @@ extension Megrez {
public func nodesBeginningAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if location >= mutSpans.count { return results }
// mutSpans location 0
let span = mutSpans[location]
if location >= spans.count { return results }
// spans location 0
let span = spans[location]
for i in 1...maxBuildSpanLength {
if let np = span.node(length: i) {
if let np = span.nodeOf(length: i) {
results.append(
.init(
node: np,
location: location,
spanningLength: i
spanLength: i
)
)
}
}
return results
return results //
}
///
@ -143,21 +132,21 @@ extension Megrez {
public func nodesEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if mutSpans.isEmpty || location > mutSpans.count { return results }
if spans.isEmpty || location > spans.count { return results }
for i in 0..<location {
let span = mutSpans[i]
if i + span.maximumLength < location { continue }
if let np = span.node(length: location - i) {
let span = spans[i]
if i + span.maxLength < location { continue }
if let np = span.nodeOf(length: location - i) {
results.append(
.init(
node: np,
location: i,
spanningLength: location - i
spanLength: location - i
)
)
}
}
return results
return results //
}
///
@ -166,46 +155,76 @@ extension Megrez {
public func nodesCrossingOrEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if mutSpans.isEmpty || location > mutSpans.count { return results }
if spans.isEmpty || location > spans.count { return results }
for i in 0..<location {
let span = mutSpans[i]
if i + span.maximumLength < location { continue }
for j in 1...span.maximumLength {
let span = spans[i]
if i + span.maxLength < location { continue }
for j in 1...span.maxLength {
if i + j < location { continue }
if let np = span.node(length: j) {
if let np = span.nodeOf(length: j) {
results.append(
.init(
node: np,
location: i,
spanningLength: location - i
spanLength: location - i
)
)
}
}
}
return results
return results //
}
///
///
///
///
/// - Parameters:
/// - location:
public func nodesOverlappedAt(location: Int) -> [NodeAnchor] {
Array(Set(nodesBeginningAt(location: location) + nodesCrossingOrEndingAt(location: location)))
}
/// 使
///
/// fixNodeWithCandidate()
/// - Parameters:
/// - location:
/// - value:
@discardableResult public func fixNodeWithCandidateLiteral(_ value: String, at location: Int) -> NodeAnchor {
let location = abs(location) //
var node = NodeAnchor()
for theAnchor in nodesOverlappedAt(location: location) {
let candidates = theAnchor.node.candidates
//
theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() {
if candidate.value == value {
theAnchor.node.selectCandidateAt(index: i)
node = theAnchor
break
}
}
}
return node
}
/// 使
///
///
/// - Parameters:
/// - location:
/// - value:
@discardableResult public func fixNodeSelectedCandidate(location: Int, value: String) -> NodeAnchor {
/// - value:
@discardableResult public func fixNodeWithCandidate(_ pair: KeyValuePaired, at location: Int) -> NodeAnchor {
let location = abs(location) //
var node = NodeAnchor()
for nodeAnchor in nodesCrossingOrEndingAt(location: location) {
guard let theNode = nodeAnchor.node else {
continue
}
let candidates = theNode.candidates
for theAnchor in nodesOverlappedAt(location: location) {
let candidates = theAnchor.node.candidates
//
theNode.resetCandidate()
theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() {
if candidate.value == value {
theNode.selectCandidateAt(index: i)
node = nodeAnchor
if candidate == pair {
theAnchor.node.selectCandidateAt(index: i)
node = theAnchor
break
}
}
@ -220,16 +239,13 @@ extension Megrez {
/// - overridingScore:
public func overrideNodeScoreForSelectedCandidate(location: Int, value: String, overridingScore: Double) {
let location = abs(location) //
for nodeAnchor in nodesCrossingOrEndingAt(location: location) {
guard let theNode = nodeAnchor.node else {
continue
}
let candidates = theNode.candidates
for theAnchor in nodesOverlappedAt(location: location) {
let candidates = theAnchor.node.candidates
//
theNode.resetCandidate()
theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() {
if candidate.value == value {
theNode.selectFloatingCandidateAt(index: i, score: overridingScore)
theAnchor.node.selectFloatingCandidateAt(index: i, score: overridingScore)
break
}
}
@ -244,29 +260,22 @@ extension Megrez.Grid {
/// GraphViz
public var dumpDOT: String {
var strOutput = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n"
for (p, span) in mutSpans.enumerated() {
for ni in 0...(span.maximumLength) {
guard let np: Megrez.Node = span.node(length: ni) else {
continue
}
for (p, span) in spans.enumerated() {
for ni in 0...(span.maxLength) {
guard let np = span.nodeOf(length: ni) else { continue }
if p == 0 {
strOutput += "BOS -> \(np.currentKeyValue.value);\n"
strOutput += "BOS -> \(np.currentPair.value);\n"
}
strOutput += "\(np.currentKeyValue.value);\n"
if (p + ni) < mutSpans.count {
let destinationSpan = mutSpans[p + ni]
for q in 0...(destinationSpan.maximumLength) {
if let dn = destinationSpan.node(length: q) {
strOutput += np.currentKeyValue.value + " -> " + dn.currentKeyValue.value + ";\n"
strOutput += "\(np.currentPair.value);\n"
if (p + ni) < spans.count {
let destinationSpan = spans[p + ni]
for q in 0...(destinationSpan.maxLength) {
guard let dn = destinationSpan.nodeOf(length: q) else { continue }
strOutput += np.currentPair.value + " -> " + dn.currentPair.value + ";\n"
}
}
}
if (p + ni) == mutSpans.count {
strOutput += np.currentKeyValue.value + " -> EOS;\n"
}
guard (p + ni) == spans.count else { continue }
strOutput += np.currentPair.value + " -> EOS;\n"
}
}
strOutput += "EOS;\n}\n"

View File

@ -25,25 +25,34 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez {
///
@frozen public struct NodeAnchor: CustomStringConvertible {
@frozen public struct NodeAnchor: Hashable {
///
public var isEmpty: Bool { node.key.isEmpty }
///
public var node: Node?
public var node: Node = .init()
///
public var location: Int = 0
///
public var spanningLength: Int = 0
///
public var spanLength: Int = 0
///
public var accumulatedScore: Double = 0.0
public var mass: Double = 0.0
///
public var keyLength: Int {
node?.key.count ?? 0
isEmpty ? node.key.count : 0
}
public func hash(into hasher: inout Hasher) {
hasher.combine(node)
hasher.combine(location)
hasher.combine(spanLength)
hasher.combine(mass)
}
///
public var description: String {
var stream = ""
stream += "{@(" + String(location) + "," + String(spanningLength) + "),"
if let node = node {
stream += "{@(" + String(location) + "," + String(spanLength) + "),"
if node.key.isEmpty {
stream += node.description
} else {
stream += "null"
@ -54,12 +63,12 @@ extension Megrez {
///
public var scoreForSort: Double {
node?.score ?? 0
isEmpty ? node.score : 0
}
}
}
// MARK: - DumpDOT-related functions.
// MARK: - Array Extensions.
extension Array where Element == Megrez.NodeAnchor {
///
@ -70,4 +79,14 @@ extension Array where Element == Megrez.NodeAnchor {
}
return arrOutputContent.joined(separator: "<-")
}
///
public var values: [String] {
map(\.node.currentPair.value)
}
///
public var keys: [String] {
map(\.node.currentPair.key)
}
}

View File

@ -25,21 +25,16 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez {
///
@frozen public struct Span {
@frozen public struct SpanUnit {
///
private var mutLengthNodeMap: [Int: Megrez.Node] = [:]
///
private var mutMaximumLength: Int = 0
///
public var maximumLength: Int {
mutMaximumLength
}
private var lengthNodeMap: [Int: Megrez.Node] = [:]
///
private(set) var maxLength: Int = 0
///
mutating func clear() {
mutLengthNodeMap.removeAll()
mutMaximumLength = 0
lengthNodeMap.removeAll()
maxLength = 0
}
///
@ -48,37 +43,37 @@ extension Megrez {
/// - length:
mutating func insert(node: Node, length: Int) {
let length = abs(length) //
mutLengthNodeMap[length] = node
mutMaximumLength = max(mutMaximumLength, length)
lengthNodeMap[length] = node
maxLength = max(maxLength, length)
}
///
/// - Parameters:
/// - length:
mutating func removeNodeOfLengthGreaterThan(_ length: Int) {
mutating func dropNodesBeyond(length: Int) {
let length = abs(length) //
if length > mutMaximumLength { return }
if length > maxLength { return }
var lenMax = 0
var removalList: [Int: Megrez.Node] = [:]
for key in mutLengthNodeMap.keys {
for key in lengthNodeMap.keys {
if key > length {
removalList[key] = mutLengthNodeMap[key]
removalList[key] = lengthNodeMap[key]
} else {
lenMax = max(lenMax, key)
}
}
for key in removalList.keys {
mutLengthNodeMap.removeValue(forKey: key)
lengthNodeMap.removeValue(forKey: key)
}
mutMaximumLength = lenMax
maxLength = lenMax
}
///
/// - Parameters:
/// - length:
public func node(length: Int) -> Node? {
public func nodeOf(length: Int) -> Node? {
// Abs()
mutLengthNodeMap.keys.contains(abs(length)) ? mutLengthNodeMap[abs(length)] : nil
lengthNodeMap.keys.contains(abs(length)) ? lengthNodeMap[abs(length)] : nil
}
}
}

View File

@ -25,76 +25,86 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez {
///
public class Node {
///
private var mutKey: String = ""
///
private var mutScore: Double = 0
///
private var mutUnigrams: [Unigram]
///
private var mutBigrams: [Bigram]
///
private var mutCandidates: [KeyValuePaired] = []
/// 調
private var mutValueUnigramIndexMap: [String: Int] = [:]
///
private var mutPrecedingBigramMap: [KeyValuePaired: [Megrez.Bigram]] = [:]
///
private var mutCandidateFixed: Bool = false
///
private var mutSelectedUnigramIndex: Int = 0
///
public let kSelectedCandidateScore: Double = 99
///
public var description: String {
"(node,key:\(mutKey),fixed:\(mutCandidateFixed ? "true" : "false"),selected:\(mutSelectedUnigramIndex),\(mutUnigrams))"
public class Node: Equatable, Hashable {
public static func == (lhs: Megrez.Node, rhs: Megrez.Node) -> Bool {
lhs.key == rhs.key && lhs.score == rhs.score && lhs.unigrams == rhs.unigrams && lhs.bigrams == rhs.bigrams
&& lhs.candidates == rhs.candidates && lhs.valueUnigramIndexMap == rhs.valueUnigramIndexMap
&& lhs.precedingBigramMap == rhs.precedingBigramMap && lhs.isCandidateFixed == rhs.isCandidateFixed
&& lhs.selectedUnigramIndex == rhs.selectedUnigramIndex
}
///
public var candidates: [KeyValuePaired] { mutCandidates }
///
public var isCandidateFixed: Bool { mutCandidateFixed }
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(score)
hasher.combine(unigrams)
hasher.combine(bigrams)
hasher.combine(candidates)
hasher.combine(valueUnigramIndexMap)
hasher.combine(precedingBigramMap)
hasher.combine(isCandidateFixed)
hasher.combine(selectedUnigramIndex)
}
///
private(set) var key: String = ""
///
private(set) var score: Double = 0
///
private var unigrams: [Unigram]
///
private var bigrams: [Bigram]
///
private(set) var candidates: [KeyValuePaired] = []
/// 調
private var valueUnigramIndexMap: [String: Int] = [:]
///
private var precedingBigramMap: [KeyValuePaired: [Megrez.Bigram]] = [:]
///
private(set) var isCandidateFixed: Bool = false
///
private var selectedUnigramIndex: Int = 0
///
public static let kSelectedCandidateScore: Double = 99
///
public var description: String {
"(node,key:\(key),fixed:\(isCandidateFixed ? "true" : "false"),selected:\(selectedUnigramIndex),\(unigrams))"
}
///
public var key: String { mutKey }
///
public var score: Double { mutScore }
///
public var currentKeyValue: KeyValuePaired {
mutSelectedUnigramIndex >= mutUnigrams.count ? KeyValuePaired() : mutCandidates[mutSelectedUnigramIndex]
public var currentPair: KeyValuePaired {
selectedUnigramIndex >= unigrams.count ? KeyValuePaired() : candidates[selectedUnigramIndex]
}
///
public var highestUnigramScore: Double { mutUnigrams.isEmpty ? 0.0 : mutUnigrams[0].score }
public var highestUnigramScore: Double { unigrams.isEmpty ? 0.0 : unigrams[0].score }
///
/// - Parameters:
/// - key:
/// - unigrams:
/// - bigrams:
public init(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) {
mutKey = key
mutUnigrams = unigrams
mutBigrams = bigrams
public init(key: String = "", unigrams: [Megrez.Unigram] = [], bigrams: [Megrez.Bigram] = []) {
self.key = key
self.unigrams = unigrams
self.bigrams = bigrams
mutUnigrams.sort {
self.unigrams.sort {
$0.score > $1.score
}
if !mutUnigrams.isEmpty {
mutScore = mutUnigrams[0].score
if !self.unigrams.isEmpty {
score = unigrams[0].score
}
for (i, gram) in mutUnigrams.enumerated() {
mutValueUnigramIndexMap[gram.keyValue.value] = i
mutCandidates.append(gram.keyValue)
for (i, gram) in self.unigrams.enumerated() {
valueUnigramIndexMap[gram.keyValue.value] = i
candidates.append(gram.keyValue)
}
for gram in bigrams.lazy.filter({ [self] in
mutPrecedingBigramMap.keys.contains($0.precedingKeyValue)
precedingBigramMap.keys.contains($0.precedingKeyValue)
}) {
mutPrecedingBigramMap[gram.precedingKeyValue]?.append(gram)
precedingBigramMap[gram.precedingKeyValue]?.append(gram)
}
}
@ -102,22 +112,22 @@ extension Megrez {
/// - Parameters:
/// - precedingKeyValues:
public func primeNodeWith(precedingKeyValues: [KeyValuePaired]) {
var newIndex = mutSelectedUnigramIndex
var max = mutScore
var newIndex = selectedUnigramIndex
var max = score
if !isCandidateFixed {
for neta in precedingKeyValues {
let bigrams = mutPrecedingBigramMap[neta] ?? []
let bigrams = precedingBigramMap[neta] ?? []
for bigram in bigrams.lazy.filter({ [self] in
$0.score > max && mutValueUnigramIndexMap.keys.contains($0.keyValue.value)
$0.score > max && valueUnigramIndexMap.keys.contains($0.keyValue.value)
}) {
newIndex = mutValueUnigramIndexMap[bigram.keyValue.value] ?? newIndex
newIndex = valueUnigramIndexMap[bigram.keyValue.value] ?? newIndex
max = bigram.score
}
}
}
mutScore = max
mutSelectedUnigramIndex = newIndex
score = max
selectedUnigramIndex = newIndex
}
///
@ -126,17 +136,17 @@ extension Megrez {
/// - fix:
public func selectCandidateAt(index: Int = 0, fix: Bool = false) {
let index = abs(index)
mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index
mutCandidateFixed = fix
mutScore = kSelectedCandidateScore
selectedUnigramIndex = index >= unigrams.count ? 0 : index
isCandidateFixed = fix
score = Megrez.Node.kSelectedCandidateScore
}
///
public func resetCandidate() {
mutSelectedUnigramIndex = 0
mutCandidateFixed = false
if !mutUnigrams.isEmpty {
mutScore = mutUnigrams[0].score
selectedUnigramIndex = 0
isCandidateFixed = false
if !unigrams.isEmpty {
score = unigrams[0].score
}
}
@ -146,16 +156,26 @@ extension Megrez {
/// - score:
public func selectFloatingCandidateAt(index: Int, score: Double) {
let index = abs(index) //
mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index
mutCandidateFixed = false
mutScore = score
selectedUnigramIndex = index >= unigrams.count ? 0 : index
isCandidateFixed = false
self.score = score
}
///
/// - Parameters:
/// - candidate:
public func scoreFor(candidate: String) -> Double {
for unigram in mutUnigrams.lazy.filter({ $0.keyValue.value == candidate }) {
for unigram in unigrams.lazy.filter({ $0.keyValue.value == candidate }) {
return unigram.score
}
return 0.0
}
///
/// - Parameters:
/// - candidate:
public func scoreForPaired(candidate: KeyValuePaired) -> Double {
for unigram in unigrams.lazy.filter({ $0.keyValue == candidate }) {
return unigram.score
}
return 0.0

View File

@ -23,7 +23,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
public protocol LanguageModelProtocol {
public protocol LangModelProtocol {
///
func unigramsFor(key: String) -> [Megrez.Unigram]
@ -36,7 +36,7 @@ public protocol LanguageModelProtocol {
extension Megrez {
/// 使
open class LanguageModel: LanguageModelProtocol {
open class LangModel: LangModelProtocol {
public init() {}
// Swift

View File

@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez {
///
@frozen public struct Bigram: Equatable, CustomStringConvertible {
@frozen public struct Bigram: Equatable, CustomStringConvertible, Hashable {
///
public var keyValue: KeyValuePaired
///
@ -61,7 +61,7 @@ extension Megrez {
public static func < (lhs: Bigram, rhs: Bigram) -> Bool {
lhs.precedingKeyValue < rhs.precedingKeyValue
|| (lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue))
|| (lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.score < rhs.score))
}
}
}

View File

@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez {
///
@frozen public struct Unigram: Equatable, CustomStringConvertible {
@frozen public struct Unigram: Equatable, CustomStringConvertible, Hashable {
///
public var keyValue: KeyValuePaired
///
@ -54,7 +54,7 @@ extension Megrez {
}
public static func < (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue)
lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.score < rhs.score)
}
}
}

View File

@ -52,7 +52,7 @@ extension Megrez {
}
public static func == (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
lhs.key.count == rhs.key.count && lhs.value == rhs.value
lhs.key == rhs.key && lhs.value == rhs.value
}
public static func < (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {