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

211 lines
9.1 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)
import Foundation
public extension Megrez.Compositor {
///
struct KeyValuePaired: Equatable, Hashable, Comparable, CustomStringConvertible {
///
public var keyArray: [String]
///
public var value: String
///
public var description: String { "(" + keyArray.description + "," + value + ")" }
/// false
public var isValid: Bool { !keyArray.joined().isEmpty && !value.isEmpty }
/// ()
public var toNGramKey: String { !isValid ? "()" : "(" + joinedKey() + "," + value + ")" }
///
/// - Parameters:
/// - keyArray:
/// - 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 = "N/A", value: String = "N/A") {
keyArray = key.isEmpty ? ["N/A"] : key.components(separatedBy: Megrez.Compositor.theSeparator)
self.value = value.isEmpty ? "N/A" : value
}
///
/// - Parameter hasher:
public func hash(into hasher: inout Hasher) {
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.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)
}
}
///
/// - all: 穿
/// - beginAt:
/// - endAt
enum CandidateFetchFilter { case all, beginAt, endAt }
///
///
/// location - 1
/// - Parameter location:
/// - Returns:
func fetchCandidates(at location: Int, filter: CandidateFetchFilter = .all) -> [KeyValuePaired] {
var result = [KeyValuePaired]()
guard !keys.isEmpty else { return result }
let location = max(min(location, keys.count - 1), 0) //
let anchors: [NodeAnchor] = fetchOverlappingNodes(at: location).stableSorted {
//
$0.spanLength > $1.spanLength
}
let keyAtCursor = keys[location]
for theNode in anchors.map(\.node) {
if theNode.keyArray.isEmpty { continue }
for gram in theNode.unigrams {
switch filter {
case .all:
//
if !theNode.keyArray.contains(keyAtCursor) { continue }
case .beginAt:
if theNode.keyArray[0] != keyAtCursor { continue }
case .endAt:
if theNode.keyArray.reversed()[0] != keyAtCursor { continue }
}
result.append(.init(keyArray: theNode.keyArray, value: gram.value))
}
}
return result
}
/// 使
///
///
/// - Parameters:
/// - candidate:
/// - location:
/// - overrideType:
/// - Returns:
@discardableResult func overrideCandidate(
_ candidate: KeyValuePaired, at location: Int, overrideType: 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: 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: 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 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 } //
for i in overridden.spanIndex ..< min(spans.count, overridden.spanIndex + overridden.node.spanLength) {
/// A BC
/// A BC 使 A
/// DEF BC A
arrOverlappedNodes = fetchOverlappingNodes(at: i)
for anchor in arrOverlappedNodes {
if anchor.node == overridden.node { continue }
if !overridden.node.joinedKey(by: "\t").contains(anchor.node.joinedKey(by: "\t"))
|| !overridden.node.value.contains(anchor.node.value)
{
anchor.node.reset()
continue
}
anchor.node.overridingScore /= 4
}
}
return true
}
}
// MARK: - Stable Sort Extension
// Reference: https://stackoverflow.com/a/50545761/4162914
private extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
func stableSorted(
by areInIncreasingOrder: (Element, Element) throws -> Bool
)
rethrows -> [Element]
{
try enumerated()
.sorted { a, b -> Bool in
try areInIncreasingOrder(a.element, b.element)
|| (a.offset < b.offset && !areInIncreasingOrder(b.element, a.element))
}
.map(\.element)
}
}