Repo // Importing Megrez v2.0.0 update.

This commit is contained in:
ShikiSuen 2022-08-07 16:18:16 +08:00
parent aaecf097b2
commit 741eee40c0
18 changed files with 990 additions and 1086 deletions

View File

@ -1,11 +1,21 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
/// The namespace for this package.
public enum Megrez {}
public enum Megrez {
public typealias KeyValuePaired = Compositor.Candidate //
}
//
// Megrez Gramambular Lukhnos Liu
// Megrez Compositor Gramambular Swift
//
// Grid:
// Walk:
// Node:
// SpanLength:
// Span:

View File

@ -1,41 +1,91 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
public class Compositor: Grid {
///
///
///
///
///
/// - Remark: Markov HMM
///
///
///
public class Compositor {
///
public enum TypingDirection { case front, rear }
///
private let kDroppedPathScore: Double = -999
///
public enum ResizeBehavior { case expand, shrink }
///
public static var maxSpanLength: Int = 10 { didSet { maxSpanLength = max(6, maxSpanLength) } }
/// -
public static let kDefaultSeparator: String = "-"
///
public var cursor: Int = 0 { didSet { cursor = max(0, min(cursor, readings.count)) } }
///
private(set) var readings: [String] = []
/// 使
private var langModel: LangModelProtocol
public var cursor: Int = 0 { didSet { cursor = max(0, min(cursor, length)) } }
/// -
public var separator = kDefaultSeparator
///
public var width: Int { keys.count }
///
public var walkedNodes: [Node] = []
///
public var length: Int { keys.count }
///
public var isEmpty: Bool { spans.isEmpty && keys.isEmpty }
///
private(set) var keys = [String]()
///
private(set) var spans = [Span]()
/// 使 LangModelRanked
private(set) var langModel: LangModelRanked
/// 0
private(set) var cursorRegionMap: [Int: Int] = .init()
///
private(set) var walkedAnchors: [NodeAnchor] = []
///
/// - Parameter nodes:
public func updateWalkedAnchors(with nodes: [Node]) {
walkedAnchors = nodes.map { Megrez.NodeAnchor(node: $0) }
///
/// - Parameter langModel:
public init(with langModel: LangModelProtocol, separator: String = "-") {
self.langModel = .init(withLM: langModel)
self.separator = separator
}
///
public var joinSeparator: String = "-"
public func clear() {
cursor = 0
keys.removeAll()
spans.removeAll()
walkedNodes.removeAll()
cursorRegionMap.removeAll()
}
///
public var length: Int { readings.count }
///
/// - Parameter key:
/// - Returns:
@discardableResult public func insertKey(_ key: String) -> Bool {
guard !key.isEmpty, key != separator, langModel.hasUnigramsFor(key: key) else { return false }
keys.insert(key, at: cursor)
resizeGrid(at: cursor, do: .expand)
update()
cursor += 1 // update()
return true
}
///
///
/// RearFront
///
/// - Parameter direction:
/// - Returns:
@discardableResult public func dropKey(direction: TypingDirection) -> Bool {
let isBackSpace: Bool = direction == .rear ? true : false
guard cursor != (isBackSpace ? 0 : keys.count) else { return false }
keys.remove(at: cursor - (isBackSpace ? 1 : 0))
cursor -= isBackSpace ? 1 : 0 //
resizeGrid(at: cursor, do: .shrink)
update()
return true
}
///
/// - Parameter direction:
@ -50,21 +100,21 @@ extension Megrez {
guard let currentRegion = cursorRegionMap[cursor] else { return false }
let aRegionForward = max(currentRegion - 1, 0)
let currentRegionBorderRear: Int = walkedAnchors[0..<currentRegion].map(\.spanLength).reduce(0, +)
let currentRegionBorderRear: Int = walkedNodes[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, +)
(currentRegion > walkedNodes.count)
? keys.count : walkedNodes[0...currentRegion].map(\.spanLength).reduce(0, +)
case .rear:
cursor = walkedAnchors[0..<aRegionForward].map(\.spanLength).reduce(0, +)
cursor = walkedNodes[0..<aRegionForward].map(\.spanLength).reduce(0, +)
}
default:
switch direction {
case .front:
cursor = currentRegionBorderRear + walkedAnchors[currentRegion].spanLength
cursor = currentRegionBorderRear + walkedNodes[currentRegion].spanLength
case .rear:
cursor = currentRegionBorderRear
}
@ -72,240 +122,150 @@ extension Megrez {
return true
}
///
/// - Parameters:
/// - lm: Megrez.LangModel
/// - length: 10
/// - separator:
public init(lm: LangModelProtocol, length: Int = 10, separator: String = "-") {
langModel = lm
super.init(spanLengthLimit: abs(length)) //
joinSeparator = separator
}
///
override public func clear() {
super.clear()
cursor = 0
readings.removeAll()
walkedAnchors.removeAll()
}
///
/// - Parameters:
/// - reading:
@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
}
///
///
/// RearFront
/// - Parameter direction:
/// - Returns:
@discardableResult public func dropReading(direction: TypingDirection) -> Bool {
let isBackSpace = direction == .rear
if cursor == (isBackSpace ? 0 : readings.count) {
return false
}
readings.remove(at: cursor - (isBackSpace ? 1 : 0))
cursor -= (isBackSpace ? 1 : 0)
resizeGridByOneAt(location: cursor, to: .shrink)
build()
return true
}
/// X
///
///
///
@discardableResult public func removeHeadReadings(count: Int) -> Bool {
let count = abs(count) //
if count > length { return false }
for _ in 0..<count {
cursor = max(cursor - 1, 0)
if !readings.isEmpty {
readings.removeFirst()
resizeGridByOneAt(location: 0, to: .shrink)
}
build()
}
return true
}
///
/// - 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:
/// - mass: 0
/// - joinedPhrase: 使
/// - longPhrases: 使
/// - Returns:
private func reverseWalk(
at location: Int,
mass: Double = 0.0,
joinedPhrase: String = "",
longPhrases: [String] = .init()
) -> [NodeAnchor] {
let location = abs(location) //
if location == 0 || location > width {
return .init()
}
var paths = [[NodeAnchor]]()
let nodes = nodesEndingAt(location: location).stableSorted {
$0.node.score > $1.node.score
}
guard !nodes.isEmpty else { return .init() } //
if nodes[0].node.score >= Node.kSelectedCandidateScore {
// 使
var theAnchor = nodes[0]
theAnchor.mass = mass + nodes[0].node.score
var path: [NodeAnchor] = reverseWalk(
at: location - theAnchor.spanLength, mass: theAnchor.mass
)
path.insert(theAnchor, at: 0)
paths.append(path)
} else if !longPhrases.isEmpty {
var path = [NodeAnchor]()
for theAnchor in nodes {
var theAnchor = theAnchor
let joinedValue = theAnchor.node.currentPair.value + joinedPhrase
//
// /////////使
//
if longPhrases.contains(joinedValue) {
theAnchor.mass = kDroppedPathScore
path.insert(theAnchor, at: 0)
paths.append(path)
continue
/// GraphViz
public var dumpDOT: String {
var strOutput = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n"
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.value);\n"
}
theAnchor.mass = mass + theAnchor.node.score
path = reverseWalk(
at: location - theAnchor.spanLength,
mass: theAnchor.mass,
joinedPhrase: (joinedValue.count >= longPhrases[0].count) ? "" : joinedValue,
longPhrases: .init()
)
path.insert(theAnchor, at: 0)
paths.append(path)
}
} else {
//
var longPhrases = [String]()
for theAnchor in nodes.lazy.filter({ $0.spanLength > 1 }) {
longPhrases.append(theAnchor.node.currentPair.value)
}
longPhrases = longPhrases.stableSorted {
$0.count > $1.count
}
for theAnchor in nodes {
var theAnchor = theAnchor
theAnchor.mass = mass + theAnchor.node.score
var path = [NodeAnchor]()
path = reverseWalk(
at: location - theAnchor.spanLength, mass: theAnchor.mass,
joinedPhrase: (theAnchor.spanLength > 1) ? "" : theAnchor.node.currentPair.value,
longPhrases: .init()
)
path.insert(theAnchor, at: 0)
paths.append(path)
strOutput += "\(np.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.value + " -> " + dn.value + ";\n"
}
}
guard (p + ni) == spans.count else { continue }
strOutput += np.value + " -> EOS;\n"
}
}
guard !paths.isEmpty else {
return .init()
}
var result: [NodeAnchor] = paths[0]
for neta in paths.lazy.filter({
$0.last!.mass > result.last!.mass
}) {
result = neta
}
return result // walk()
}
private func build() {
let itrBegin: Int =
(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 = readings[p..<(p + q)]
let combinedReading: String = join(slice: arrSlice, separator: joinSeparator)
if hasMatchedNode(location: p, spanLength: q, key: combinedReading) { continue }
let unigrams: [Unigram] = langModel.unigramsFor(key: combinedReading)
if unigrams.isEmpty { continue }
let n: Node = .init(key: combinedReading, spanLength: q, unigrams: unigrams)
insertNode(node: n, location: p, spanLength: q)
}
}
}
private func join(slice arrSlice: ArraySlice<String>, separator: String) -> String {
arrSlice.joined(separator: separator)
}
internal func updateCursorJumpingTables(_ anchors: [NodeAnchor]) {
var cursorRegionMapDict = [Int: Int]()
cursorRegionMapDict[-1] = 0 //
var counter = 0
for (i, anchor) in anchors.enumerated() {
for _ in 0..<anchor.spanLength {
cursorRegionMapDict[counter] = i
counter += 1
}
}
cursorRegionMapDict[counter] = anchors.count
cursorRegionMap = cursorRegionMapDict
strOutput += "EOS;\n}\n"
return strOutput
}
}
}
// MARK: - Stable Sort Extension
// MARK: - Internal Methods
// Reference: https://stackoverflow.com/a/50545761/4162914
extension Megrez.Compositor {
// MARK: Internal methods for maintaining the grid.
extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameters:
/// - location:
/// - action:
func resizeGrid(at location: Int, do action: ResizeBehavior) {
let location = max(min(location, spans.count), 0) //
switch action {
case .expand:
spans.insert(Span(), at: location)
if [0, spans.count].contains(location) { return }
case .shrink:
if spans.count == location { return }
spans.remove(at: location)
}
dropWreckedNodes(at: location)
}
/// resizeGrid()
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
fileprivate 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))
///
/// ```
/// Span Index 0 1 2 3
/// (---)
/// (-------)
/// (-----------)
/// ```
/// 2 (SpanIndex = 2) :
/// ```
/// Span Index 0 1 2 3 4
/// (---)
/// (XXX? ?XXX) <-
/// (XXXXXXX? ?XXX) <-
/// ```
///
/// ```
/// Span Index 0 1 2 3
/// (---)
/// (-------)
/// (-----------)
/// ```
/// 2 :
/// ```
/// Span Index 0 1 2 3 4
/// (---)
/// (XXX? <-
/// (XXXXXXX? <-
/// ```
/// - Parameter location:
func dropWreckedNodes(at location: Int) {
let location = max(min(location, spans.count), 0) //
guard !spans.isEmpty else { return }
let affectedLength = Megrez.Compositor.maxSpanLength - 1
let begin = max(0, location - affectedLength)
guard location >= begin else { return }
for i in begin..<location {
spans[i].dropNodesOfOrBeyond(length: location - i + 1)
}
}
@discardableResult func insertNode(_ node: Node, at location: Int) -> Bool {
let location = max(min(location, spans.count - 1), 0) //
spans[location].append(node: node)
return true
}
func getJointKey(range: Range<Int>) -> String {
// contains macOS 13 Ventura
guard range.upperBound <= keys.count, range.lowerBound >= 0 else { return "" }
return keys[range].joined(separator: separator)
}
func getJointKeyArray(range: Range<Int>) -> [String] {
// contains macOS 13 Ventura
guard range.upperBound <= keys.count, range.lowerBound >= 0 else { return [] }
return keys[range].map { String($0) }
}
func hasNode(at location: Int, length: Int, key: String) -> Bool {
let location = max(min(location, spans.count), 0) //
guard let node = spans[location].nodeOf(length: length) else { return false }
return key == node.key
}
func update() {
let maxSpanLength = Megrez.Compositor.maxSpanLength
let range = max(0, cursor - maxSpanLength)..<min(cursor + maxSpanLength, keys.count)
for position in range {
for theLength in 1...min(maxSpanLength, range.upperBound - position) {
let jointKeyArray = getJointKeyArray(range: position..<(position + theLength))
let jointKey = getJointKey(range: position..<(position + theLength))
if hasNode(at: position, length: theLength, key: jointKey) { continue }
let unigrams = langModel.unigramsFor(key: jointKey)
guard !unigrams.isEmpty else { continue }
insertNode(
.init(keyArray: jointKeyArray, spanLength: theLength, unigrams: unigrams, keySeparator: separator),
at: position
)
}
.map(\.element)
}
}
func updateCursorJumpingTables(_ walkedNodes: [Node]) {
var cursorRegionMapDict = [Int: Int]()
cursorRegionMapDict[-1] = 0 //
var counter = 0
for (i, anchor) in walkedNodes.enumerated() {
for _ in 0..<anchor.spanLength {
cursorRegionMapDict[counter] = i
counter += 1
}
}
cursorRegionMapDict[counter] = walkedNodes.count
cursorRegionMap = cursorRegionMapDict
}
}

