diff --git a/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMCassetteTests.swift b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMCassetteTests.swift index 62908ef8..a7c1eee7 100644 --- a/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMCassetteTests.swift +++ b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMCassetteTests.swift @@ -1,5 +1,4 @@ //// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -// StringView Ranges extension by (c) 2022 and onwards Isaac Xen (MIT License). // ==================== // This code is released under the MIT license (SPDX-License-Identifier: MIT) // ... with NTL restriction stating that: @@ -49,8 +48,8 @@ final class LMCassetteTests: XCTestCase { XCTAssertFalse(lmCassette.quickDefMap.isEmpty) print(lmCassette.quickSetsFor(key: ",.") ?? "") XCTAssertEqual(lmCassette.keyNameMap.count, 41) - XCTAssertEqual(lmCassette.charDefMap.count, 29537) - XCTAssertEqual(lmCassette.charDefWildcardMap.count, 11973) + XCTAssertEqual(lmCassette.charDefMap.count, 29491) + XCTAssertEqual(lmCassette.charDefWildcardMap.count, 11946) XCTAssertEqual(lmCassette.octagramMap.count, 0) XCTAssertEqual(lmCassette.octagramDividedMap.count, 0) XCTAssertEqual(lmCassette.nameShort, "AR30") diff --git a/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMUserOverrideTests.swift b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMUserOverrideTests.swift new file mode 100644 index 00000000..225d8079 --- /dev/null +++ b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMUserOverrideTests.swift @@ -0,0 +1,106 @@ +//// (c) 2021 and onwards The vChewing Project (MIT-NTL 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. + +import Foundation +import XCTest + +@testable import LangModelAssembly + +private let nowTimeStamp: Double = 114_514 * 10000 +private let capacity = 5 +private let halfLife: Double = 5400 +private let nullURL = URL(fileURLWithPath: "/dev/null") + +final class LMUserOverrideTests: XCTestCase { + private func observe(who uom: vChewingLM.LMUserOverride, key: String, candidate: String, timestamp stamp: Double) { + uom.doObservation(key: key, candidate: candidate, timestamp: stamp, forceHighScoreOverride: false, saveCallback: {}) + } + + func testUOM_1_BasicOps() throws { + let uom = vChewingLM.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL) + let key = "((ㄍㄨㄥ-ㄙ,公司),(ㄉㄜ˙,的),ㄋㄧㄢˊ-ㄓㄨㄥ)" + let headReading = "ㄋㄧㄢˊ-ㄓㄨㄥ" + let expectedSuggestion = "年終" + observe(who: uom, key: key, candidate: expectedSuggestion, timestamp: nowTimeStamp) + var suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp, headReading: headReading) + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", expectedSuggestion) + var i = 0 + while !suggested.candidates.isEmpty { + suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp + (halfLife * Double(i)), headReading: headReading) + let suggestedCandidates = suggested.candidates + if suggestedCandidates.isEmpty { print(i) } + if i >= 21 { + XCTAssertNotEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", expectedSuggestion, i.description) + XCTAssert(suggested.candidates.isEmpty) + } else { + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", expectedSuggestion, i.description) + } + i += 1 + } + } + + func testUOM_2_NewestAgainstRepeatedlyUsed() throws { + let uom = vChewingLM.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL) + let key = "((ㄍㄨㄥ-ㄙ,公司),(ㄉㄜ˙,的),ㄋㄧㄢˊ-ㄓㄨㄥ)" + let headReading = "ㄋㄧㄢˊ-ㄓㄨㄥ" + let valRepeatedlyUsed = "年終" // 更常用 + let valNewest = "年中" // 最近偶爾用了一次 + let stamps: [Double] = [0, 0.5, 2, 2.5, 4, 4.5, 5.3].map { nowTimeStamp + halfLife * $0 } + stamps.forEach { stamp in + observe(who: uom, key: key, candidate: valRepeatedlyUsed, timestamp: stamp) + } + var suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp, headReading: headReading) + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", valRepeatedlyUsed) + [6.0, 18.0, 23.0].forEach { i in + suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp + halfLife * Double(i), headReading: headReading) + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", valRepeatedlyUsed, i.description) + } + // 試試看偶爾選了不常用的詞的話、是否會影響上文所生成的有一定強效的記憶。 + observe(who: uom, key: key, candidate: valNewest, timestamp: nowTimeStamp + halfLife * 23.4) + suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp + halfLife * 23.6, headReading: headReading) + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", valNewest) + suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp + halfLife * 26, headReading: headReading) + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", valNewest) + suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp + halfLife * 50, headReading: headReading) + XCTAssertNotEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", valNewest) + XCTAssert(suggested.candidates.isEmpty) + } + + func testUOM_3_LRUTable() throws { + let a = (key: "((ㄍㄨㄥ-ㄙ,公司),(ㄉㄜ˙,的),ㄋㄧㄢˊ-ㄓㄨㄥ)", value: "年終", head: "ㄋㄧㄢˊ-ㄓㄨㄥ") + let b = (key: "((ㄑㄧˋ-ㄧㄝˋ,企業),(ㄉㄜ˙,的),ㄐㄧㄤˇ-ㄐㄧㄣ)", value: "獎金", head: "ㄐㄧㄤˇ-ㄐㄧㄣ") + let c = (key: "((ㄒㄩㄝˊ-ㄕㄥ,學生),(ㄉㄜ˙,的),ㄈㄨˊ-ㄌㄧˋ)", value: "福利", head: "ㄈㄨˊ-ㄌㄧˋ") + let d = (key: "((ㄌㄟˊ-ㄉㄧㄢˋ-ㄐㄧㄤ-ㄐㄩㄣ,雷電將軍),(ㄉㄜ˙,的),ㄐㄧㄠˇ-ㄔㄡˋ)", value: "腳臭", head: "ㄐㄧㄠˇ-ㄔㄡˋ") + let uom = vChewingLM.LMUserOverride(capacity: 2, decayConstant: Double(halfLife), dataURL: nullURL) + observe(who: uom, key: a.key, candidate: a.value, timestamp: nowTimeStamp) + observe(who: uom, key: b.key, candidate: b.value, timestamp: nowTimeStamp + halfLife * 1) + observe(who: uom, key: c.key, candidate: c.value, timestamp: nowTimeStamp + halfLife * 2) + // C is in the list. + var suggested = uom.getSuggestion(key: c.key, timestamp: nowTimeStamp + halfLife * 3, headReading: c.head) + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", c.value) + // B is in the list. + suggested = uom.getSuggestion(key: b.key, timestamp: nowTimeStamp + halfLife * 3.5, headReading: b.head) + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", b.value) + // A is purged. + suggested = uom.getSuggestion(key: a.key, timestamp: nowTimeStamp + halfLife * 4, headReading: a.head) + XCTAssertNotEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", a.value) + XCTAssert(suggested.candidates.isEmpty) + // Observe a new pair (D). + observe(who: uom, key: d.key, candidate: d.value, timestamp: nowTimeStamp + halfLife * 4.5) + // D is in the list. + suggested = uom.getSuggestion(key: d.key, timestamp: nowTimeStamp + halfLife * 5, headReading: d.head) + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", d.value) + // C is in the list. + suggested = uom.getSuggestion(key: c.key, timestamp: nowTimeStamp + halfLife * 5.5, headReading: c.head) + XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", c.value) + // B is purged. + suggested = uom.getSuggestion(key: b.key, timestamp: nowTimeStamp + halfLife * 6, headReading: b.head) + XCTAssertNotEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", b.value) + XCTAssert(suggested.candidates.isEmpty) + } +}