Megrez // Refactor Vertex(Spans), deprecating NodeAnchor.

This commit is contained in:
ShikiSuen 2023-03-21 21:06:45 +08:00
parent 2063b0e50b
commit 652069b1de
7 changed files with 129 additions and 110 deletions

View File

@ -18,39 +18,27 @@ public extension Megrez.Compositor {
defer { walkedNodes = result } defer { walkedNodes = result }
guard !spans.isEmpty else { return (result, true) } guard !spans.isEmpty else { return (result, true) }
var vertexSpans = [[Vertex]]() var vertexSpans: [[Int: Vertex]] = spans.map(\.asVertexSpan)
spans.forEach { _ in
vertexSpans.append(.init())
}
spans.enumerated().forEach { i, span in
(1 ... max(span.maxLength, 1)).forEach { j in
guard let theNode = span[j] else { return }
vertexSpans[i].append(.init(node: theNode))
}
}
let terminal = Vertex(node: .init(keyArray: ["_TERMINAL_"])) let terminal = Vertex(node: .init(keyArray: ["_TERMINAL_"]))
var root = Vertex(node: .init(keyArray: ["_ROOT_"])) var root = Vertex(node: .init(keyArray: ["_ROOT_"]))
root.distance = 0
vertexSpans.enumerated().forEach { i, vertexSpan in vertexSpans.enumerated().forEach { location, vertexSpan in
vertexSpan.forEach { vertex in vertexSpan.values.forEach { vertex in
let nextVertexPosition = i + vertex.node.spanLength let nextVertexPosition = location + vertex.node.spanLength
if nextVertexPosition == vertexSpans.count { if nextVertexPosition == vertexSpans.count {
vertex.edges.append(terminal) vertex.edges.append(terminal)
return return
} }
vertexSpans[nextVertexPosition].forEach { vertex.edges.append($0) } vertexSpans[nextVertexPosition].values.forEach { vertex.edges.append($0) }
} }
} }
root.distance = 0 root.edges.append(contentsOf: vertexSpans[0].values)
root.edges.append(contentsOf: vertexSpans[0])
var ordered = topologicalSort(root: &root) topologicalSort(root: &root).reversed().forEach { neta in
ordered.reversed().enumerated().forEach { j, neta in neta.edges.indices.forEach { neta.relax(target: &neta.edges[$0]) }
neta.edges.indices.forEach { relax(u: neta, v: &neta.edges[$0]) }
ordered[j] = neta
} }
var iterated = terminal var iterated = terminal
@ -64,7 +52,6 @@ public extension Megrez.Compositor {
} }
// //
ordered.removeAll()
vertexSpans.removeAll() vertexSpans.removeAll()
iterated.destroy() iterated.destroy()
root.destroy() root.destroy()
@ -84,3 +71,14 @@ public extension Megrez.Compositor {
return (result, true) return (result, true)
} }
} }
extension Megrez.SpanUnit {
///
var asVertexSpan: [Int: Megrez.Compositor.Vertex] {
var result = [Int: Megrez.Compositor.Vertex]()
forEach { theKey, theValue in
result[theKey] = .init(node: theValue)
}
return result
}
}

View File