View File

@ -1,250 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
public class Grid {
///
public enum ResizeBehavior { case expand, shrink }
///
private(set) var spans: [Megrez.SpanUnit]
///
private(set) var maxBuildSpanLength = 10
///
public var width: Int { spans.count }
///
public var isEmpty: Bool { spans.isEmpty }
///
public init(spanLengthLimit: Int = 10) {
maxBuildSpanLength = spanLengthLimit
spans = [Megrez.SpanUnit]()
}
///
public func clear() {
spans.removeAll()
}
///
/// - Parameters:
/// - node:
/// - location:
/// - spanLength:
public func insertNode(node: Node, location: Int, spanLength: Int) {
let location = abs(location) //
let spanLength = abs(spanLength) //
if location >= spans.count {
let diff = location - spans.count + 1
for _ in 0..<diff {
spans.append(SpanUnit())
}
}
spans[location].insert(node: node, length: spanLength)
}
///
/// - Parameters:
/// - location:
/// - spanLength:
/// - key:
public func hasMatchedNode(location: Int, spanLength: Int, key: String) -> Bool {
let location = abs(location) //
let spanLength = abs(spanLength) //
if location > spans.count {
return false
}
let n = spans[location].nodeOf(length: spanLength)
return n != nil && key == n?.key
}
///
/// - Parameters:
/// - location:
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 {
//
spans[i].dropNodesBeyond(length: location - i)
}
}
///
/// - Parameters:
/// - location:
public func nodesBeginningAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if location >= spans.count { return results }
// spans location 0
let span = spans[location]
for i in 1...maxBuildSpanLength {
if let np = span.nodeOf(length: i) {
results.append(.init(node: np))
}
}
return results //
}
///
/// - Parameters:
/// - location:
public func nodesEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if spans.isEmpty || location > spans.count { return results }
for i in 0..<location {
let span = spans[i]
if i + span.maxLength < location { continue }
if let np = span.nodeOf(length: location - i) {
results.append(.init(node: np))
}
}
return results //
}
///
/// - Parameters:
/// - location:
public func nodesCrossingOrEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if spans.isEmpty || location > spans.count { return results }
for i in 0..<location {
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.nodeOf(length: j) {
results.append(.init(node: np))
}
}
}
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 nodesCrossingOrEndingAt(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 fixNodeWithCandidate(_ pair: KeyValuePaired, at location: Int) -> NodeAnchor {
let location = abs(location) //
var node = NodeAnchor()
for theAnchor in nodesCrossingOrEndingAt(location: location) {
let candidates = theAnchor.node.candidates
//
theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() {
if candidate == pair {
theAnchor.node.selectCandidateAt(index: i)
node = theAnchor
break
}
}
}
return node
}
///
/// - Parameters:
/// - location:
/// - value:
/// - overridingScore:
public func overrideNodeScoreForSelectedCandidate(location: Int, value: String, overridingScore: Double) {
let location = abs(location) //
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.selectFloatingCandidateAt(index: i, score: overridingScore)
break
}
}
}
}
}
}
// MARK: - DumpDOT-related functions.
extension Megrez.Grid {
/// GraphViz
public var dumpDOT: String {
var strOutput = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n"
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.currentPair.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"
}
}
guard (p + ni) == spans.count else { continue }
strOutput += np.currentPair.value + " -> EOS;\n"
}
}
strOutput += "EOS;\n}\n"
return strOutput
}
}

