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); } }