vChewing-macOS/Packages/vChewing_Megrez/Sources/Megrez/3_KeyValuePaired.swift

230 lines
10 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Swiftified and further development by (c) 2022 and onwards The vChewing Project (MIT License).
// Was initially rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
public extension Megrez {
///
struct KeyValuePaired: Equatable, CustomStringConvertible, Hashable, Comparable {
///
public let keyArray: [String]
///
public let value: String
///
public let score: Double
///
public var description: String { "(\(keyArray.description),\(value),\(score))" }
/// false
public var isValid: Bool { !keyArray.joined().isEmpty && !value.isEmpty }
/// ()
public var toNGramKey: String { !isValid ? "()" : "(\(joinedKey()),\(value))" }
///
public var keyValueTuplet: (keyArray: [String], value: String) { (keyArray, value) }
///
public var triplet: (keyArray: [String], value: String, score: Double) { (keyArray, value, score) }
///
/// - Parameters:
/// - keyArray:
/// - value:
/// - score:
public init(keyArray: [String] = [], value: String = "N/A", score: Double = 0) {
self.keyArray = keyArray.isEmpty ? ["N/A"] : keyArray
self.value = value.isEmpty ? "N/A" : value
self.score = score
}
///
/// - Parameter tripletExpression:
public init(_ tripletExpression: (keyArray: [String], value: String, score: Double)) {
keyArray = tripletExpression.keyArray.isEmpty ? ["N/A"] : tripletExpression.keyArray
let theValue = tripletExpression.value.isEmpty ? "N/A" : tripletExpression.value
value = theValue
score = tripletExpression.score
}
///
/// - Parameter tuplet:
public init(_ tupletExpression: (keyArray: [String], value: String)) {
keyArray = tupletExpression.keyArray.isEmpty ? ["N/A"] : tupletExpression.keyArray
let theValue = tupletExpression.value.isEmpty ? "N/A" : tupletExpression.value
value = theValue
score = 0
}
///
/// - Parameters:
/// - key:
/// - value:
/// - score:
public init(key: String = "N/A", value: String = "N/A", score: Double = 0) {
keyArray = key.isEmpty ? ["N/A"] : key.sliced(by: Megrez.Compositor.theSeparator)
self.value = value.isEmpty ? "N/A" : value
self.score = score
}
///
/// - Parameter hasher:
public func hash(into hasher: inout Hasher) {
hasher.combine(keyArray)
hasher.combine(value)
hasher.combine(score)
}
public var hardCopy: KeyValuePaired {
.init(keyArray: keyArray, value: value, score: score)
}
public func joinedKey(by separator: String = Megrez.Compositor.theSeparator) -> String {
keyArray.joined(separator: separator)
}
public static func == (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
lhs.score == rhs.score && lhs.keyArray == rhs.keyArray && lhs.value == rhs.value
}
public static func < (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.keyArray.count < rhs.keyArray.count)
|| (lhs.keyArray.count == rhs.keyArray.count && lhs.value < rhs.value)
}
public static func > (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.keyArray.count > rhs.keyArray.count)
|| (lhs.keyArray.count == rhs.keyArray.count && lhs.value > rhs.value)
}
public static func <= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.keyArray.count <= rhs.keyArray.count)
|| (lhs.keyArray.count == rhs.keyArray.count && lhs.value <= rhs.value)
}
public static func >= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.keyArray.count >= rhs.keyArray.count)
|| (lhs.keyArray.count == rhs.keyArray.count && lhs.value >= rhs.value)
}
}
}
public extension Megrez.Compositor {
///
/// - all: 穿
/// - beginAt:
/// - endAt
enum CandidateFetchFilter { case all, beginAt, endAt }
///
///
/// location 1
/// - Parameter location:
/// - Returns:
func fetchCandidates(
at givenLocation: Int? = nil, filter givenFilter: CandidateFetchFilter = .all
) -> [Megrez.KeyValuePaired] {
var result = [Megrez.KeyValuePaired]()
guard !keys.isEmpty else { return result }
var location = max(min(givenLocation ?? cursor, keys.count), 0)
var filter = givenFilter
if filter == .endAt {
if location == keys.count { filter = .all }
location -= 1
}
location = max(min(location, keys.count - 1), 0)
let anchors: [(location: Int, node: Megrez.Node)] = fetchOverlappingNodes(at: location)
let keyAtCursor = keys[location]
anchors.forEach { theAnchor in
let theNode = theAnchor.node
theNode.unigrams.forEach { gram in
switch filter {
case .all:
//
if !theNode.keyArray.contains(keyAtCursor) { return }
case .beginAt:
guard theAnchor.location == location else { return }
case .endAt:
guard theNode.keyArray.last == keyAtCursor else { return }
switch theNode.spanLength {
case 2... where theAnchor.location + theAnchor.node.spanLength - 1 != location: return
default: break
}
}
result.append(.init(keyArray: theNode.keyArray, value: gram.value))
}
}
return result
}
/// 使
///
///
/// - Parameters:
/// - candidate:
/// - location:
/// - overrideType:
/// - Returns:
@discardableResult func overrideCandidate(
_ candidate: Megrez.KeyValuePaired, at location: Int, overrideType: Megrez.Node.OverrideType = .withHighScore
)
-> Bool
{
overrideCandidateAgainst(keyArray: candidate.keyArray, at: location, value: candidate.value, type: overrideType)
}
/// 使
///
///
/// - Parameters:
/// - candidate:
/// - location:
/// - overrideType:
/// - Returns:
@discardableResult func overrideCandidateLiteral(
_ candidate: String,
at location: Int, overrideType: Megrez.Node.OverrideType = .withHighScore
) -> Bool {
overrideCandidateAgainst(keyArray: nil, at: location, value: candidate, type: overrideType)
}
// MARK: Internal implementations.
/// 使
/// - Parameters:
/// - keyArray:
/// - location:
/// - value:
/// - type:
/// - Returns:
internal func overrideCandidateAgainst(keyArray: [String]?, at location: Int, value: String, type: Megrez.Node.OverrideType)
-> Bool
{
let location = max(min(location, keys.count), 0) //
var arrOverlappedNodes: [(location: Int, node: Megrez.Node)] = fetchOverlappingNodes(at: min(keys.count - 1, location))
var overridden: (location: Int, node: Megrez.Node)?
for anchor in arrOverlappedNodes {
if keyArray != nil, anchor.node.keyArray != keyArray { continue }
if !anchor.node.selectOverrideUnigram(value: value, type: type) { continue }
overridden = anchor
break
}
guard let overridden = overridden else { return false } //
(overridden.location ..< min(spans.count, overridden.location + overridden.node.spanLength)).forEach { i in
/// A BC
/// A BC 使 A
/// DEF BC A
arrOverlappedNodes = fetchOverlappingNodes(at: i)
arrOverlappedNodes.forEach { anchor in
if anchor.node == overridden.node { return }
let anchorNodeKeyJoined = anchor.node.joinedKey(by: "\t")
let overriddenNodeKeyJoined = overridden.node.joinedKey(by: "\t")
if !overriddenNodeKeyJoined.has(string: anchorNodeKeyJoined) || !overridden.node.value.has(string: anchor.node.value) {
anchor.node.reset()
return
}
anchor.node.overridingScore /= 4
}
}
return true
}
}