View File

@ -0,0 +1,107 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez.Compositor {
///
/// 使 Cormen 2001
///
///
/// `G = (V, E)` `O(|V|+|E|)` `G`
/// 使
/// - Returns:
@discardableResult public func walk() -> ([Node], Bool) {
var result = [Node]()
defer {
walkedNodes = result
updateCursorJumpingTables(walkedNodes)
}
guard !spans.isEmpty else { return (result, true) }
var vertexSpans = [VertexSpan]()
for _ in spans {
vertexSpans.append(.init())
}
for (i, span) in spans.enumerated() {
for j in 1...span.maxLength {
if let p = span.nodeOf(length: j) {
vertexSpans[i].append(.init(node: p))
}
}
}
let terminal = Vertex(node: .init(keyArray: ["_TERMINAL_"], keySeparator: separator))
for (i, vertexSpan) in vertexSpans.enumerated() {
for vertex in vertexSpan {
let nextVertexPosition = i + vertex.node.spanLength
if nextVertexPosition == vertexSpans.count {
vertex.edges.append(terminal)
continue
}
for nextVertex in vertexSpans[nextVertexPosition] {
vertex.edges.append(nextVertex)
}
}
}
let root = Vertex(node: .init(keyArray: ["_ROOT_"], keySeparator: separator))
root.distance = 0
root.edges.append(contentsOf: vertexSpans[0])
var ordered: [Vertex] = topologicalSort(root: root)
for (j, neta) in ordered.reversed().enumerated() {
for (k, _) in neta.edges.enumerated() {
relax(u: neta, v: &neta.edges[k])
}
ordered[j] = neta
}
var walked = [Node]()
var totalKeyLength = 0
var it = terminal
while let itPrev = it.prev {
walked.append(itPrev.node)
it = itPrev
totalKeyLength += it.node.spanLength
}
guard totalKeyLength == keys.count else {
print("!!! ERROR A")
return (result, false)
}
guard walked.count >= 2 else {
print("!!! ERROR B")
return (result, false)
}
walked = walked.reversed()
walked.removeFirst()
result = walked
return (result, true)
}
}
// MARK: - Stable Sort Extension
// Reference: https://stackoverflow.com/a/50545761/4162914
extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
fileprivate 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)
}
}

View File