@ -107,10 +107,7 @@ public extension Megrez.Compositor {
location -= 1 location -= 1
} }
location = max(min(location, keys.count - 1), 0) location = max(min(location, keys.count - 1), 0)
let anchors: [NodeAnchor] = fetchOverlappingNodes(at: location).stableSorted { let anchors: [(location: Int, node: Megrez.Node)] = fetchOverlappingNodes(at: location)
//
$0.spanLength > $1.spanLength
}
let keyAtCursor = keys[location] let keyAtCursor = keys[location]
anchors.forEach { theAnchor in anchors.forEach { theAnchor in
let theNode = theAnchor.node let theNode = theAnchor.node
@ -120,11 +117,11 @@ public extension Megrez.Compositor {
// //
if !theNode.keyArray.contains(keyAtCursor) { return } if !theNode.keyArray.contains(keyAtCursor) { return }
case .beginAt: case .beginAt:
guard theAnchor.spanIndex == location else { return } guard theAnchor.location == location else { return }
case .endAt: case .endAt:
guard theNode.keyArray.last == keyAtCursor else { return } guard theNode.keyArray.last == keyAtCursor else { return }
switch theNode.spanLength { switch theNode.spanLength {
case 2... where theAnchor.spanIndex + theAnchor.spanLength - 1 != location: return case 2... where theAnchor.location + theAnchor.node.spanLength - 1 != location: return
default: break default: break
} }
} }
@ -178,8 +175,8 @@ public extension Megrez.Compositor {
-> Bool -> Bool
{ {
let location = max(min(location, keys.count), 0) // let location = max(min(location, keys.count), 0) //
var arrOverlappedNodes: [NodeAnchor] = fetchOverlappingNodes(at: min(keys.count - 1, location)) var arrOverlappedNodes: [(location: Int, node: Megrez.Node)] = fetchOverlappingNodes(at: min(keys.count - 1, location))
var overridden: NodeAnchor? var overridden: (location: Int, node: Megrez.Node)?
for anchor in arrOverlappedNodes { for anchor in arrOverlappedNodes {
if keyArray != nil, anchor.node.keyArray != keyArray { continue } if keyArray != nil, anchor.node.keyArray != keyArray { continue }
if !anchor.node.selectOverrideUnigram(value: value, type: type) { continue } if !anchor.node.selectOverrideUnigram(value: value, type: type) { continue }
@ -189,7 +186,7 @@ public extension Megrez.Compositor {
guard let overridden = overridden else { return false } // guard let overridden = overridden else { return false } //
(overridden.spanIndex ..< min(spans.count, overridden.spanIndex + overridden.node.spanLength)).forEach { i in (overridden.location ..< min(spans.count, overridden.location + overridden.node.spanLength)).forEach { i in
/// A BC /// A BC
/// A BC 使 A /// A BC 使 A
/// DEF BC A /// DEF BC A
@ -208,32 +205,3 @@ public extension Megrez.Compositor {
return true 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)
}
}
// MARK: - Bool Extension (Private)
extension Bool {
var negative: Bool { !self }
}

View File

@ -64,16 +64,15 @@ extension Megrez.Compositor {
/// ///
/// - Parameter location: /// - Parameter location:
/// - Returns: /// - Returns:
func fetchOverlappingNodes(at givenLocation: Int) -> [NodeAnchor] { public func fetchOverlappingNodes(at givenLocation: Int) -> [(location: Int, node: Megrez.Node)] {
var results = [NodeAnchor]() var results = [(location: Int, node: Megrez.Node)]()
let givenLocation = max(0, min(givenLocation, keys.count - 1)) let givenLocation = max(0, min(givenLocation, keys.count - 1))
guard !spans.isEmpty else { return results } guard !spans.isEmpty else { return results }
// //
(1 ... max(spans[givenLocation].maxLength, 1)).forEach { theSpanLength in (1 ... max(spans[givenLocation].maxLength, 1)).forEach { theSpanLength in
guard let node = spans[givenLocation][theSpanLength] else { return } guard let node = spans[givenLocation][theSpanLength] else { return }
guard !node.keyArray.joined().isEmpty else { return } Self.insertAnchor(spanIndex: givenLocation, node: node, to: &results)
results.append(.init(node: node, spanIndex: givenLocation))
} }
// //
@ -83,11 +82,27 @@ extension Megrez.Compositor {
guard A <= B else { return } guard A <= B else { return }
(A ... B).forEach { theLength in (A ... B).forEach { theLength in
guard let node = spans[theLocation][theLength] else { return } guard let node = spans[theLocation][theLength] else { return }
guard !node.keyArray.joined().isEmpty else { return } Self.insertAnchor(spanIndex: theLocation, node: node, to: &results)
results.append(.init(node: node, spanIndex: theLocation))
} }
} }
return results return results
} }
/// fetchOverlappingNodes() 使
private static func insertAnchor(
spanIndex location: Int, node: Megrez.Node,
to targetContainer: inout [(location: Int, node: Megrez.Node)]
) {
guard !node.keyArray.joined().isEmpty else { return }
let anchor = (location: location, node: node)
for i in 0 ... targetContainer.count {
guard !targetContainer.isEmpty else { break }
guard targetContainer[i].node.spanLength <= anchor.node.spanLength else { continue }
targetContainer.insert(anchor, at: i)
return
}
guard targetContainer.isEmpty else { return }
targetContainer.append(anchor)
}
} }

View File

@ -39,23 +39,23 @@ extension Megrez.Compositor {
edges.removeAll() edges.removeAll()
node = .init() node = .init()
} }
}
/// ///
/// ///
/// (relax) Cormen 2001 Introduction to Algorithms 585 /// (relax) Cormen 2001 Introduction to Algorithms 585
/// - Parameters: /// - Remark: (u) target (v)
/// - u: v /// - Parameters:
/// - v: /// - target:
func relax(u: Vertex, v: inout Vertex) { public func relax(target: inout Vertex) {
// u w v // u w v
let w: Double = v.node.score let w: Double = target.node.score
// //
// v u ww u w v // v u ww u w v
// v // v
if v.distance >= u.distance + w { return } if target.distance >= distance + w { return }
v.distance = u.distance + w target.distance = distance + w
v.prev = u target.prev = self
}
} }
/// topological /// topological
@ -65,7 +65,7 @@ extension Megrez.Compositor {
/// ///
/// ``` /// ```
/// func topologicalSort(vertex: Vertex) { /// func topologicalSort(vertex: Vertex) {
/// vertex.edges.forEach {vertexNode in /// vertex.edges.forEach { vertexNode in
/// if !vertexNode.topologicallySorted { /// if !vertexNode.topologicallySorted {
/// dfs(vertexNode, result) /// dfs(vertexNode, result)
/// vertexNode.topologicallySorted = true /// vertexNode.topologicallySorted = true

View File

@ -176,31 +176,6 @@ public extension Megrez {
} }
} }
public extension Megrez.Compositor {
/// Gramambular 2 NodeInSpan
struct NodeAnchor: Hashable {
///
let node: Megrez.Node
///
let spanIndex: Int
///
var spanLength: Int { node.spanLength }
///
var unigrams: [Megrez.Unigram] { node.unigrams }
///
var keyArray: [String] { node.keyArray }
///
var value: String { node.value }
///
/// - Parameter hasher:
public func hash(into hasher: inout Hasher) {
hasher.combine(node)
hasher.combine(spanIndex)
}
}
}
// MARK: - Array Extensions. // MARK: - Array Extensions.
public extension Array where Element == Megrez.Node { public extension Array where Element == Megrez.Node {

View File

@ -0,0 +1,40 @@
// 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 Megrez
// MARK: - Megrez Extensions for Test Purposes Only.
public extension Megrez.Compositor {
///
///
/// location - 1
/// - Remark: node-crossing
///
/// - Parameter location:
/// - Returns:
func fetchCandidatesDeprecated(at location: Int, filter: CandidateFetchFilter = .all) -> [Megrez.KeyValuePaired] {
var result = [Megrez.KeyValuePaired]()
guard !keys.isEmpty else { return result }
let location = max(min(location, keys.count - 1), 0) //
let anchors: [(location: Int, node: Megrez.Node)] = fetchOverlappingNodes(at: location)
let keyAtCursor = keys[location]
anchors.map(\.node).forEach { theNode in
theNode.unigrams.forEach { gram in
switch filter {
case .all:
//
if !theNode.keyArray.contains(keyAtCursor) { return }
case .beginAt:
if theNode.keyArray[0] != keyAtCursor { return }
case .endAt:
if theNode.keyArray.reversed()[0] != keyAtCursor { return }
}
result.append(.init(keyArray: theNode.keyArray, value: gram.value))
}
}
return result
}
}

View File

@ -522,7 +522,7 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(result.values, ["高熱", "🔥", "危險"]) XCTAssertEqual(result.values, ["高熱", "🔥", "危險"])
} }
func test20_Compositor_updateUnigramData() throws { func test20_Compositor_UpdateUnigramData() throws {
let theLM = SimpleLM(input: strSampleData) let theLM = SimpleLM(input: strSampleData)
var compositor = Megrez.Compositor(with: theLM) var compositor = Megrez.Compositor(with: theLM)
compositor.separator = "" compositor.separator = ""
@ -547,7 +547,7 @@ final class MegrezTests: XCTestCase {
XCTAssertEqual(newResult2, ["", ""]) XCTAssertEqual(newResult2, ["", ""])
} }
func test21_Compositor_hardCopy() throws { func test21_Compositor_HardCopy() throws {
let theLM = SimpleLM(input: strSampleData) let theLM = SimpleLM(input: strSampleData)
let rawReadings = "gao1 ke1 ji4 gong1 si1 de5 nian2 zhong1 jiang3 jin1" let rawReadings = "gao1 ke1 ji4 gong1 si1 de5 nian2 zhong1 jiang3 jin1"
var compositorA = Megrez.Compositor(with: theLM) var compositorA = Megrez.Compositor(with: theLM)
@ -580,4 +580,27 @@ final class MegrezTests: XCTestCase {
d = compositor.fetchCandidates(at: 2, filter: .endAt).map(\.keyArray.count).max() ?? 0 d = compositor.fetchCandidates(at: 2, filter: .endAt).map(\.keyArray.count).max() ?? 0
XCTAssertEqual("\(a) \(b) \(c) \(d)", "1 1 2 2") XCTAssertEqual("\(a) \(b) \(c) \(d)", "1 1 2 2")
} }
func test23_Compositor_CheckGetCandidates() throws {
let theLM = SimpleLM(input: strSampleData)
let rawReadings = "gao1 ke1 ji4 gong1 si1 de5 nian2 zhong1 jiang3 jin1"
var compositor = Megrez.Compositor(with: theLM)
rawReadings.split(separator: " ").forEach { key in
compositor.insertKey(key.description)
}
var stack1A = [String]()
var stack1B = [String]()
var stack2A = [String]()
var stack2B = [String]()
for i in 0 ... compositor.keys.count {
stack1A.append(compositor.fetchCandidates(at: i, filter: .beginAt).map(\.value).joined(separator: "-"))
stack1B.append(compositor.fetchCandidates(at: i, filter: .endAt).map(\.value).joined(separator: "-"))
stack2A.append(compositor.fetchCandidatesDeprecated(at: i, filter: .beginAt).map(\.value).joined(separator: "-"))
stack2B.append(compositor.fetchCandidatesDeprecated(at: i, filter: .endAt).map(\.value).joined(separator: "-"))
}
stack1B.removeFirst()
stack2B.removeLast()
XCTAssertEqual(stack1A, stack2A)
XCTAssertEqual(stack1B, stack2B)
}
} }