77#include " XG_Settings.hpp"
88#include " XG_CancellationManager.hpp"
99#include < clocale>
10+ #include < mutex>
1011
1112// ////////////////////////////////////////////////////////////////////////////
1213// global variables
@@ -1945,6 +1946,60 @@ void __fastcall XG_Board::DoNumberingNoCheck()
19451946 sort (xg_vHorzInfo.begin (), xg_vHorzInfo.end (), xg_placeinfo_compare_number ());
19461947}
19471948
1949+ // パターン候補のキャッシュ(高速化)。
1950+ // スレッドセーフなLRUキャッシュ。
1951+ namespace {
1952+ struct CandidateCache {
1953+ static constexpr size_t MAX_CACHE_SIZE = 500 ; // キャッシュの最大サイズ。
1954+ static constexpr size_t EVICT_COUNT = 100 ; // 一度に削除するエントリ数。
1955+ std::unordered_map<XGStringW, std::vector<XGStringW>> cache;
1956+ std::mutex cache_mutex;
1957+ size_t eviction_counter = 0 ; // 削除カウンター。
1958+
1959+ // キャッシュから候補を取得。
1960+ bool get (const XGStringW& pattern, std::vector<XGStringW>& cands) {
1961+ std::lock_guard<std::mutex> lock (cache_mutex);
1962+ auto it = cache.find (pattern);
1963+ if (it != cache.end ()) {
1964+ cands = it->second ;
1965+ return true ;
1966+ }
1967+ return false ;
1968+ }
1969+
1970+ // キャッシュに候補を保存(空の結果もキャッシュする)。
1971+ void put (const XGStringW& pattern, const std::vector<XGStringW>& cands) {
1972+ std::lock_guard<std::mutex> lock (cache_mutex);
1973+ // キャッシュサイズ制限(段階的削除でパフォーマンススパイクを軽減)。
1974+ if (cache.size () >= MAX_CACHE_SIZE ) {
1975+ // 先頭から一定数のエントリを削除(簡易LRU)。
1976+ auto it = cache.begin ();
1977+ for (size_t i = 0 ; i < EVICT_COUNT && it != cache.end (); ) {
1978+ it = cache.erase (it);
1979+ }
1980+ }
1981+ cache[pattern] = cands;
1982+ }
1983+
1984+ // キャッシュをクリア。
1985+ void clear () {
1986+ std::lock_guard<std::mutex> lock (cache_mutex);
1987+ cache.clear ();
1988+ eviction_counter = 0 ;
1989+ }
1990+ };
1991+
1992+ // 2つのキャッシュ(優先辞書と代替辞書用)。
1993+ CandidateCache g_candidate_cache_1;
1994+ CandidateCache g_candidate_cache_2;
1995+ }
1996+
1997+ // 候補キャッシュをクリアする。
1998+ void __fastcall XgClearCandidateCache (void ) {
1999+ g_candidate_cache_1.clear ();
2000+ g_candidate_cache_2.clear ();
2001+ }
2002+
19482003// 候補を取得する。
19492004template <bool t_alternative> bool __fastcall
19502005XgGetCandidatesAddBlack (
@@ -2023,68 +2078,91 @@ XgGetCandidatesAddBlack(
20232078 }
20242079 }
20252080
2081+ // 候補数の上限(メモリとパフォーマンスのバランス)。
2082+ constexpr size_t MAX_CANDIDATES = 1000 ;
2083+
2084+ // 長さでインデックス化された辞書を使用(高速化)。
2085+ const auto & dict_by_length = t_alternative ? xg_dict_2_by_length : xg_dict_1_by_length;
2086+
20262087 // すべての登録されている単語について。
2027- for (const auto & data : (t_alternative ? xg_dict_2 : xg_dict_1)) {
2028- // パターンより単語の方が長い場合、スキップする。
2029- const XGStringW& word = data.m_word ;
2030- const int wordlen = static_cast <int >(word.size ());
2031- if (wordlen > patlen)
2032- continue ;
2088+ // 長さがpatlen以下の単語のみを検索。
2089+ for (int wordlen = 2 ; wordlen <= patlen; ++wordlen) {
2090+ // 早期終了: 候補数が上限に達したら打ち切る。
2091+ if (cands.size () >= MAX_CANDIDATES )
2092+ break ;
20332093
2034- // 単語の置ける区間について調べる。
2035- const int patlen_minus_wordlen = patlen - wordlen;
2036- for (int j = 0 ; j <= patlen_minus_wordlen; j++) {
2037- // 区間[j, j + wordlen - 1]の前後に文字があったらスキップする。
2038- if (j > 0 && pattern[j - 1 ] != ZEN_SPACE )
2039- continue ;
2040- if (j < patlen_minus_wordlen && pattern[j + wordlen] != ZEN_SPACE )
2041- continue ;
2094+ auto it = dict_by_length.find (wordlen);
2095+ if (it == dict_by_length.end ())
2096+ continue ; // 該当する長さの単語がない。
20422097
2043- // 区間[j, j + wordlen - 1]に文字マスがあるか?
2044- bool bCharFound = false ;
2045- const int j_plus_wordlen = j + wordlen;
2046- for (int m = j; m < j_plus_wordlen; m++) {
2047- assert (pattern[m] != ZEN_BLACK );
2048- if (pattern[m] != ZEN_SPACE ) {
2049- bCharFound = true ;
2050- break ;
2098+ const auto & words_of_length = it->second ;
2099+
2100+ for (const auto & data : words_of_length) {
2101+ // 早期終了: 候補数が上限に達したら打ち切る。
2102+ if (cands.size () >= MAX_CANDIDATES )
2103+ break ;
2104+
2105+ const XGStringW& word = data.m_word ;
2106+
2107+ // 単語の置ける区間について調べる。
2108+ const int patlen_minus_wordlen = patlen - wordlen;
2109+ for (int j = 0 ; j <= patlen_minus_wordlen; j++) {
2110+ // 区間[j, j + wordlen - 1]の前後に文字があったらスキップする。
2111+ if (j > 0 && pattern[j - 1 ] != ZEN_SPACE )
2112+ continue ;
2113+ if (j < patlen_minus_wordlen && pattern[j + wordlen] != ZEN_SPACE )
2114+ continue ;
2115+
2116+ // 区間[j, j + wordlen - 1]に文字マスがあるか?
2117+ bool bCharFound = false ;
2118+ const int j_plus_wordlen = j + wordlen;
2119+ for (int m = j; m < j_plus_wordlen; m++) {
2120+ assert (pattern[m] != ZEN_BLACK );
2121+ if (pattern[m] != ZEN_SPACE ) {
2122+ bCharFound = true ;
2123+ break ;
2124+ }
20512125 }
2052- }
2053- if (!bCharFound)
2054- continue ;
2126+ if (!bCharFound)
2127+ continue ;
20552128
2056- // パターンが単語にマッチするか?
2057- bool bMatched = true ;
2058- for (int m = j, n = 0 ; n < wordlen; m++, n++) {
2059- if (pattern[m] != ZEN_SPACE && pattern[m] != word[n]) {
2060- bMatched = false ;
2061- break ;
2129+ // パターンが単語にマッチするか?
2130+ bool bMatched = true ;
2131+ for (int m = j, n = 0 ; n < wordlen; m++, n++) {
2132+ if (pattern[m] != ZEN_SPACE && pattern[m] != word[n]) {
2133+ bMatched = false ;
2134+ break ;
2135+ }
20622136 }
2063- }
2064- if (!bMatched)
2065- continue ;
2137+ if (!bMatched)
2138+ continue ;
20662139
2067- // マッチした。
2068- result = pattern;
2140+ // マッチした。
2141+ result = pattern;
20692142
2070- // 区間[j, j + wordlen - 1]の前後に■をおく。
2071- if (j > 0 )
2072- result[j - 1 ] = ZEN_BLACK ;
2073- if (j < patlen_minus_wordlen)
2074- result[j + wordlen] = ZEN_BLACK ;
2143+ // 区間[j, j + wordlen - 1]の前後に■をおく。
2144+ if (j > 0 )
2145+ result[j - 1 ] = ZEN_BLACK ;
2146+ if (j < patlen_minus_wordlen)
2147+ result[j + wordlen] = ZEN_BLACK ;
20752148
2076- // 区間[j, j + wordlen - 1]に単語を適用する。
2077- for (int k = 0 , m = j; k < wordlen; k++, m++)
2078- result[m] = word[k];
2149+ // 区間[j, j + wordlen - 1]に単語を適用する。
2150+ for (int k = 0 , m = j; k < wordlen; k++, m++)
2151+ result[m] = word[k];
20792152
2080- // 黒マスの連続を除外する。
2081- if (left_black_check && result[0 ] == ZEN_BLACK )
2082- continue ;
2083- if (right_black_check && result[patlen - 1 ] == ZEN_BLACK )
2084- continue ;
2153+ // 黒マスの連続を除外する。
2154+ if (left_black_check && result[0 ] == ZEN_BLACK )
2155+ continue ;
2156+ if (right_black_check && result[patlen - 1 ] == ZEN_BLACK )
2157+ continue ;
20852158
2086- // 追加する。
2087- cands.emplace_back (result);
2159+ // 追加する。
2160+ cands.emplace_back (result);
2161+
2162+ // 早期終了: 候補数が上限に達したら打ち切る。
2163+ if (cands.size () >= MAX_CANDIDATES )
2164+ break ;
2165+ }
20882166 }
20892167 }
20902168
@@ -2096,29 +2174,44 @@ XgGetCandidatesAddBlack(
20962174template <bool t_alternative> bool __fastcall
20972175XgGetCandidatesNoAddBlack (std::vector<XGStringW>& cands, const XGStringW& pattern)
20982176{
2177+ // キャッシュをチェック。
2178+ auto & cache = t_alternative ? g_candidate_cache_2 : g_candidate_cache_1;
2179+ if (cache.get (pattern, cands)) {
2180+ return !cands.empty ();
2181+ }
2182+
20992183 // 単語の長さ。
21002184 const int patlen = static_cast <int >(pattern.size ());
21012185 assert (patlen >= 2 );
21022186
21032187 // 候補をクリアする。
21042188 cands.clear ();
2189+
2190+ // 長さでインデックス化された辞書を使用(高速化)。
2191+ const auto & dict_by_length = t_alternative ? xg_dict_2_by_length : xg_dict_1_by_length;
2192+ auto it = dict_by_length.find (patlen);
2193+ if (it == dict_by_length.end ())
2194+ return false ; // 該当する長さの単語がない。
2195+
2196+ const auto & words_of_length = it->second ;
2197+
21052198 // スピードのため、予約する。
2106- if (t_alternative)
2107- cands.reserve (xg_dict_2.size () / 32 );
2108- else
2109- cands.reserve (xg_dict_1.size () / 32 );
2199+ cands.reserve (std::min<size_t >(words_of_length.size () / 8 , 1000 ));
2200+
2201+ // 候補数の上限(メモリとパフォーマンスのバランス)。
2202+ constexpr size_t MAX_CANDIDATES = 1000 ;
2203+
2204+ // 該当する長さの単語のみを検索。
2205+ for (const auto & data : words_of_length) {
2206+ // 早期終了: 候補数が上限に達したら打ち切る。
2207+ if (cands.size () >= MAX_CANDIDATES )
2208+ break ;
21102209
2111- // すべての登録された単語について。
2112- for (const auto & data : (t_alternative ? xg_dict_2 : xg_dict_1)) {
2113- // パターンと単語の長さが等しくなければ、スキップする。
21142210 const XGStringW& word = data.m_word ;
2115- const int wordlen = static_cast <int >(word.size ());
2116- if (wordlen != patlen)
2117- continue ;
21182211
2119- // 区間[0, wordlen - 1]に文字マスがあるか?
2212+ // 区間[0, patlen - 1]に文字マスがあるか?
21202213 bool bCharFound = false ;
2121- for (int k = 0 ; k < wordlen ; k++) {
2214+ for (int k = 0 ; k < patlen ; k++) {
21222215 assert (pattern[k] != ZEN_BLACK );
21232216 if (pattern[k] != ZEN_SPACE ) {
21242217 bCharFound = true ;
@@ -2130,7 +2223,7 @@ XgGetCandidatesNoAddBlack(std::vector<XGStringW>& cands, const XGStringW& patter
21302223
21312224 // パターンが単語にマッチするか?
21322225 bool bMatched = true ;
2133- for (int k = 0 ; k < wordlen ; k++) {
2226+ for (int k = 0 ; k < patlen ; k++) {
21342227 if (pattern[k] != ZEN_SPACE && pattern[k] != word[k]) {
21352228 bMatched = false ;
21362229 break ;
@@ -2143,6 +2236,9 @@ XgGetCandidatesNoAddBlack(std::vector<XGStringW>& cands, const XGStringW& patter
21432236 cands.emplace_back (word);
21442237 }
21452238
2239+ // キャッシュに保存(空の結果もキャッシュして無駄な検索を防ぐ)。
2240+ cache.put (pattern, cands);
2241+
21462242 // 候補が空でなければ成功。
21472243 return !cands.empty ();
21482244}
0 commit comments