@ -0,0 +1,181 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// 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
extension Megrez.Compositor {
public struct Candidate: Equatable, Hashable, Comparable, CustomStringConvertible {
///
public var key: String
///
public var value: String
///
public var description: String { "(" + key + "," + value + ")" }
/// false
public var isValid: Bool { !key.isEmpty && !value.isEmpty }
/// ()
public var toNGramKey: String { !isValid ? "()" : "(" + key + "," + value + ")" }
///
/// - Parameters:
/// - key:
/// - value:
public init(key: String = "", value: String = "") {
self.key = key
self.value = value
}
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(value)
}
public static func == (lhs: Candidate, rhs: Candidate) -> Bool {
lhs.key == rhs.key && lhs.value == rhs.value
}
public static func < (lhs: Candidate, rhs: Candidate) -> Bool {
(lhs.key.count < rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value < rhs.value)
}
public static func > (lhs: Candidate, rhs: Candidate) -> Bool {
(lhs.key.count > rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value > rhs.value)
}
public static func <= (lhs: Candidate, rhs: Candidate) -> Bool {
(lhs.key.count <= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value <= rhs.value)
}
public static func >= (lhs: Candidate, rhs: Candidate) -> Bool {
(lhs.key.count >= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value >= rhs.value)
}
}
public enum CandidateFetchFilter { case all, beginAt, endAt }
///
///
/// location - 1
/// - Parameter location:
/// - Returns:
public func fetchCandidates(at location: Int, filter: CandidateFetchFilter = .all) -> [Candidate] {
var result = [Candidate]()
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.key.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(key: theNode.key, value: gram.value))
}
}
return result
}
/// 使
///
///
/// - Parameters:
/// - candidate:
/// - location:
/// - overrideType:
/// - Returns:
@discardableResult public func overrideCandidate(
_ candidate: Candidate, at location: Int, overrideType: Node.OverrideType = .withHighScore
)
-> Bool
{
overrideCandidateAgainst(key: candidate.key, at: location, value: candidate.value, type: overrideType)
}
/// 使
///
///
/// - Parameters:
/// - candidate:
/// - location:
/// - overrideType:
/// - Returns:
@discardableResult public func overrideCandidateLiteral(
_ candidate: String,
at location: Int, overrideType: Node.OverrideType = .withHighScore
) -> Bool {
overrideCandidateAgainst(key: nil, at: location, value: candidate, type: overrideType)
}
// MARK: Internal implementations.
/// 使
/// - Parameters:
/// - key:
/// - location:
/// - value:
/// - type:
/// - Returns:
internal func overrideCandidateAgainst(key: String?, at location: Int, value: String, type: Node.OverrideType)
-> Bool
{
let location = max(min(location, keys.count), 0) //
var arrOverlappedNodes: [NodeAnchor] = fetchOverlappingNodes(at: min(keys.count - 1, location))
var overridden: NodeAnchor?
for anchor in arrOverlappedNodes {
if let key = key, anchor.node.key != key { continue }
if anchor.node.selectOverrideUnigram(value: value, type: type) {
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 }
anchor.node.reset()
}
}
return true
}
}
// MARK: - Stable Sort Extension
// Reference: https://stackoverflow.com/a/50545761/4162914
extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
fileprivate 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)
}
}

View File

@ -1,78 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct NodeAnchor: Hashable {
///
public var isEmpty: Bool { node.key.isEmpty }
///
public var node: Node = .init()
///
public var spanLength: Int { node.spanLength }
///
public var scoreForSort: Double { node.score }
///
public var mass: Double = 0.0
///
public var unigrams: [Unigram] { node.unigrams }
///
public var bigrams: [Bigram] { node.bigrams }
///
public var key: String { node.key }
///
public init(node: Node = .init(), mass: Double? = nil) {
self.node = node
self.mass = mass ?? self.node.score
}
///
public func hash(into hasher: inout Hasher) {
hasher.combine(node)
hasher.combine(mass)
}
///
public var description: String {
var stream = ""
stream += "{@(" + String(spanLength) + "),"
if node.key.isEmpty {
stream += node.description
} else {
stream += "null"
}
stream += "}"
return stream
}
}
}
// MARK: - Array Extensions.
extension Array where Element == Megrez.NodeAnchor {
///
public var description: String {
var arrOutputContent = [""]
for anchor in self {
arrOutputContent.append(anchor.description)
}
return arrOutputContent.joined(separator: "<-")
}
///
public var values: [String] {
map(\.node.currentPair.value)
}
///
public var keys: [String] {
map(\.node.currentPair.key)
}
}

View File

@ -1,63 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct SpanUnit {
///
private var lengthNodeMap: [Int: Megrez.Node] = [:]
///
private(set) var maxLength: Int = 0
///
mutating func clear() {
lengthNodeMap.removeAll()
maxLength = 0
}
///
/// - Parameters:
/// - node:
/// - length:
mutating func insert(node: Node, length: Int) {
let length = abs(length) //
lengthNodeMap[length] = node
maxLength = max(maxLength, length)
}
///
/// - Parameters:
/// - length:
mutating func dropNodesBeyond(length: Int) {
let length = abs(length) //
if length > maxLength { return }
var lenMax = 0
var removalList: [Int: Megrez.Node] = [:]
for key in lengthNodeMap.keys {
if key > length {
removalList[key] = lengthNodeMap[key]
} else {
lenMax = max(lenMax, key)
}
}
for key in removalList.keys {
lengthNodeMap.removeValue(forKey: key)
}
maxLength = lenMax
}
///
/// - Parameters:
/// - length:
public func nodeOf(length: Int) -> Node? {
// Abs()
lengthNodeMap.keys.contains(abs(length)) ? lengthNodeMap[abs(length)] : nil
}
}
}

View File

