countExplicitAnswerSteps($text);
if ($stepCount > 0) {
return $stepCount;
}
return $this->inferSubQuestionCount($stem);
}
/**
* 判题卡解答题方框数:
* - 优先按题干小题编号系列计数((1)(2)...)
* - 若不存在系列小题,则固定 1 框(避免被解析步骤数放大)
*/
public function countAnswerMarkBoxes(?string $stem, ?string $solution = null): int
{
$minFromStem = $this->inferSubQuestionCount($stem);
$stepCount = $this->countExplicitAnswerSteps($solution);
return max($minFromStem, $stepCount > 0 ? $stepCount : 1);
}
/**
* 仅识别“显式步骤标记”并计数:
* - 步骤1: / 步骤一:
* - 第1步: / 第一步:
* 要求位于段首(行首或标点后),降低误判。
*/
private function countExplicitAnswerSteps(?string $text): int
{
$text = trim((string) $text);
if ($text === '') {
return 0;
}
$text = preg_replace('/
/iu', "\n", $text) ?? $text;
$stepLabelPattern = '(步骤\s*[0-9一二三四五六七八九十百零两]+\s*[::]?)';
$anchorPattern = '/(?:^|[\r\n。;;!?!?])\s*' . $stepLabelPattern . '/u';
preg_match_all($anchorPattern, $text, $matches);
$count = count($matches[0] ?? []);
return max(0, $count);
}
/**
* 当解析里没有步骤时,按题干中的小题“系列编号”估算判题卡方框数。
* 仅在至少出现 2 个编号时生效,避免误把 f(1) 这类数学表达当作小题。
*/
private function inferSubQuestionCount(?string $stem): int
{
$stem = (string) $stem;
if ($stem === '') {
return 1;
}
preg_match_all('/[((][1-9][0-9]*[))]/u', $stem, $allMarkers);
$markerCount = count($allMarkers[0] ?? []);
if ($markerCount < 2) {
return 1;
}
// 与题干显示层保持一致:把小题起始语境标准化到
(n)
$normalized = preg_replace('/^\s*([((][1-9][0-9]*[))])\s*/u', '$1 ', $stem) ?? $stem;
$normalized = preg_replace('/([。;;!?!?::.])\s*([((][1-9][0-9]*[))])\s*/u', '$1
$2 ', $normalized) ?? $normalized;
$normalized = preg_replace('/(?:(?:\\\\r\\\\n|\\\\n)|(?:\r?\n)|(?:
)|\s)+\s*([((][1-9][0-9]*[))])\s*/u', '
$1 ', $normalized) ?? $normalized;
$normalized = preg_replace('/(求出|求解|求|写出|计算|证明|判断|化简)\s*([((][1-9][0-9]*[))])\s*/u', '$1
$2 ', $normalized) ?? $normalized;
// 统计“行首或换行后”的编号数量,作为子题数量
preg_match_all('/(?:^|
\s*)([((][1-9][0-9]*[))])/u', $normalized, $series);
$seriesCount = count($series[1] ?? []);
return max(1, $seriesCount);
}
}