Files
msime-windows/engine/japanese/japanese_matrix_search.cpp
古明四季andfanlusky 0bad4c1aaa fix(engine): 修复日语分段选词吞掉后续输入 (#1020)
* fix(engine): 修复日语分段选词吞掉后续输入

* fix(engine): 日语分段选词后重建混排候选并去掉剩余输入开头的分隔符

固定候选位打开时 candidates() 返回混排列表,分段选词后不重建就还是选词前整串的候选,
下一次选词会选到过期项。ka'ta 选「か」剩下 'ta,开头的分隔符转不出假名,剩余输入一个
候选都没有;现在和拼音路径一样去掉,去掉后为空就按整串上屏处理。
已转换部分的边界改用 segments,与整句候选同一来源。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-17 17:41:01 +08:00

161 lines
5.8 KiB
C++

#include "japanese_matrix_search.h"
#include <algorithm>
#include <unordered_set>
namespace
{
constexpr std::int64_t kUnknownKanaCost = 12000;
std::vector<size_t> Utf8Boundaries(const std::string &text)
{
std::vector<size_t> boundaries{0};
size_t index = 0;
while (index < text.size())
{
const unsigned char lead = static_cast<unsigned char>(text[index]);
size_t length = lead < 0x80 ? 1 : (lead >> 5) == 0x6 ? 2 : (lead >> 4) == 0xE ? 3 : 4;
index = (std::min)(text.size(), index + length);
boundaries.push_back(index);
}
return boundaries;
}
struct MatrixNode
{
std::string text;
std::int64_t cost = 0;
std::uint16_t right_id = 0;
};
void KeepBest(std::vector<MatrixNode> &row, size_t limit)
{
if (row.size() <= limit)
return;
std::partial_sort(row.begin(), row.begin() + static_cast<std::ptrdiff_t>(limit), row.end(),
[](const MatrixNode &a, const MatrixNode &b) { return a.cost < b.cost; });
row.resize(limit);
}
void AppendUnique(std::vector<japanese::SentenceCandidate> &out, std::unordered_set<std::string> &seen,
std::string text, std::int64_t cost, size_t limit, size_t reading_length = 0)
{
if (text.empty() || out.size() >= limit)
return;
if (seen.insert(text).second)
out.push_back({std::move(text), cost, reading_length});
}
} // namespace
namespace japanese
{
JapaneseMatrixSearch::JapaneseMatrixSearch(const JapaneseSentenceDecoder &decoder) : decoder_(decoder)
{
}
std::vector<SentenceCandidate> JapaneseMatrixSearch::Search(std::string_view romaji, size_t limit) const
{
return SearchConverted(ConvertRomaji(romaji), limit);
}
std::vector<SentenceCandidate> JapaneseMatrixSearch::SearchConverted(const RomajiConversion &conversion,
size_t limit) const
{
return SearchReading(conversion.hiragana, conversion.pending, limit);
}
std::vector<SentenceCandidate> JapaneseMatrixSearch::SearchReading(const std::string &reading,
const std::string &pending, size_t limit) const
{
if (!decoder_.ready() || limit == 0)
return {};
const auto pending_kana = KanaForRomajiPrefix(pending);
if (reading.empty())
{
std::vector<SentenceCandidate> result;
std::unordered_set<std::string> seen;
for (const auto &kana : pending_kana)
{
for (const auto &lemma : decoder_.PrefixLemmas(kana, 24))
{
AppendUnique(result, seen, lemma.surface, lemma.word_cost, limit);
if (result.size() == limit)
return result;
}
}
return result;
}
const auto boundaries = Utf8Boundaries(reading);
const size_t mora_count = boundaries.size() - 1;
std::vector<std::vector<MatrixNode>> rows(mora_count + 1);
rows[0].push_back({{}, 0, 0});
for (size_t start_mora = 0; start_mora < mora_count; ++start_mora)
{
if (rows[start_mora].empty())
continue;
const size_t start_byte = boundaries[start_mora];
const size_t max_end = (std::min)(mora_count, start_mora + kMaxLemmaMora);
for (size_t end_mora = start_mora + 1; end_mora <= max_end; ++end_mora)
{
const std::string key = reading.substr(start_byte, boundaries[end_mora] - start_byte);
for (const auto &lemma : decoder_.ExactLemmas(key, 24))
{
for (const auto &previous : rows[start_mora])
{
rows[end_mora].push_back(
{previous.text + lemma.surface,
previous.cost + lemma.word_cost + decoder_.ConnectionCost(previous.right_id, lemma.left_id),
lemma.right_id});
}
}
}
const size_t next_mora = start_mora + 1;
const std::string kana = reading.substr(start_byte, boundaries[next_mora] - start_byte);
for (const auto &previous : rows[start_mora])
rows[next_mora].push_back({previous.text + kana, previous.cost + kUnknownKanaCost, 0});
for (size_t end_mora = start_mora + 1; end_mora <= max_end; ++end_mora)
KeepBest(rows[end_mora], kMaxNodeARow);
}
std::vector<SentenceCandidate> result;
std::unordered_set<std::string> seen;
auto finals = rows[mora_count];
for (auto &node : finals)
node.cost += decoder_.ConnectionCost(node.right_id, 0);
std::sort(finals.begin(), finals.end(), [](const MatrixNode &a, const MatrixNode &b) { return a.cost < b.cost; });
if (!finals.empty())
AppendUnique(result, seen, finals.front().text, finals.front().cost, limit, reading.size());
if (!pending.empty())
{
for (const auto &kana : pending_kana)
{
for (const auto &lemma : decoder_.ExactLemmas(reading + kana, 16))
AppendUnique(result, seen, lemma.surface, lemma.word_cost, limit);
}
for (const auto &lemma : decoder_.PrefixLemmasContinuing(reading, pending_kana, 48))
AppendUnique(result, seen, lemma.surface, lemma.word_cost, limit);
}
for (size_t index = 1; index < finals.size(); ++index)
AppendUnique(result, seen, std::move(finals[index].text), finals[index].cost, limit, reading.size());
size_t lemma_mora = mora_count;
while (lemma_mora > 0 && result.size() < limit)
{
const std::string key = reading.substr(0, boundaries[lemma_mora]);
for (const auto &lemma : decoder_.ExactLemmas(key, 16))
{
AppendUnique(result, seen, lemma.surface, lemma.word_cost, limit, key.size());
if (result.size() == limit)
break;
}
--lemma_mora;
}
return result;
}
} // namespace japanese