@ -1,172 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
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 && lhs.spanLength == rhs.spanLength
}
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(score)
hasher.combine(unigrams)
hasher.combine(bigrams)
hasher.combine(spanLength)
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(set) var unigrams: [Unigram]
///
private(set) var bigrams: [Bigram]
///
public var spanLength: Int = 0
///
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 currentPair: KeyValuePaired {
selectedUnigramIndex >= unigrams.count ? KeyValuePaired() : candidates[selectedUnigramIndex]
}
///
public var highestUnigramScore: Double { unigrams.isEmpty ? 0.0 : unigrams[0].score }
///
/// - Parameters:
/// - key:
/// - unigrams:
/// - bigrams:
public init(key: String = "", spanLength: Int = 0, unigrams: [Megrez.Unigram] = [], bigrams: [Megrez.Bigram] = []) {
self.key = key
self.unigrams = unigrams
self.bigrams = bigrams
self.spanLength = spanLength
self.unigrams.sort {
$0.score > $1.score
}
if !self.unigrams.isEmpty {
score = unigrams[0].score
}
for (i, gram) in self.unigrams.enumerated() {
valueUnigramIndexMap[gram.keyValue.value] = i
candidates.append(gram.keyValue)
}
for gram in bigrams.lazy.filter({ [self] in
precedingBigramMap.keys.contains($0.precedingKeyValue)
}) {
precedingBigramMap[gram.precedingKeyValue]?.append(gram)
}
}
///
/// - Parameters:
/// - precedingKeyValues:
public func primeNodeWith(precedingKeyValues: [KeyValuePaired]) {
var newIndex = selectedUnigramIndex
var max = score
if !isCandidateFixed {
for neta in precedingKeyValues {
let bigrams = precedingBigramMap[neta] ?? []
for bigram in bigrams.lazy.filter({ [self] in
$0.score > max && valueUnigramIndexMap.keys.contains($0.keyValue.value)
}) {
newIndex = valueUnigramIndexMap[bigram.keyValue.value] ?? newIndex
max = bigram.score
}
}
}
score = max
selectedUnigramIndex = newIndex
}
///
/// - Parameters:
/// - index:
/// - fix:
public func selectCandidateAt(index: Int = 0, fix: Bool = false) {
let index = abs(index)
selectedUnigramIndex = index >= unigrams.count ? 0 : index
isCandidateFixed = fix
score = Megrez.Node.kSelectedCandidateScore
}
///
public func resetCandidate() {
selectedUnigramIndex = 0
isCandidateFixed = false
if !unigrams.isEmpty {
score = unigrams[0].score
}
}
///
/// - Parameters:
/// - index:
/// - score:
public func selectFloatingCandidateAt(index: Int, score: Double) {
let index = abs(index) //
selectedUnigramIndex = index >= unigrams.count ? 0 : index
isCandidateFixed = false
self.score = score
}
///
/// - Parameters:
/// - candidate:
public func scoreFor(candidate: String) -> Double {
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

@ -0,0 +1,96 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez.Compositor {
///
public class Span {
private var nodes: [Node?] = []
private(set) var maxLength = 0
private var maxSpanLength: Int { Megrez.Compositor.maxSpanLength }
public init() {
clear()
}
public func clear() {
nodes.removeAll()
for _ in 0..<maxSpanLength {
nodes.append(nil)
}
maxLength = 0
}
///
/// - Parameter node:
/// - Returns:
@discardableResult public func append(node: Node) -> Bool {
guard (1...maxSpanLength).contains(node.spanLength) else {
return false
}
nodes[node.spanLength - 1] = node
maxLength = max(maxLength, node.spanLength)
return true
}
///
/// - Parameter length:
/// - Returns:
@discardableResult public func dropNodesOfOrBeyond(length: Int) -> Bool {
guard (1...maxSpanLength).contains(length) else {
return false
}
for i in length...maxSpanLength {
nodes[i - 1] = nil
}
maxLength = 0
guard length > 1 else { return false }
let maxR = length - 2
for i in 0...maxR {
if nodes[maxR - i] != nil {
maxLength = maxR - i + 1
break
}
}
return true
}
public func nodeOf(length: Int) -> Node? {
guard (1...maxSpanLength).contains(length) else { return nil }
return nodes[length - 1] ?? nil
}
}
// MARK: Internal implementations.
///
/// - Parameter location:
/// - Returns:
func fetchOverlappingNodes(at location: Int) -> [NodeAnchor] {
var results = [NodeAnchor]()
guard !spans.isEmpty, location < spans.count else { return results }
//
for theLocation in 1...spans[location].maxLength {
guard let node = spans[location].nodeOf(length: theLocation) else { continue }
results.append(.init(node: node, spanIndex: location))
}
//
let begin: Int = location - min(location, Megrez.Compositor.maxSpanLength - 1)
for theLocation in begin..<location {
let (A, B): (Int, Int) = {
(
min(location - theLocation + 1, spans[theLocation].maxLength),
max(location - theLocation + 1, spans[theLocation].maxLength)
)
}()
for theLength in A...B {
guard let node = spans[theLocation].nodeOf(length: theLength) else { continue }
results.append(.init(node: node, spanIndex: theLocation))
}
}
return results
}
}

View File

@ -1,43 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
public protocol LangModelProtocol {
///
func unigramsFor(key: String) -> [Megrez.Unigram]
///
func bigramsFor(precedingKey: String, key: String) -> [Megrez.Bigram]
///
func hasUnigramsFor(key: String) -> Bool
}
extension Megrez {
/// 使
open class LangModel: LangModelProtocol {
public init() {}
// Swift
///
open func unigramsFor(key: String) -> [Megrez.Unigram] {
key.isEmpty ? [Megrez.Unigram]() : [Megrez.Unigram]()
}
///
open func bigramsFor(precedingKey: String, key: String) -> [Megrez.Bigram] {
precedingKey == key ? [Megrez.Bigram]() : [Megrez.Bigram]()
}
///
open func hasUnigramsFor(key: String) -> Bool {
key.count != 0
}
}
}

View File

@ -0,0 +1,96 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez.Compositor {
///
///
///
class Vertex {
///
public var prev: Vertex?
///
public var edges = [Vertex]()
///
///
///
/// /
public var distance = -(Double.infinity)
///
public var topologicallySorted = false
public var node: Node
public init(node: Node) {
self.node = node
}
}
///
///
/// (relax) Cormen 2001 Introduction to Algorithms 585
/// - Parameters:
/// - u: v
/// - v:
func relax(u: Vertex, v: inout Vertex) {
/// u w v
let w: Double = v.node.score
///
/// v u ww u w v
/// v
if v.distance < u.distance + w {
v.distance = u.distance + w
v.prev = u
}
}
typealias VertexSpan = [Vertex]
/// topological
/// sort
///
/// 使
///
/// ```
/// func topologicalSort(vertex: Vertex) {
/// for vertexNode in vertex.edges {
/// if !vertexNode.topologicallySorted {
/// dfs(vertexNode, result)
/// vertexNode.topologicallySorted = true
/// }
/// result.append(vertexNode)
/// }
/// }
/// ```
/// Cormen 2001 Introduction to Algorithms
/// - Parameter root:
/// - Returns:
func topologicalSort(root: Vertex) -> [Vertex] {
class State {
var iterIndex: Int
var vertex: Vertex
init(vertex: Vertex, iterIndex: Int = 0) {
self.vertex = vertex
self.iterIndex = iterIndex
}
}
var result = [Vertex]()
var stack = [State]()
stack.append(.init(vertex: root))
while !stack.isEmpty {
let state = stack[stack.count - 1]
let theVertex = state.vertex
if state.iterIndex < state.vertex.edges.count {
let newVertex = state.vertex.edges[state.iterIndex]
state.iterIndex += 1
if !newVertex.topologicallySorted {
stack.append(.init(vertex: newVertex))
continue
}
}
theVertex.topologicallySorted = true
result.append(theVertex)
stack.removeLast()
}
return result
}
}

View File

@ -1,64 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct Bigram: Equatable, CustomStringConvertible, Hashable {
///
public var keyValue: KeyValuePaired
///
public var precedingKeyValue: KeyValuePaired
///
public var score: Double
///
public var description: String {
"(" + keyValue.description + "|" + precedingKeyValue.description + "," + String(score) + ")"
}
///
/// - Parameters:
/// - precedingKeyValue:
/// - keyValue:
/// - score:
public init(precedingKeyValue: KeyValuePaired, keyValue: KeyValuePaired, score: Double) {
self.keyValue = keyValue
self.precedingKeyValue = precedingKeyValue
self.score = score
}
public func hash(into hasher: inout Hasher) {
hasher.combine(keyValue)
hasher.combine(precedingKeyValue)
hasher.combine(score)
// hasher.combine(paired)
}
public static func == (lhs: Bigram, rhs: Bigram) -> Bool {
lhs.precedingKeyValue == rhs.precedingKeyValue && lhs.keyValue == rhs.keyValue && lhs.score == rhs.score
}
public static func < (lhs: Bigram, rhs: Bigram) -> Bool {
lhs.precedingKeyValue < rhs.precedingKeyValue
|| (lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.score < rhs.score))
}
}
}
// MARK: - DumpDOT-related functions.
extension Array where Element == Megrez.Bigram {
///
public var description: String {
var arrOutputContent = [""]
for (index, gram) in enumerated() {
arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.description])
}
return "[" + String(count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}"
}
}

