diff --git a/Megrez.Tests/Megrez.Tests.csproj b/Megrez.Tests/Megrez.Tests.csproj index f7139c7..f825975 100644 --- a/Megrez.Tests/Megrez.Tests.csproj +++ b/Megrez.Tests/Megrez.Tests.csproj @@ -4,7 +4,7 @@ net6.0 enable false - 2.5.4 + 2.6.0 diff --git a/Megrez.Tests/MegrezTests.cs b/Megrez.Tests/MegrezTests.cs index 055c530..9abf7de 100644 --- a/Megrez.Tests/MegrezTests.cs +++ b/Megrez.Tests/MegrezTests.cs @@ -15,10 +15,9 @@ public class MegrezTests : TestDataClass { public void Test01_SpanUnitInternalAbilities() { SimpleLM langModel = new(input: StrSampleData); Compositor.SpanUnit span = new(); - Compositor.Node n1 = - new(keyArray: new() { "gao" }, spanLength: 1, unigrams: langModel.UnigramsFor(new() { "gao1" })); - Compositor.Node n3 = new(keyArray: new() { "gao1", "ke1", "ji4" }, spanLength: 3, - unigrams: langModel.UnigramsFor(new() { "gao1ke1ji4" })); + Node n1 = new(keyArray: new() { "gao" }, spanLength: 1, unigrams: langModel.UnigramsFor(new() { "gao1" })); + Node n3 = new(keyArray: new() { "gao1", "ke1", "ji4" }, spanLength: 3, + unigrams: langModel.UnigramsFor(new() { "gao1ke1ji4" })); Assert.AreEqual(actual: span.MaxLength, expected: 0); span.Append(node: n1); Assert.AreEqual(actual: span.MaxLength, expected: 1); @@ -43,7 +42,7 @@ public void Test01_SpanUnitInternalAbilities() { span.DropNodesOfOrBeyond(length: 1); Assert.AreEqual(actual: span.MaxLength, expected: 0); Assert.AreEqual(actual: span.NodeOf(length: 1), expected: null); - Compositor.Node n114514 = new(new(), 114_514, new()); + Node n114514 = new(new(), 114_514, new()); Assert.IsFalse(span.Append(n114514)); Assert.IsNull(span.NodeOf(length: 0)); Assert.IsNull(span.NodeOf(length: Compositor.MaxSpanLength + 1)); @@ -364,7 +363,7 @@ public void Test15_Compositor_InputTestAndCursorJump() { compositor.InsertKey("jiang3"); compositor.Walk(); compositor.InsertKey("jin1"); - List result = compositor.Walk().WalkedNodes; + List result = compositor.Walk().WalkedNodes; Assert.AreEqual(actual: result.Values(), expected: new List { "高科技", "公司", "的", "年中", "獎金" }); Assert.AreEqual(actual: compositor.Length, expected: 10); compositor.Cursor = 7; @@ -418,7 +417,7 @@ public void Test16_Compositor_InputTest2() { compositor.InsertKey("gao1"); compositor.InsertKey("ke1"); compositor.InsertKey("ji4"); - List result = compositor.Walk().WalkedNodes; + List result = compositor.Walk().WalkedNodes; Assert.AreEqual(actual: result.Values(), expected: new List { "高科技" }); compositor.InsertKey("gong1"); compositor.InsertKey("si1"); @@ -432,7 +431,7 @@ public void Test17_Compositor_OverrideOverlappingNodes() { compositor.InsertKey("gao1"); compositor.InsertKey("ke1"); compositor.InsertKey("ji4"); - List result = compositor.Walk().WalkedNodes; + List result = compositor.Walk().WalkedNodes; Assert.AreEqual(actual: result.Values(), expected: new List { "高科技" }); compositor.Cursor = 0; Assert.IsTrue(compositor.OverrideCandidateLiteral("膏", location: compositor.Cursor)); @@ -466,7 +465,7 @@ public void Test18_Compositor_OverrideReset() { compositor.InsertKey("zhong1"); compositor.InsertKey("jiang3"); compositor.InsertKey("jin1"); - List result = compositor.Walk().WalkedNodes; + List result = compositor.Walk().WalkedNodes; Assert.AreEqual(actual: result.Values(), expected: new List { "年中", "獎金" }); Assert.IsTrue(compositor.OverrideCandidateLiteral("終講", location: 1)); @@ -491,7 +490,7 @@ public void Test19_Compositor_CandidateDisambiguation() { compositor.InsertKey("yan4"); compositor.InsertKey("wei2"); compositor.InsertKey("xian3"); - List? result = compositor.Walk().WalkedNodes; + List? result = compositor.Walk().WalkedNodes; Assert.AreEqual(actual: result.Values(), expected: new List { "高熱", "火焰", "危險" }); Assert.IsTrue(compositor.OverrideCandidate(new(keyArray: new() { "huo3" }, value: "🔥"), location: 2)); @@ -525,4 +524,18 @@ public void Test20_Compositor_updateUnigramData() { string newResult2 = compositor.Walk().WalkedNodes.Values().Joined(separator: ","); Assert.AreEqual(actual: newResult2, expected: "年,中"); } + + [Test] + public void Test21_Compositor_hardCopy() { + SimpleLM theLM = new(input: StrSampleData); + string rawReadings = "gao1 ke1 ji4 gong1 si1 de5 nian2 zhong1 jiang3 jin1"; + Compositor compositorA = new(langModel: theLM, separator: ""); + foreach (string key in rawReadings.Split(separator: " ")) { + compositorA.InsertKey(key); + } + Compositor compositorB = compositorA.HardCopy(); + List resultA = compositorA.Walk().WalkedNodes; + List resultB = compositorB.Walk().WalkedNodes; + Assert.True(resultA.SequenceEqual(resultB)); + } } diff --git a/Megrez.sln b/Megrez.sln index a1a3666..dc69c73 100644 --- a/Megrez.sln +++ b/Megrez.sln @@ -52,6 +52,6 @@ Global $0.DotNetNamingPolicy = $4 $4.DirectoryNamespaceAssociation = PrefixedHierarchical $0.StandardHeader = $5 - version = 2.5.4 + version = 2.6.0 EndGlobalSection EndGlobal diff --git a/Megrez/Megrez.csproj b/Megrez/Megrez.csproj index 9ed285c..33c44cc 100644 --- a/Megrez/Megrez.csproj +++ b/Megrez/Megrez.csproj @@ -3,16 +3,16 @@ net6.0 enable - 2.5.4 + 2.6.0 vChewing.Megrez Shiki Suen Atelier Inmu (c) 2022 and onwards The vChewing Project for Megrez-specific changes; (c) 2022 and onwards Lukhnos Liu for upstream contents. https://github.com/ShikiSuen/MegrezNT zh-TW - 2.5.4 - 2.5.4 - 2.5.4 + 2.6.0 + 2.6.0 + 2.6.0 Megrez True README.md diff --git a/Megrez/src/1_Compositor.cs b/Megrez/src/1_Compositor.cs index 2cdb4d7..45361a7 100644 --- a/Megrez/src/1_Compositor.cs +++ b/Megrez/src/1_Compositor.cs @@ -172,6 +172,37 @@ public Compositor(LangModelProtocol langModel, string separator = "-") { Spans = new(); } + /// + /// 以指定組字器生成拷貝。 + /// + /// + /// 因為 Node 不是 Struct,所以會在 Compositor 被拷貝的時候無法被真實複製。 + /// 這樣一來,Compositor 複製品當中的 Node 的變化會被反應到原先的 Compositor 身上。 + /// 這在某些情況下會造成意料之外的混亂情況,所以需要引入一個拷貝用的建構子。 + /// + /// + public Compositor(Compositor compositor) { + _theLangModel = compositor.TheLangModel; + _cursor = compositor.Cursor; + _marker = compositor.Marker; + Keys = compositor.Keys; + Spans = new(); + Separator = compositor.Separator; + foreach (Node walkedNode in compositor.WalkedNodes) WalkedNodes.Add(walkedNode.Copy()); + foreach (SpanUnit span in compositor.Spans) Spans.Add(span.HardCopy()); + } + + /// + /// 生成自身的拷貝。 + /// + /// + /// 因為 Node 不是 Struct,所以會在 Compositor 被拷貝的時候無法被真實複製。 + /// 這樣一來,Compositor 複製品當中的 Node 的變化會被反應到原先的 Compositor 身上。 + /// 這在某些情況下會造成意料之外的混亂情況,所以需要引入一個拷貝用的建構子。 + /// + /// 拷貝。 + public Compositor HardCopy() => new(compositor: this); + /// /// 重置包括游標在內的各項參數,且清空各種由組字器生成的內部資料。 /// 且將已經被插入的索引鍵陣列與幅位單元陣列(包括其內的節點)全部清空。 diff --git a/Megrez/src/3_KeyValuePaired.cs b/Megrez/src/3_KeyValuePaired.cs index c60a17f..546d330 100644 --- a/Megrez/src/3_KeyValuePaired.cs +++ b/Megrez/src/3_KeyValuePaired.cs @@ -8,142 +8,142 @@ using System.Linq; namespace Megrez { -public partial struct Compositor { +/// +/// 鍵值配對,乃索引鍵陣列與讀音的配對單元。 +/// +public struct KeyValuePaired { /// - /// 鍵值配對,乃索引鍵陣列與讀音的配對單元。 + /// 索引鍵陣列。一般情況下用來放置讀音等可以用來作為索引的內容。 /// - public struct KeyValuePaired { - /// - /// 索引鍵陣列。一般情況下用來放置讀音等可以用來作為索引的內容。 - /// - public List KeyArray { get; } - /// - /// 資料值。 - /// - public string Value { get; } - /// - /// 初期化一組鍵值配對。 - /// - /// 索引鍵陣列。一般情況下用來放置讀音等可以用來作為索引的內容。 - /// 資料值。 - public KeyValuePaired(List keyArray, string value) { - KeyArray = keyArray; - Value = value; - } - /// - /// 初期化一組鍵值配對。 - /// - /// 索引鍵。一般情況下用來放置讀音等可以用來作為索引的內容。 - /// 資料值。 - public KeyValuePaired(string key, string value) { - KeyArray = key.Split(TheSeparator.ToCharArray()).ToList(); - Value = value; - } - - /// - /// 將索引鍵按照給定的分隔符銜接成一個字串。 - /// - /// 給定的分隔符,預設值為 。 - /// 已經銜接完畢的字串。 - public string JoinedKey(string? separator = null) => string.Join(separator ?? TheSeparator, KeyArray.ToArray()); + public List KeyArray { get; } + /// + /// 資料值。 + /// + public string Value { get; } + /// + /// 初期化一組鍵值配對。 + /// + /// 索引鍵陣列。一般情況下用來放置讀音等可以用來作為索引的內容。 + /// 資料值。 + public KeyValuePaired(List keyArray, string value) { + KeyArray = keyArray; + Value = value; + } + /// + /// 初期化一組鍵值配對。 + /// + /// 索引鍵。一般情況下用來放置讀音等可以用來作為索引的內容。 + /// 資料值。 + public KeyValuePaired(string key, string value) { + KeyArray = key.Split(Compositor.TheSeparator.ToCharArray()).ToList(); + Value = value; + } - /// - /// 判斷當前鍵值配對是否合規。如果鍵與值有任一為空,則結果為 false。 - /// - public bool IsValid => !string.IsNullOrEmpty(JoinedKey()) && !string.IsNullOrEmpty(Value); + /// + /// 將索引鍵按照給定的分隔符銜接成一個字串。 + /// + /// 給定的分隔符,預設值為 。 + /// 已經銜接完畢的字串。 + public string JoinedKey(string? separator = null) => string.Join(separator ?? Compositor.TheSeparator, + KeyArray.ToArray()); - /// - /// - /// - /// - /// - public override bool Equals(object obj) { - return obj is KeyValuePaired pair && JoinedKey().SequenceEqual(pair.JoinedKey()) && Value == pair.Value; - } + /// + /// 判斷當前鍵值配對是否合規。如果鍵與值有任一為空,則結果為 false。 + /// + public bool IsValid => !string.IsNullOrEmpty(JoinedKey()) && !string.IsNullOrEmpty(Value); - /// - /// 做為預設雜湊函式。 - /// - /// 目前物件的雜湊碼。 - public override int GetHashCode() => HashCode.Combine(KeyArray, Value); - /// - /// 傳回代表目前物件的字串。 - /// - /// 表示目前物件的字串。 - public override string ToString() => $"({JoinedKey()},{Value})"; + /// + /// + /// + /// + /// + public override bool Equals(object obj) { + return obj is KeyValuePaired pair && JoinedKey().SequenceEqual(pair.JoinedKey()) && Value == pair.Value; + } - /// - /// 會在某些特殊場合用到的字串表達方法。 - /// - public string ToNGramKey => IsValid ? $"({JoinedKey()},{Value})" : "()"; - /// - /// - /// - /// - /// - /// - public static bool operator ==(KeyValuePaired lhs, KeyValuePaired rhs) { - return lhs.KeyArray.SequenceEqual(rhs.KeyArray) && lhs.Value == rhs.Value; - } - /// - /// - /// - /// - /// - /// - public static bool operator !=(KeyValuePaired lhs, KeyValuePaired rhs) { - return lhs.KeyArray.Count != rhs.KeyArray.Count || lhs.Value != rhs.Value; - } + /// + /// 做為預設雜湊函式。 + /// + /// 目前物件的雜湊碼。 + public override int GetHashCode() => HashCode.Combine(KeyArray, Value); + /// + /// 傳回代表目前物件的字串。 + /// + /// 表示目前物件的字串。 + public override string ToString() => $"({JoinedKey()},{Value})"; - /// - /// - /// - /// - /// - /// - public static bool operator<(KeyValuePaired lhs, KeyValuePaired rhs) { - return lhs.KeyArray.Count < rhs.KeyArray.Count || - lhs.KeyArray.Count == rhs.KeyArray.Count && - string.Compare(lhs.Value, rhs.Value, StringComparison.Ordinal) < 0; - } + /// + /// 會在某些特殊場合用到的字串表達方法。 + /// + public string ToNGramKey => IsValid ? $"({JoinedKey()},{Value})" : "()"; + /// + /// + /// + /// + /// + /// + public static bool operator ==(KeyValuePaired lhs, KeyValuePaired rhs) { + return lhs.KeyArray.SequenceEqual(rhs.KeyArray) && lhs.Value == rhs.Value; + } + /// + /// + /// + /// + /// + /// + public static bool operator !=(KeyValuePaired lhs, KeyValuePaired rhs) { + return lhs.KeyArray.Count != rhs.KeyArray.Count || lhs.Value != rhs.Value; + } - /// - /// - /// - /// - /// - /// - public static bool operator>(KeyValuePaired lhs, KeyValuePaired rhs) { - return lhs.KeyArray.Count > rhs.KeyArray.Count || - lhs.KeyArray.Count == rhs.KeyArray.Count && - string.Compare(lhs.Value, rhs.Value, StringComparison.Ordinal) > 0; - } + /// + /// + /// + /// + /// + /// + public static bool operator<(KeyValuePaired lhs, KeyValuePaired rhs) { + return lhs.KeyArray.Count < rhs.KeyArray.Count || + lhs.KeyArray.Count == rhs.KeyArray.Count && + string.Compare(lhs.Value, rhs.Value, StringComparison.Ordinal) < 0; + } - /// - /// - /// - /// - /// - /// - public static bool operator <=(KeyValuePaired lhs, KeyValuePaired rhs) { - return lhs.KeyArray.Count <= rhs.KeyArray.Count || - lhs.KeyArray.Count == rhs.KeyArray.Count && - string.Compare(lhs.Value, rhs.Value, StringComparison.Ordinal) <= 0; - } + /// + /// + /// + /// + /// + /// + public static bool operator>(KeyValuePaired lhs, KeyValuePaired rhs) { + return lhs.KeyArray.Count > rhs.KeyArray.Count || + lhs.KeyArray.Count == rhs.KeyArray.Count && + string.Compare(lhs.Value, rhs.Value, StringComparison.Ordinal) > 0; + } - /// - /// - /// - /// - /// - /// - public static bool operator >=(KeyValuePaired lhs, KeyValuePaired rhs) { - return lhs.KeyArray.Count >= rhs.KeyArray.Count || - lhs.KeyArray.Count == rhs.KeyArray.Count && - string.Compare(lhs.Value, rhs.Value, StringComparison.Ordinal) >= 0; - } + /// + /// + /// + /// + /// + /// + public static bool operator <=(KeyValuePaired lhs, KeyValuePaired rhs) { + return lhs.KeyArray.Count <= rhs.KeyArray.Count || + lhs.KeyArray.Count == rhs.KeyArray.Count && + string.Compare(lhs.Value, rhs.Value, StringComparison.Ordinal) <= 0; } + /// + /// + /// + /// + /// + /// + public static bool operator >=(KeyValuePaired lhs, KeyValuePaired rhs) { + return lhs.KeyArray.Count >= rhs.KeyArray.Count || + lhs.KeyArray.Count == rhs.KeyArray.Count && + string.Compare(lhs.Value, rhs.Value, StringComparison.Ordinal) >= 0; + } +} +public partial struct Compositor { /// /// 候選字陣列內容的獲取範圍類型。 /// diff --git a/Megrez/src/4_SpanUnit.cs b/Megrez/src/4_SpanUnit.cs index cc567fe..b46e49b 100644 --- a/Megrez/src/4_SpanUnit.cs +++ b/Megrez/src/4_SpanUnit.cs @@ -12,7 +12,10 @@ public partial struct Compositor { /// /// 幅位單元乃指一組共享起點的節點。 /// - public class SpanUnit { + /// + /// 其實就是個 [int: Node] 形態的辭典。然而,C# 沒有 Swift 那樣的 TypeAlias 機制,所以還是不要精簡了。 + /// + public struct SpanUnit { /// /// 節點陣列。每個位置上的節點可能是 null。 /// @@ -30,10 +33,34 @@ public class SpanUnit { /// /// 幅位乃指一組共享起點的節點。 /// - public SpanUnit() => Clear(); // 該函式會自動給 MaxLength 賦值。 + /// + /// 其實就是個 [int: Node] 形態的辭典。然而,C# 沒有 Swift 那樣的 TypeAlias 機制,所以還是不要精簡了。 + /// + public SpanUnit() {} + + /// + /// 幅位乃指一組共享起點的節點。該建構子用來拿給定的既有幅位單元製作硬拷貝。 + /// + /// + /// 其實就是個 [int: Node] 形態的辭典。然而,C# 沒有 Swift 那樣的 TypeAlias 機制,所以還是不要精簡了。 + /// + public SpanUnit(SpanUnit spanUnit) { + foreach (int spanLength in spanUnit.Nodes.Keys) Nodes[spanLength] = spanUnit.Nodes[spanLength].Copy(); + } /// - /// 清除該幅位單元內的全部的節點,且重設最長節點長度為 0,然後再在節點陣列內預留空位。 + /// 生成自身的拷貝。 + /// + /// + /// 因為 Node 不是 Struct,所以會在 Compositor 被拷貝的時候無法被真實複製。 + /// 這樣一來,Compositor 複製品當中的 Node 的變化會被反應到原先的 Compositor 身上。 + /// 這在某些情況下會造成意料之外的混亂情況,所以需要引入一個拷貝用的建構子。 + /// + /// 拷貝。 + public SpanUnit HardCopy() => new(spanUnit: this); + + /// + /// 清除該幅位單元內的全部的節點。 /// public void Clear() => Nodes.Clear(); @@ -51,11 +78,6 @@ public bool Append(Node node) { /// /// 丟掉任何與給定節點完全雷同的節點。 /// - /// - /// Swift 不像 C# 那樣有容量鎖定型陣列, - /// 對某個位置的內容的刪除行為都可能會導致其它內容錯位、繼發其它不可知故障。 - /// 於是就提供了這個專門的工具函式。 - /// /// 要參照的節點。 public void Nullify(Node givenNode) => Nodes.Remove(givenNode.SpanLength); @@ -86,22 +108,22 @@ public bool DropNodesOfOrBeyond(int length) { /// /// 找出所有與該位置重疊的節點。其返回值為一個節錨陣列(包含節點、以及其起始位置)。 /// - /// 游標位置。 + /// 游標位置。 /// 一個包含所有與該位置重疊的節點的陣列。 - internal List FetchOverlappingNodesAt(int location) { + internal List FetchOverlappingNodesAt(int givenLocation) { List results = new(); - if (Spans.IsEmpty() || location >= Spans.Count) return results; + if (Spans.IsEmpty() || givenLocation >= Spans.Count) return results; // 先獲取該位置的所有單字節點。 - foreach (int theLocation in new BRange(1, Spans[location].MaxLength + 1)) { - if (Spans[location].NodeOf(theLocation) is not {} node) continue; - results.Add(new(node, location)); + foreach (int spanLength in new BRange(1, Spans[givenLocation].MaxLength + 1)) { + if (Spans[givenLocation].NodeOf(spanLength) is not {} node) continue; + results.Add(new(node, givenLocation)); } // 再獲取以當前位置結尾或開頭的節點。 - int begin = location - Math.Min(location, MaxSpanLength - 1); - foreach (int theLocation in new BRange(begin, location)) { - (int alpha, int bravo) = (location - theLocation + 1, Spans[theLocation].MaxLength); + int begin = givenLocation - Math.Min(givenLocation, MaxSpanLength - 1); + foreach (int theLocation in new BRange(begin, givenLocation)) { + (int alpha, int bravo) = (givenLocation - theLocation + 1, Spans[theLocation].MaxLength); if (alpha > bravo) continue; foreach (int theLength in new BRange(alpha, bravo + 1)) { if (Spans[theLocation].NodeOf(theLength) is not {} node) continue; diff --git a/Megrez/src/6_Node.cs b/Megrez/src/6_Node.cs index b1ead1c..dd1f1b9 100644 --- a/Megrez/src/6_Node.cs +++ b/Megrez/src/6_Node.cs @@ -8,206 +8,238 @@ using System.Linq; namespace Megrez { -public partial struct Compositor { +/// +/// 字詞節點。 +/// 一個節點由這些內容組成:幅位長度、索引鍵、以及一組單元圖。幅位長度就是指這個 +/// 節點在組字器內橫跨了多少個字長。組字器負責構築自身的節點。對於由多個漢字組成 +/// 的詞,組字器會將多個讀音索引鍵合併為一個讀音索引鍵、據此向語言模組請求對應的 +/// 單元圖結果陣列。舉例說,如果一個詞有兩個漢字組成的話,那麼讀音也是有兩個、其 +/// 索引鍵也是由兩個讀音組成的,那麼這個節點的幅位長度就是 2。 +/// +public class Node { + // MARK: - Enums + /// - /// 字詞節點。 - /// 一個節點由這些內容組成:幅位長度、索引鍵、以及一組單元圖。幅位長度就是指這個 - /// 節點在組字器內橫跨了多少個字長。組字器負責構築自身的節點。對於由多個漢字組成 - /// 的詞,組字器會將多個讀音索引鍵合併為一個讀音索引鍵、據此向語言模組請求對應的 - /// 單元圖結果陣列。舉例說,如果一個詞有兩個漢字組成的話,那麼讀音也是有兩個、其 - /// 索引鍵也是由兩個讀音組成的,那麼這個節點的幅位長度就是 2。 + /// 三種不同的針對一個節點的覆寫行為。 /// - public class Node { - // MARK: - Enums - + public enum OverrideType { /// - /// 三種不同的針對一個節點的覆寫行為。 + /// 無覆寫行為。 /// - public enum OverrideType { - /// - /// 無覆寫行為。 - /// - NoOverrides = 0, - /// - /// 使用指定的單元圖資料值來覆寫該節點,但卻使用 - /// 當前狀態下權重最高的單元圖的權重數值。打比方說,如果該節點內的單元圖陣列是 - /// [("a", -114), ("b", -514), ("c", -1919)] 的話,指定該覆寫行為則會導致該節 - /// 點返回的結果為 ("c", -114)。該覆寫行為多用於諸如使用者半衰記憶模組的建議 - /// 行為。被覆寫的這個節點的狀態可能不會再被爬軌行為擅自改回。該覆寫行為無法 - /// 防止其它節點被爬軌函式所支配。這種情況下就需要用到 。 - /// - TopUnigramScore = 1, - /// - /// 將該節點權重覆寫為 ,使其被爬軌函式所青睞。 - /// - HighScore = 2 - } - - // MARK: - Variables - + NoOverrides = 0, /// - /// 一個用以覆寫權重的數值。該數值之高足以改變爬軌函式對該節點的讀取結果。這裡用 - /// 「0」可能看似足夠了,但仍會使得該節點的覆寫狀態有被爬軌函式忽視的可能。比方說 - /// 要針對索引鍵「a b c」複寫的資料值為「A B C」,使用大寫資料值來覆寫節點。這時, - /// 如果這個獨立的 c 有一個可以拮抗權重的詞「bc」的話,可能就會導致爬軌函式的算法 - /// 找出「A->bc」的爬軌途徑(尤其是當 A 和 B 使用「0」作為複寫數值的情況下)。這樣 - /// 一來,「A-B」就不一定始終會是爬軌函式的青睞結果了。所以,這裡一定要用大於 0 的 - /// 數(比如野獸常數),以讓「c」更容易單獨被選中。 + /// 使用指定的單元圖資料值來覆寫該節點,但卻使用 + /// 當前狀態下權重最高的單元圖的權重數值。打比方說,如果該節點內的單元圖陣列是 + /// [("a", -114), ("b", -514), ("c", -1919)] 的話,指定該覆寫行為則會導致該節 + /// 點返回的結果為 ("c", -114)。該覆寫行為多用於諸如使用者半衰記憶模組的建議 + /// 行為。被覆寫的這個節點的狀態可能不會再被爬軌行為擅自改回。該覆寫行為無法 + /// 防止其它節點被爬軌函式所支配。這種情況下就需要用到 。 /// - public double OverridingScore = 114514; - + TopUnigramScore = 1, /// - /// 索引鍵陣列。 + /// 將該節點權重覆寫為 ,使其被爬軌函式所青睞。 /// - public List KeyArray { get; } - /// - /// 幅位長度。 - /// - public int SpanLength { get; } - /// - /// 單元圖陣列。 - /// - public List Unigrams { get; private set; } - /// - /// 該節點目前的覆寫狀態種類。 - /// - public OverrideType CurrentOverrideType { get; private set; } + HighScore = 2 + } - private int _currentUnigramIndex; - /// - /// 當前該節點所指向的(單元圖陣列內的)單元圖索引位置。 - /// - public int CurrentUnigramIndex { - get => _currentUnigramIndex; - set { - int corrected = Math.Max(Math.Min(Unigrams.Count - 1, value), 0); - _currentUnigramIndex = corrected; - } - } + // MARK: - Variables - // MARK: - Constructor and Other Fundamentals + /// + /// 一個用以覆寫權重的數值。該數值之高足以改變爬軌函式對該節點的讀取結果。這裡用 + /// 「0」可能看似足夠了,但仍會使得該節點的覆寫狀態有被爬軌函式忽視的可能。比方說 + /// 要針對索引鍵「a b c」複寫的資料值為「A B C」,使用大寫資料值來覆寫節點。這時, + /// 如果這個獨立的 c 有一個可以拮抗權重的詞「bc」的話,可能就會導致爬軌函式的算法 + /// 找出「A->bc」的爬軌途徑(尤其是當 A 和 B 使用「0」作為複寫數值的情況下)。這樣 + /// 一來,「A-B」就不一定始終會是爬軌函式的青睞結果了。所以,這裡一定要用大於 0 的 + /// 數(比如野獸常數),以讓「c」更容易單獨被選中。 + /// + public double OverridingScore = 114514; - /// - /// 生成一個字詞節點。 - /// 一個節點由這些內容組成:幅位長度、索引鍵、以及一組單元圖。幅位長度就是指這個 - /// 節點在組字器內橫跨了多少個字長。組字器負責構築自身的節點。對於由多個漢字組成 - /// 的詞,組字器會將多個讀音索引鍵合併為一個讀音索引鍵、據此向語言模組請求對應的 - /// 單元圖結果陣列。舉例說,如果一個詞有兩個漢字組成的話,那麼讀音也是有兩個、其 - /// 索引鍵也是由兩個讀音組成的,那麼這個節點的幅位長度就是 2。 - /// - /// 給定索引鍵陣列,不得為空。 - /// 給定幅位長度,一般情況下與給定索引鍵陣列內的索引鍵數量一致。 - /// 給定單元圖陣列,不得為空。 - public Node(List keyArray, int spanLength, List unigrams) { - _currentUnigramIndex = 0; - KeyArray = keyArray; - SpanLength = Math.Max(0, spanLength); - Unigrams = unigrams; - CurrentOverrideType = OverrideType.NoOverrides; + /// + /// 索引鍵陣列。 + /// + public List KeyArray { get; } + /// + /// 幅位長度。 + /// + public int SpanLength { get; } + /// + /// 單元圖陣列。 + /// + public List Unigrams { get; private set; } + /// + /// 該節點目前的覆寫狀態種類。 + /// + public OverrideType CurrentOverrideType { get; private set; } + + private int _currentUnigramIndex; + /// + /// 當前該節點所指向的(單元圖陣列內的)單元圖索引位置。 + /// + public int CurrentUnigramIndex { + get => _currentUnigramIndex; + set { + int corrected = Math.Max(Math.Min(Unigrams.Count - 1, value), 0); + _currentUnigramIndex = corrected; } - /// - /// - /// - /// - /// - public override bool Equals(object obj) => obj is Node node - && KeyArray.SequenceEqual(node.KeyArray) && SpanLength == node.SpanLength - && Unigrams == node.Unigrams - && CurrentOverrideType == node.CurrentOverrideType; + } - /// - /// 做為預設雜湊函式。 - /// - /// 目前物件的雜湊碼。 - public override int GetHashCode() => HashCode.Combine(KeyArray, SpanLength, Unigrams, CurrentUnigramIndex, - SpanLength, CurrentOverrideType); + // MARK: - Constructor and Other Fundamentals - // MARK: - Dynamic Variables + /// + /// 生成一個字詞節點。 + /// 一個節點由這些內容組成:幅位長度、索引鍵、以及一組單元圖。幅位長度就是指這個 + /// 節點在組字器內橫跨了多少個字長。組字器負責構築自身的節點。對於由多個漢字組成 + /// 的詞,組字器會將多個讀音索引鍵合併為一個讀音索引鍵、據此向語言模組請求對應的 + /// 單元圖結果陣列。舉例說,如果一個詞有兩個漢字組成的話,那麼讀音也是有兩個、其 + /// 索引鍵也是由兩個讀音組成的,那麼這個節點的幅位長度就是 2。 + /// + /// 給定索引鍵陣列,不得為空。 + /// 給定幅位長度,一般情況下與給定索引鍵陣列內的索引鍵數量一致。 + /// 給定單元圖陣列,不得為空。 + public Node(List keyArray, int spanLength, List unigrams) { + _currentUnigramIndex = 0; + KeyArray = keyArray; + SpanLength = Math.Max(0, spanLength); + Unigrams = unigrams; + CurrentOverrideType = OverrideType.NoOverrides; + } - /// - /// 該節點當前狀態所展示的鍵值配對。 - /// - public KeyValuePaired CurrentPair => new(KeyArray, Value); + /// + /// 以指定字詞節點生成拷貝。 + /// + /// + /// 因為 Node 不是 Struct,所以會在 Compositor 被拷貝的時候無法被真實複製。 + /// 這樣一來,Compositor 複製品當中的 Node 的變化會被反應到原先的 Compositor 身上。 + /// 這在某些情況下會造成意料之外的混亂情況,所以需要引入一個拷貝用的建構子。 + /// + /// + public Node(Node node) { + OverridingScore = node.OverridingScore; + KeyArray = node.KeyArray; + SpanLength = node.SpanLength; + Unigrams = node.Unigrams; + CurrentOverrideType = node.CurrentOverrideType; + CurrentUnigramIndex = node.CurrentUnigramIndex; + } - /// - /// 給出該節點內部單元圖陣列內目前被索引位置所指向的單元圖。 - /// - public Unigram CurrentUnigram => Unigrams.IsEmpty() ? new() : Unigrams[CurrentUnigramIndex]; + /// + /// 生成自身的拷貝。 + /// + /// + /// 因為 Node 不是 Struct,所以會在 Compositor 被拷貝的時候無法被真實複製。 + /// 這樣一來,Compositor 複製品當中的 Node 的變化會被反應到原先的 Compositor 身上。 + /// 這在某些情況下會造成意料之外的混亂情況,所以需要引入一個拷貝用的建構子。 + /// + /// 拷貝。 + public Node Copy() => new(node: this); - /// - /// 給出該節點內部單元圖陣列內目前被索引位置所指向的單元圖的資料值。 - /// - public string Value => CurrentUnigram.Value; + /// + /// + /// + /// + /// + public override bool Equals(object obj) { + if (obj is not Node node) return false; + return OverridingScore == node.OverridingScore && KeyArray.SequenceEqual(node.KeyArray) && + SpanLength == node.SpanLength && Unigrams == node.Unigrams && + CurrentOverrideType == node.CurrentOverrideType && CurrentUnigramIndex == node.CurrentUnigramIndex; + } - /// - /// 給出目前的最高權重單元圖當中的權重值。該結果可能會受節點覆寫狀態所影響。 - /// - public double Score { - get { - if (Unigrams.IsEmpty()) return 0; - return CurrentOverrideType switch { OverrideType.HighScore => OverridingScore, - OverrideType.TopUnigramScore => Unigrams.First().Score, - _ => CurrentUnigram.Score }; - } - } + /// + /// 做為預設雜湊函式。 + /// + /// 目前物件的雜湊碼。 + public override int GetHashCode() => HashCode.Combine(OverridingScore, KeyArray, SpanLength, Unigrams, + CurrentUnigramIndex, CurrentOverrideType); - /// - /// 檢查當前節點是否「讀音字長與候選字字長不一致」。 - /// - public bool IsReadingMismatched => KeyArray.Count != Value.LiteralCount(); - /// - /// 該節點是否處於被覆寫的狀態。 - /// - public bool IsOverridden => CurrentOverrideType != OverrideType.NoOverrides; + // MARK: - Dynamic Variables - // MARK: - Methods and Functions + /// + /// 該節點當前狀態所展示的鍵值配對。 + /// + public KeyValuePaired CurrentPair => new(KeyArray, Value); - /// - /// 將索引鍵按照給定的分隔符銜接成一個字串。 - /// - /// 給定的分隔符,預設值為 。 - /// 已經銜接完畢的字串。 - public string JoinedKey(string? separator = null) => KeyArray.Joined(separator: separator ?? TheSeparator); + /// + /// 給出該節點內部單元圖陣列內目前被索引位置所指向的單元圖。 + /// + public Unigram CurrentUnigram => Unigrams.IsEmpty() ? new() : Unigrams[CurrentUnigramIndex]; - /// - /// 重設該節點的覆寫狀態、及其內部的單元圖索引位置指向。 - /// - public void Reset() { - _currentUnigramIndex = 0; - CurrentOverrideType = OverrideType.NoOverrides; - } + /// + /// 給出該節點內部單元圖陣列內目前被索引位置所指向的單元圖的資料值。 + /// + public string Value => CurrentUnigram.Value; - /// - /// 置換掉該節點內的單元圖陣列資料。 - /// 如果此時影響到了 currentUnigramIndex 所指的內容的話,則將其重設為 0。 - /// - /// 新的單元圖陣列資料,必須不能為空(否則必定崩潰)。 - public void SyncingUnigramsFrom(List source) { - string oldCurrentValue = Unigrams[CurrentUnigramIndex].Value; - Unigrams = source; - CurrentUnigramIndex = _currentUnigramIndex; // 自動觸發 didSet() 的糾錯過程。 - string newCurrentValue = Unigrams[CurrentUnigramIndex].Value; - if (oldCurrentValue != newCurrentValue) Reset(); + /// + /// 給出目前的最高權重單元圖當中的權重值。該結果可能會受節點覆寫狀態所影響。 + /// + public double Score { + get { + if (Unigrams.IsEmpty()) return 0; + return CurrentOverrideType switch { OverrideType.HighScore => OverridingScore, + OverrideType.TopUnigramScore => Unigrams.First().Score, + _ => CurrentUnigram.Score }; } + } - /// - /// 指定要覆寫的單元圖資料值、以及覆寫行為種類。 - /// - /// 給定的單元圖資料值。 - /// 覆寫行為種類。 - /// 操作是否順利完成。 - public bool SelectOverrideUnigram(string value, OverrideType type) { - if (type == OverrideType.NoOverrides) return false; - foreach ((int i, Unigram gram) in Unigrams.Enumerated()) { - if (value != gram.Value) continue; - CurrentUnigramIndex = i; - CurrentOverrideType = type; - return true; - } - return false; + /// + /// 檢查當前節點是否「讀音字長與候選字字長不一致」。 + /// + public bool IsReadingMismatched => KeyArray.Count != Value.LiteralCount(); + /// + /// 該節點是否處於被覆寫的狀態。 + /// + public bool IsOverridden => CurrentOverrideType != OverrideType.NoOverrides; + + // MARK: - Methods and Functions + + /// + /// 將索引鍵按照給定的分隔符銜接成一個字串。 + /// + /// 給定的分隔符,預設值為 。 + /// 已經銜接完畢的字串。 + public string JoinedKey(string? separator = null) => KeyArray.Joined(separator: separator ?? Compositor.TheSeparator); + + /// + /// 重設該節點的覆寫狀態、及其內部的單元圖索引位置指向。 + /// + public void Reset() { + _currentUnigramIndex = 0; + CurrentOverrideType = OverrideType.NoOverrides; + } + + /// + /// 置換掉該節點內的單元圖陣列資料。 + /// 如果此時影響到了 currentUnigramIndex 所指的內容的話,則將其重設為 0。 + /// + /// 新的單元圖陣列資料,必須不能為空(否則必定崩潰)。 + public void SyncingUnigramsFrom(List source) { + string oldCurrentValue = Unigrams[CurrentUnigramIndex].Value; + Unigrams = source; + CurrentUnigramIndex = _currentUnigramIndex; // 自動觸發 didSet() 的糾錯過程。 + string newCurrentValue = Unigrams[CurrentUnigramIndex].Value; + if (oldCurrentValue != newCurrentValue) Reset(); + } + + /// + /// 指定要覆寫的單元圖資料值、以及覆寫行為種類。 + /// + /// 給定的單元圖資料值。 + /// 覆寫行為種類。 + /// 操作是否順利完成。 + public bool SelectOverrideUnigram(string value, OverrideType type) { + if (type == OverrideType.NoOverrides) return false; + foreach ((int i, Unigram gram) in Unigrams.Enumerated()) { + if (value != gram.Value) continue; + CurrentUnigramIndex = i; + CurrentOverrideType = type; + return true; } + return false; } +} +public partial struct Compositor { /// /// 節錨。在 Gramambular 2 當中又被稱為「NodeInSpan」。 /// @@ -264,27 +296,27 @@ public static class NodeExtensions { /// /// 節點。 /// 目前的索引鍵陣列。 - public static List> KeyArray(this List self) => self.Select(x => x.KeyArray).ToList(); + public static List> KeyArray(this List self) => self.Select(x => x.KeyArray).ToList(); /// /// 從一個節點陣列當中取出目前的選字字串陣列。 /// /// 節點。 /// 目前的自動選字字串陣列。 - public static List Values(this List self) => self.Select(x => x.Value).ToList(); + public static List Values(this List self) => self.Select(x => x.Value).ToList(); /// /// 從一個節點陣列當中取出目前的索引鍵陣列。 /// /// 節點。 /// /// 取出目前的索引鍵陣列。 - public static List JoinedKeys(this List self, string? separator) => + public static List JoinedKeys(this List self, string? separator) => self.Select(x => x.KeyArray.Joined(separator ?? Compositor.TheSeparator)).ToList(); /// /// 總讀音單元數量。在絕大多數情況下,可視為總幅位長度。 /// /// 節點。 /// 總讀音單元數量。 - public static int TotalKeyCount(this List self) => self.Select(x => x.KeyArray.Count).Sum(); + public static int TotalKeyCount(this List self) => self.Select(x => x.KeyArray.Count).Sum(); /// /// 返回一連串的節點起點。結果為 (Result A, Result B) 辭典陣列。 /// Result A 以索引查座標,Result B 以座標查索引。 @@ -292,11 +324,11 @@ public static List JoinedKeys(this List self, string? s /// 節點。 /// (Result A, Result B) 辭典陣列。Result A 以索引查座標,Result B 以座標查索引。 private static (Dictionary RegionCursorMap, Dictionary CursorRegionMap) - NodeBorderPointDictPair(this List self) { + NodeBorderPointDictPair(this List self) { Dictionary resultA = new(); Dictionary resultB = new() { [-1] = 0 }; // 防呆 int cursorCounter = 0; - foreach ((int nodeCounter, Compositor.Node neta) in self.Enumerated()) { + foreach ((int nodeCounter, Node neta) in self.Enumerated()) { resultA[nodeCounter] = cursorCounter; foreach (string _ in neta.KeyArray) { resultB[cursorCounter] = nodeCounter; @@ -313,7 +345,7 @@ private static (Dictionary RegionCursorMap, Dictionary Curso /// /// 節點。 /// 一個辭典,以座標查索引。允許以游標位置查詢其屬於第幾個幅位座標(從 0 開始算)。 - public static Dictionary CursorRegionMap(this List self) => + public static Dictionary CursorRegionMap(this List self) => self.NodeBorderPointDictPair().CursorRegionMap; /// @@ -322,7 +354,7 @@ public static Dictionary CursorRegionMap(this List se /// 節點。 /// 給定的游標。 /// 前後最近的邊界點。 - public static BRange ContextRange(this List self, int givenCursor) { + public static BRange ContextRange(this List self, int givenCursor) { if (self.IsEmpty()) return new(0, 0); int lastSpanLength = self.Last().KeyArray.Count; int totalKeyCount = self.TotalKeyCount(); @@ -345,8 +377,7 @@ public static BRange ContextRange(this List self, int givenCurs /// 給定游標位置。 /// 找出的節點的前端位置。 /// 查找結果。 - public static Compositor.Node? FindNodeAt(this List self, int givenCursor, - ref int outCursorPastNode) { + public static Node? FindNodeAt(this List self, int givenCursor, ref int outCursorPastNode) { if (self.IsEmpty()) return null; int cursor = Math.Max(0, Math.Min(givenCursor, self.TotalKeyCount() - 1)); BRange range = self.ContextRange(givenCursor: cursor); @@ -361,7 +392,7 @@ public static BRange ContextRange(this List self, int givenCurs /// 節點。 /// 給定游標位置。 /// 查找結果。 - public static Compositor.Node? FindNodeAt(this List self, int givenCursor) { + public static Node? FindNodeAt(this List self, int givenCursor) { int mudamuda = 0; // muda = useless. return self.FindNodeAt(givenCursor, ref mudamuda); } @@ -371,10 +402,10 @@ public static BRange ContextRange(this List self, int givenCurs /// /// 節點。 /// 一組逐字的字音配對陣列。 - public static List<(string key, string value)> SmashedPairs(this List self) { + public static List<(string key, string value)> SmashedPairs(this List self) { List<(string key, string value)> arrData = new(); string separator = Compositor.TheSeparator; - foreach (Compositor.Node node in self) { + foreach (Node node in self) { if (node.IsReadingMismatched) { string newKey = node.JoinedKey(); if (!string.IsNullOrEmpty(separator) && newKey != separator && newKey.Contains(separator))