| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
- <?php
- namespace App\Support;
- class GradingMarkBoxCounter
- {
- public function countFillBlanks(?string $text): int
- {
- $text = (string) $text;
- $count = 0;
- $count += preg_match_all('/_{2,}/u', $text, $m);
- $count += preg_match_all('/(\s*)/u', $text, $m);
- $count += preg_match_all('/\(\s*\)/', $text, $m);
- return max(1, $count);
- }
- public function countAnswerSteps(?string $text, ?string $stem = null): int
- {
- $stepCount = $this->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('/<br\s*\/?>/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;
- }
- // 与题干显示层保持一致:把小题起始语境标准化到 <br>(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<br>$2 ', $normalized) ?? $normalized;
- $normalized = preg_replace('/(?:(?:\\\\r\\\\n|\\\\n)|(?:\r?\n)|(?:<br\s*\/?>)|\s)+\s*([((][1-9][0-9]*[))])\s*/u', '<br>$1 ', $normalized) ?? $normalized;
- $normalized = preg_replace('/(求出|求解|求|写出|计算|证明|判断|化简)\s*([((][1-9][0-9]*[))])\s*/u', '$1<br>$2 ', $normalized) ?? $normalized;
- // 统计“行首或换行后”的编号数量,作为子题数量
- preg_match_all('/(?:^|<br>\s*)([((][1-9][0-9]*[))])/u', $normalized, $series);
- $seriesCount = count($series[1] ?? []);
- return max(1, $seriesCount);
- }
- }
|