View File

@ -0,0 +1,142 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez.Compositor {
///
///
///
///
/// 2
public class Node: Equatable, Hashable {
///
/// - withNoOverrides:
/// - withTopUnigramScore: 使使
///
/// [("a", -114), ("b", -514), ("c", -1919)]
/// ("c", -114)使
///
/// kOverridingScore
/// - withHighScore: kOverridingScore使
public enum OverrideType: Int {
case withNoOverrides = 0
case withTopUnigramScore = 1
case withHighScore = 2
}
///
/// 0使
/// a b cA B C使
/// c bc
/// A->bc A B 使0
/// A-B 0
/// c
public static let kOverridingScore: Double = 114_514
private(set) var key: String
private(set) var keyArray: [String]
private(set) var spanLength: Int
private(set) var unigrams: [Megrez.Unigram]
private(set) var currentUnigramIndex: Int = 0 {
didSet { currentUnigramIndex = min(max(0, currentUnigramIndex), unigrams.count - 1) }
}
public var currentPair: Megrez.Compositor.Candidate { .init(key: key, value: value) }
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(spanLength)
hasher.combine(unigrams)
hasher.combine(currentUnigramIndex)
hasher.combine(spanLength)
hasher.combine(overrideType)
}
private(set) var overrideType: Node.OverrideType
public static func == (lhs: Node, rhs: Node) -> Bool {
lhs.key == rhs.key && lhs.spanLength == rhs.spanLength
&& lhs.unigrams == rhs.unigrams && lhs.overrideType == rhs.overrideType
}
public init(
keyArray: [String] = [], spanLength: Int = 0, unigrams: [Megrez.Unigram] = [], keySeparator: String = ""
) {
key = keyArray.joined(separator: keySeparator)
self.keyArray = keyArray
self.spanLength = spanLength
self.unigrams = unigrams
overrideType = .withNoOverrides
}
///
/// - Returns:
public var currentUnigram: Megrez.Unigram {
unigrams.isEmpty ? .init() : unigrams[currentUnigramIndex]
}
public var value: String { currentUnigram.value }
public var score: Double {
guard !unigrams.isEmpty else { return 0 }
switch overrideType {
case .withHighScore: return Megrez.Compositor.Node.kOverridingScore
case .withTopUnigramScore: return unigrams[0].score
default: return currentUnigram.score
}
}
public var isOverriden: Bool {
overrideType != .withNoOverrides
}
public func reset() {
currentUnigramIndex = 0
overrideType = .withNoOverrides
}
public func selectOverrideUnigram(value: String, type: Node.OverrideType) -> Bool {
guard type != .withNoOverrides else {
return false
}
for (i, gram) in unigrams.enumerated() {
if value != gram.value { continue }
currentUnigramIndex = i
overrideType = type
return true
}
return false
}
}
}
extension Megrez.Compositor {
///
///
/// Gramambular NodeInSpan
public struct NodeAnchor: Hashable {
let node: Megrez.Compositor.Node
let spanIndex: Int //
var spanLength: Int { node.spanLength }
var unigrams: [Megrez.Unigram] { node.unigrams }
var key: String { node.key }
var value: String { node.value }
///
public func hash(into hasher: inout Hasher) {
hasher.combine(node)
hasher.combine(spanIndex)
}
}
}
// MARK: - Array Extensions.
extension Array where Element == Megrez.Compositor.Node {
///
public var values: [String] { map(\.value) }
///
public var keys: [String] { map(\.key) }
}

View File

@ -1,57 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct Unigram: Equatable, CustomStringConvertible, Hashable {
///
public var keyValue: KeyValuePaired
///
public var score: Double
///
public var description: String {
"(" + keyValue.description + "," + String(score) + ")"
}
///
/// - Parameters:
/// - keyValue:
/// - score:
public init(keyValue: KeyValuePaired, score: Double) {
self.keyValue = keyValue
self.score = score
}
public func hash(into hasher: inout Hasher) {
hasher.combine(keyValue)
hasher.combine(score)
}
public static func == (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.keyValue == rhs.keyValue && lhs.score == rhs.score
}
public static func < (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.score < rhs.score)
}
}
}
// MARK: - DumpDOT-related functions.
extension Array where Element == Megrez.Unigram {
///
public var description: String {
var arrOutputContent = [""]
for (index, gram) in enumerated() {
arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.description])
}
return "[" + String(count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}"
}
}

View File

