diff --git a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/SubLMs/lmCassette.swift b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/SubLMs/lmCassette.swift index 0155f2bc..4f7b5f0b 100644 --- a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/SubLMs/lmCassette.swift +++ b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/SubLMs/lmCassette.swift @@ -37,6 +37,7 @@ public extension vChewingLM { /// 音韻輸入法專用八股文:[字詞:(頻次, 讀音)]。 public private(set) var octagramDividedMap: [String: (Int, String)] = [:] public private(set) var areCandidateKeysShiftHeld: Bool = false + public private(set) var supplyQuickResults: Bool = false /// 計算頻率時要用到的東西 private static let fscale = 2.7 @@ -91,38 +92,49 @@ public extension vChewingLM { var loadingOctagramData = false var keysUsedInCharDef: Set = .init() for strLine in lineReader { - if !loadingKeys, strLine.contains("%keyname"), strLine.contains("begin") { loadingKeys = true } - if loadingKeys, strLine.contains("%keyname"), strLine.contains("end") { loadingKeys = false } - // %quick - if !loadingQuickSets, strLine.contains("%quick"), strLine.contains("begin") { - loadingQuickSets = true + if strLine.starts(with: "%keyname") { + if !loadingKeys, strLine.contains("begin") { loadingKeys = true } + if loadingKeys, strLine.contains("end") { loadingKeys = false } } - if loadingQuickSets, strLine.contains("%quick"), strLine.contains("end") { - loadingQuickSets = false - if quickDefMap.keys.contains(wildcardKey) { wildcardKey = "" } + // %quick + if strLine.starts(with: "%quick") { + if strLine == "%quick", !supplyQuickResults { supplyQuickResults = true } + if !loadingQuickSets, strLine.contains("begin") { + loadingQuickSets = true + } + if loadingQuickSets, strLine.contains("end") { + loadingQuickSets = false + if quickDefMap.keys.contains(wildcardKey) { wildcardKey = "" } + } } // %chardef - if !loadingCharDefinitions, strLine.contains("%chardef"), strLine.contains("begin") { - loadingCharDefinitions = true - } - if loadingCharDefinitions, strLine.contains("%chardef"), strLine.contains("end") { - loadingCharDefinitions = false - if charDefMap.keys.contains(wildcardKey) { wildcardKey = "" } + if strLine.starts(with: "%chardef") { + if !loadingCharDefinitions, strLine.contains("begin") { + loadingCharDefinitions = true + } + if loadingCharDefinitions, strLine.contains("end") { + loadingCharDefinitions = false + if charDefMap.keys.contains(wildcardKey) { wildcardKey = "" } + } } // %symboldef - if !loadingSymbolDefinitions, strLine.contains("%symboldef"), strLine.contains("begin") { - loadingSymbolDefinitions = true - } - if loadingSymbolDefinitions, strLine.contains("%symboldef"), strLine.contains("end") { - loadingSymbolDefinitions = false - if symbolDefMap.keys.contains(wildcardKey) { wildcardKey = "" } + if strLine.starts(with: "%symboldef") { + if !loadingSymbolDefinitions, strLine.contains("begin") { + loadingSymbolDefinitions = true + } + if loadingSymbolDefinitions, strLine.contains("end") { + loadingSymbolDefinitions = false + if symbolDefMap.keys.contains(wildcardKey) { wildcardKey = "" } + } } // %octagram - if !loadingOctagramData, strLine.contains("%octagram"), strLine.contains("begin") { - loadingOctagramData = true - } - if loadingOctagramData, strLine.contains("%octagram"), strLine.contains("end") { - loadingOctagramData = false + if strLine.starts(with: "%octagram") { + if !loadingOctagramData, strLine.contains("begin") { + loadingOctagramData = true + } + if loadingOctagramData, strLine.contains("end") { + loadingOctagramData = false + } } // Start data parsing. let cells: [String.SubSequence] = @@ -130,13 +142,13 @@ public extension vChewingLM { guard cells.count >= 2 else { continue } let strFirstCell = cells[0].trimmingCharacters(in: .newlines) let strSecondCell = cells[1].trimmingCharacters(in: .newlines) - if loadingKeys, !cells[0].contains("%keyname") { + if loadingKeys, !cells[0].starts(with: "%keyname") { keyNameMap[strFirstCell] = cells[1].trimmingCharacters(in: .newlines) - } else if loadingQuickSets, !strLine.contains("%quick") { + } else if loadingQuickSets, !strLine.starts(with: "%quick") { theMaxKeyLength = max(theMaxKeyLength, cells[0].count) quickDefMap[strFirstCell, default: .init()].append(strSecondCell) } else if loadingCharDefinitions, !loadingSymbolDefinitions, - !strLine.contains("%chardef"), !strLine.contains("%symboldef") + !strLine.starts(with: "%chardef"), !strLine.starts(with: "%symboldef") { theMaxKeyLength = max(theMaxKeyLength, cells[0].count) charDefMap[strFirstCell, default: []].append(strSecondCell) @@ -151,11 +163,11 @@ public extension vChewingLM { keyComps.removeLast() charDefWildcardMap[keyComps.joined() + wildcard, default: []].append(strSecondCell) } - } else if loadingSymbolDefinitions, !strLine.contains("%chardef"), !strLine.contains("%symboldef") { + } else if loadingSymbolDefinitions, !strLine.starts(with: "%chardef"), !strLine.starts(with: "%symboldef") { theMaxKeyLength = max(theMaxKeyLength, cells[0].count) symbolDefMap[strFirstCell, default: []].append(strSecondCell) reverseLookupMap[strSecondCell, default: []].append(strFirstCell) - } else if loadingOctagramData, !strLine.contains("%octagram") { + } else if loadingOctagramData, !strLine.starts(with: "%octagram") { guard let countValue = Int(cells[1]) else { continue } switch cells.count { case 2: octagramMap[strFirstCell] = countValue @@ -165,7 +177,7 @@ public extension vChewingLM { norm += Self.fscale ** (Double(cells[0].count) / 3.0 - 1.0) * Double(countValue) } guard !loadingKeys, !loadingQuickSets, !loadingCharDefinitions, !loadingOctagramData else { continue } - if nameENG.isEmpty, strLine.contains("%ename ") { + if nameENG.isEmpty, strLine.starts(with: "%ename ") { for neta in cells[1].components(separatedBy: ";") { let subNetaGroup = neta.components(separatedBy: ":") if subNetaGroup.count == 2, subNetaGroup[1].contains("en") { @@ -175,19 +187,19 @@ public extension vChewingLM { } if nameENG.isEmpty { nameENG = strSecondCell } } - if nameIntl.isEmpty, strLine.contains("%intlname ") { + if nameIntl.isEmpty, strLine.starts(with: "%intlname ") { nameIntl = strSecondCell.replacingOccurrences(of: "_", with: " ") } - if nameCJK.isEmpty, strLine.contains("%cname ") { nameCJK = strSecondCell } - if nameShort.isEmpty, strLine.contains("%sname ") { nameShort = strSecondCell } - if nullCandidate.isEmpty, strLine.contains("%nullcandidate ") { nullCandidate = strSecondCell } - if selectionKeys.isEmpty, strLine.contains("%selkey ") { + if nameCJK.isEmpty, strLine.starts(with: "%cname ") { nameCJK = strSecondCell } + if nameShort.isEmpty, strLine.starts(with: "%sname ") { nameShort = strSecondCell } + if nullCandidate.isEmpty, strLine.starts(with: "%nullcandidate ") { nullCandidate = strSecondCell } + if selectionKeys.isEmpty, strLine.starts(with: "%selkey ") { selectionKeys = cells[1].map(\.description).deduplicated.joined() } - if endKeys.isEmpty, strLine.contains("%endkey ") { + if endKeys.isEmpty, strLine.starts(with: "%endkey ") { endKeys = cells[1].map(\.description).deduplicated } - if wildcardKey.isEmpty, strLine.contains("%wildcardkey ") { + if wildcardKey.isEmpty, strLine.starts(with: "%wildcardkey ") { wildcardKey = cells[1].first?.description ?? "" } } @@ -232,8 +244,22 @@ public extension vChewingLM { } public func quickSetsFor(key: String) -> String? { - guard let result = quickDefMap[key] as String? else { return nil } - return result.isEmpty ? nil : result + guard !key.isEmpty else { return nil } + var result = [String]() + if let specifiedResult = quickDefMap[key], !specifiedResult.isEmpty { + result.append(contentsOf: specifiedResult.map(\.description)) + } + if supplyQuickResults, quickDefMap.isEmpty { + let fetched = charDefMap.compactMap { + $0.key.starts(with: key) ? $0 : nil + }.stableSort { + $0.key.count < $1.key.count + }.flatMap(\.value).filter { + $0.count == 1 + } + result.append(contentsOf: fetched.deduplicated.prefix(selectionKeys.count * 6)) + } + return result.isEmpty ? nil : result.joined(separator: "\t") } /// 根據給定的字根索引鍵,來獲取資料庫辭典內的對應結果。 diff --git a/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMCassetteTests.swift b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMCassetteTests.swift index be800418..62908ef8 100644 --- a/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMCassetteTests.swift +++ b/Packages/vChewing_LangModelAssembly/Tests/LangModelAssemblyTests/LMCassetteTests.swift @@ -49,8 +49,8 @@ final class LMCassetteTests: XCTestCase { XCTAssertFalse(lmCassette.quickDefMap.isEmpty) print(lmCassette.quickSetsFor(key: ",.") ?? "") XCTAssertEqual(lmCassette.keyNameMap.count, 41) - XCTAssertEqual(lmCassette.charDefMap.count, 29491) - XCTAssertEqual(lmCassette.charDefWildcardMap.count, 11946) + XCTAssertEqual(lmCassette.charDefMap.count, 29537) + XCTAssertEqual(lmCassette.charDefWildcardMap.count, 11973) XCTAssertEqual(lmCassette.octagramMap.count, 0) XCTAssertEqual(lmCassette.octagramDividedMap.count, 0) XCTAssertEqual(lmCassette.nameShort, "AR30") diff --git a/Source/Modules/InputHandler_HandleComposition.swift b/Source/Modules/InputHandler_HandleComposition.swift index 96a724cd..4a1b05f0 100644 --- a/Source/Modules/InputHandler_HandleComposition.swift +++ b/Source/Modules/InputHandler_HandleComposition.swift @@ -279,7 +279,7 @@ extension InputHandler { if !isStrokesFull { var result = generateStateOfInputting() - if !calligrapher.isEmpty, var fetched = currentLM.cassetteQuickSetsFor(key: calligrapher) { + if !calligrapher.isEmpty, var fetched = currentLM.cassetteQuickSetsFor(key: calligrapher)?.split(separator: "\t") { if prefs.useIMKCandidateWindow { fetched = fetched.deduplicated.filter { $0.description != currentLM.nullCandidateInCassette } } diff --git a/Source/Modules/InputHandler_HandleStates.swift b/Source/Modules/InputHandler_HandleStates.swift index 91913e1a..f63c277e 100644 --- a/Source/Modules/InputHandler_HandleStates.swift +++ b/Source/Modules/InputHandler_HandleStates.swift @@ -507,7 +507,7 @@ extension InputHandler { switch isConsideredEmptyForNow { case false: var result = generateStateOfInputting() - if prefs.cassetteEnabled, var fetched = currentLM.cassetteQuickSetsFor(key: calligrapher) { + if prefs.cassetteEnabled, var fetched = currentLM.cassetteQuickSetsFor(key: calligrapher)?.split(separator: "\t") { if prefs.useIMKCandidateWindow { fetched = fetched.deduplicated.filter { $0.description != currentLM.nullCandidateInCassette } }