@ -1,58 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct KeyValuePaired: Equatable, Hashable, Comparable, CustomStringConvertible {
///
public var key: String
///
public var value: String
///
public var description: String { "(" + key + "," + value + ")" }
/// false
public var isValid: Bool { !key.isEmpty && !value.isEmpty }
/// ()
public var toNGramKey: String { !isValid ? "()" : "(" + key + "," + value + ")" }
///
/// - Parameters:
/// - key:
/// - value:
public init(key: String = "", value: String = "") {
self.key = key
self.value = value
}
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(value)
}
public static func == (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
lhs.key == rhs.key && lhs.value == rhs.value
}
public static func < (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.key.count < rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value < rhs.value)
}
public static func > (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.key.count > rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value > rhs.value)
}
public static func <= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.key.count <= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value <= rhs.value)
}
public static func >= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.key.count >= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value >= rhs.value)
}
}
}

View File

@ -0,0 +1,61 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// 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 protocol LangModelProtocol {
///
func unigramsFor(key: String) -> [Megrez.Unigram]
///
func hasUnigramsFor(key: String) -> Bool
}
extension Megrez.Compositor {
///
public class LangModelRanked: LangModelProtocol {
private let langModel: LangModelProtocol
///
/// - Parameter withLM:
public init(withLM: LangModelProtocol) {
langModel = withLM
}
///
/// - Parameter key:
/// - Returns:
public func unigramsFor(key: String) -> [Megrez.Unigram] {
langModel.unigramsFor(key: key).stableSorted { $0.score > $1.score }
}
///
/// - Parameter key:
/// - Returns:
public func hasUnigramsFor(key: String) -> Bool {
langModel.hasUnigramsFor(key: key)
}
}
}
// MARK: - Stable Sort Extension
// Reference: https://stackoverflow.com/a/50545761/4162914
extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
fileprivate 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)
}
}

View File

@ -0,0 +1,40 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez {
///
@frozen public struct Unigram: Equatable, CustomStringConvertible, Hashable {
///
public var value: String
///
public var score: Double
///
public var description: String {
"(" + value.description + "," + String(score) + ")"
}
///
/// - Parameters:
/// - value:
/// - score:
public init(value: String = "", score: Double = 0) {
self.value = value
self.score = score
}
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
hasher.combine(score)
}
public static func == (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.value == rhs.value && lhs.score == rhs.score
}
public static func < (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.value < rhs.value || (lhs.value == rhs.value && lhs.score < rhs.score)
}
}
}

View File

@ -12,18 +12,17 @@
5B21176C287539BB000443A9 /* ctlInputMethod_HandleStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */; };
5B21176E28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */; };
5B21177028753B9D000443A9 /* ctlInputMethod_Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */; };
5B2170E0289FACAD00BE7304 /* 7_LangModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170D7289FACAC00BE7304 /* 7_LangModel.swift */; };
5B2170E1289FACAD00BE7304 /* 0_Megrez.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170D8289FACAC00BE7304 /* 0_Megrez.swift */; };
5B2170E2289FACAD00BE7304 /* 8_Unigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170D9289FACAC00BE7304 /* 8_Unigram.swift */; };
5B2170E3289FACAD00BE7304 /* 3_Candidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DA289FACAC00BE7304 /* 3_Candidate.swift */; };
5B2170E4289FACAD00BE7304 /* 2_Walker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DB289FACAC00BE7304 /* 2_Walker.swift */; };
5B2170E5289FACAD00BE7304 /* 6_Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DC289FACAC00BE7304 /* 6_Node.swift */; };
5B2170E6289FACAD00BE7304 /* 4_Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DD289FACAC00BE7304 /* 4_Span.swift */; };
5B2170E7289FACAD00BE7304 /* 1_Compositor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DE289FACAC00BE7304 /* 1_Compositor.swift */; };
5B2170E8289FACAD00BE7304 /* 5_Vertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DF289FACAC00BE7304 /* 5_Vertex.swift */; };
5B242403284B0D6500520FE4 /* ctlCandidateUniversal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */; };
5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */; };
5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */; };
5B38F59B281E2E49007D5F5D /* 7_KeyValuePaired.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePaired.swift */; };
5B38F59C281E2E49007D5F5D /* 2_Grid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */; };
5B38F59D281E2E49007D5F5D /* 4_Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */; };
5B38F59E281E2E49007D5F5D /* 6_Bigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */; };
5B38F59F281E2E49007D5F5D /* 3_NodeAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */; };
5B38F5A1281E2E49007D5F5D /* 1_Compositor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1515FC0EB100ABF4B3 /* 1_Compositor.swift */; };
5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */; };
5B38F5A3281E2E49007D5F5D /* 3_Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */; };
5B38F5A4281E2E49007D5F5D /* 5_LanguageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */; };
5B3A87BC28597CDB0090E163 /* LMSymbolNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3A87BB28597CDB0090E163 /* LMSymbolNode.swift */; };
5B40730C281672610023DFFF /* lmAssociates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B407309281672610023DFFF /* lmAssociates.swift */; };
5B40730D281672610023DFFF /* lmReplacements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B40730A281672610023DFFF /* lmReplacements.swift */; };
@ -213,6 +212,15 @@
5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleStates.swift; sourceTree = "<group>"; };
5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleDisplay.swift; sourceTree = "<group>"; };
5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Delegates.swift; sourceTree = "<group>"; };
5B2170D7289FACAC00BE7304 /* 7_LangModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 7_LangModel.swift; sourceTree = "<group>"; };
5B2170D8289FACAC00BE7304 /* 0_Megrez.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 0_Megrez.swift; sourceTree = "<group>"; };
5B2170D9289FACAC00BE7304 /* 8_Unigram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 8_Unigram.swift; sourceTree = "<group>"; };
5B2170DA289FACAC00BE7304 /* 3_Candidate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 3_Candidate.swift; sourceTree = "<group>"; };
5B2170DB289FACAC00BE7304 /* 2_Walker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 2_Walker.swift; sourceTree = "<group>"; };
5B2170DC289FACAC00BE7304 /* 6_Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6_Node.swift; sourceTree = "<group>"; };
5B2170DD289FACAC00BE7304 /* 4_Span.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 4_Span.swift; sourceTree = "<group>"; };
5B2170DE289FACAC00BE7304 /* 1_Compositor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 1_Compositor.swift; sourceTree = "<group>"; };
5B2170DF289FACAC00BE7304 /* 5_Vertex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 5_Vertex.swift; sourceTree = "<group>"; };
5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlCandidateUniversal.swift; sourceTree = "<group>"; };
5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = "<group>"; };
5B2F2BB3286216A500B8557B /* vChewingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = vChewingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@ -329,16 +337,6 @@
5BFDF48C27B51867009523B6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Main.strings"; sourceTree = "<group>"; };
6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewing.app; sourceTree = BUILT_PRODUCTS_DIR; };
6A0D4EF515FC0DA600ABF4B3 /* IME-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "IME-Info.plist"; sourceTree = "<group>"; };
6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 6_Bigram.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1515FC0EB100ABF4B3 /* 1_Compositor.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 1_Compositor.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = 0_Megrez.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 2_Grid.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePaired.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 7_KeyValuePaired.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 5_LanguageModel.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 4_Node.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_NodeAnchor.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_Span.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 6_Unigram.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A15B32421A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = "<group>"; };
@ -821,16 +819,15 @@
6A0D4F1315FC0EB100ABF4B3 /* Megrez */ = {
isa = PBXGroup;
children = (
6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */,
6A0D4F1515FC0EB100ABF4B3 /* 1_Compositor.swift */,
6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */,
6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */,
6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */,
6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */,
6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */,
6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */,
6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */,
6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePaired.swift */,
5B2170D8289FACAC00BE7304 /* 0_Megrez.swift */,
5B2170DE289FACAC00BE7304 /* 1_Compositor.swift */,
5B2170DB289FACAC00BE7304 /* 2_Walker.swift */,
5B2170DA289FACAC00BE7304 /* 3_Candidate.swift */,
5B2170DD289FACAC00BE7304 /* 4_Span.swift */,
5B2170DF289FACAC00BE7304 /* 5_Vertex.swift */,
5B2170DC289FACAC00BE7304 /* 6_Node.swift */,
5B2170D7289FACAC00BE7304 /* 7_LangModel.swift */,
5B2170D9289FACAC00BE7304 /* 8_Unigram.swift */,
);
path = Megrez;
sourceTree = "<group>";
@ -1155,8 +1152,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5B38F59D281E2E49007D5F5D /* 4_Node.swift in Sources */,
5B38F5A3281E2E49007D5F5D /* 3_Span.swift in Sources */,
5B40730C281672610023DFFF /* lmAssociates.swift in Sources */,
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
5BA9FD4527FEF3C9002DE248 /* ToolbarItemStyleViewController.swift in Sources */,
@ -1169,22 +1164,25 @@
D4A13D5A27A59F0B003BE359 /* ctlInputMethod_Core.swift in Sources */,
5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */,
5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */,
5B2170E7289FACAD00BE7304 /* 1_Compositor.swift in Sources */,
5B21177028753B9D000443A9 /* ctlInputMethod_Delegates.swift in Sources */,
5B21176E28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift in Sources */,
5B84579F2871AD2200C93B01 /* HotenkaChineseConverter.swift in Sources */,
5B887F302826AEA400B6651E /* lmCoreEX.swift in Sources */,
5BA9FD4627FEF3C9002DE248 /* Container.swift in Sources */,
D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */,
5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */,
5B2170E5289FACAD00BE7304 /* 6_Node.swift in Sources */,
5B949BD92816DC5400D87B5D /* LineReader.swift in Sources */,
D456576E279E4F7B00DF6BC9 /* InputSignal.swift in Sources */,
5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */,
5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */,
5B2170E1289FACAD00BE7304 /* 0_Megrez.swift in Sources */,
5B3A87BC28597CDB0090E163 /* LMSymbolNode.swift in Sources */,
5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */,
5BA9FD4427FEF3C8002DE248 /* SegmentedControlStyleViewController.swift in Sources */,
D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */,
5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */,
5B2170E4289FACAD00BE7304 /* 2_Walker.swift in Sources */,
5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */,
5BA0DF312817857D009E73BB /* lmUserOverride.swift in Sources */,
5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */,
@ -1194,39 +1192,37 @@
5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */,
5B54E743283A7D89001ECBDC /* lmCoreNS.swift in Sources */,
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */,
5B2170E2289FACAD00BE7304 /* 8_Unigram.swift in Sources */,
5B21176C287539BB000443A9 /* ctlInputMethod_HandleStates.swift in Sources */,
5B38F59B281E2E49007D5F5D /* 7_KeyValuePaired.swift in Sources */,
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */,
5B38F5A4281E2E49007D5F5D /* 5_LanguageModel.swift in Sources */,
5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */,
5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */,
5B62A33827AE79CD00A19448 /* StringUtils.swift in Sources */,
5B2170E3289FACAD00BE7304 /* 3_Candidate.swift in Sources */,
5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */,
5B2170E6289FACAD00BE7304 /* 4_Span.swift in Sources */,
5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */,
5BA9FD3E27FEF3C8002DE248 /* Utilities.swift in Sources */,
5B242403284B0D6500520FE4 /* ctlCandidateUniversal.swift in Sources */,
5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */,
5B8457A12871ADBE00C93B01 /* HotenkaCCBridge.swift in Sources */,
5B38F59C281E2E49007D5F5D /* 2_Grid.swift in Sources */,
5B40730D281672610023DFFF /* lmReplacements.swift in Sources */,
5B38F59E281E2E49007D5F5D /* 6_Bigram.swift in Sources */,
5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */,
5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
5B2170E0289FACAD00BE7304 /* 7_LangModel.swift in Sources */,
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */,
5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */,
5BAA8FBE282CAF380066C406 /* SyllableComposer.swift in Sources */,
5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */,
5B2170E8289FACAD00BE7304 /* 5_Vertex.swift in Sources */,
5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */,
5BA9FD4727FEF3C9002DE248 /* PreferencesStyleController.swift in Sources */,
5B949BDB2816DDBC00D87B5D /* LMConsolidator.swift in Sources */,
5B38F59F281E2E49007D5F5D /* 3_NodeAnchor.swift in Sources */,
5BFDF011289635C100417BBC /* ctlCandidateIMK.swift in Sources */,
5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */,
5BA58646289BCFAC0077D02F /* ShiftKeyUpChecker.swift in Sources */,
5BA9FD3F27FEF3C8002DE248 /* Pane.swift in Sources */,
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */,
5B38F5A1281E2E49007D5F5D /* 1_Compositor.swift in Sources */,
5BE377A0288FED8D0037365B /* KeyHandler_HandleComposition.swift in Sources */,
5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */,
);