GradingMarkBoxCounter.php 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. <?php
  2. namespace App\Support;
  3. class GradingMarkBoxCounter
  4. {
  5. public function countFillBlanks(?string $text): int
  6. {
  7. $text = (string) $text;
  8. $count = 0;
  9. $count += preg_match_all('/_{2,}/u', $text, $m);
  10. $count += preg_match_all('/(\s*)/u', $text, $m);
  11. $count += preg_match_all('/\(\s*\)/', $text, $m);
  12. return max(1, $count);
  13. }
  14. public function countAnswerSteps(?string $text, ?string $stem = null): int
  15. {
  16. $stepCount = $this->countExplicitAnswerSteps($text);
  17. if ($stepCount > 0) {
  18. return $stepCount;
  19. }
  20. return $this->inferSubQuestionCount($stem);
  21. }
  22. /**
  23. * 判题卡解答题方框数:
  24. * - 优先按题干小题编号系列计数((1)(2)...)
  25. * - 若不存在系列小题,则固定 1 框(避免被解析步骤数放大)
  26. */
  27. public function countAnswerMarkBoxes(?string $stem, ?string $solution = null): int
  28. {
  29. $minFromStem = $this->inferSubQuestionCount($stem);
  30. $stepCount = $this->countExplicitAnswerSteps($solution);
  31. return max($minFromStem, $stepCount > 0 ? $stepCount : 1);
  32. }
  33. /**
  34. * 仅识别“显式步骤标记”并计数:
  35. * - 步骤1: / 步骤一:
  36. * - 第1步: / 第一步:
  37. * 要求位于段首(行首或标点后),降低误判。
  38. */
  39. private function countExplicitAnswerSteps(?string $text): int
  40. {
  41. $text = trim((string) $text);
  42. if ($text === '') {
  43. return 0;
  44. }
  45. $text = preg_replace('/<br\s*\/?>/iu', "\n", $text) ?? $text;
  46. $stepLabelPattern = '(步骤\s*[0-9一二三四五六七八九十百零两]+\s*[::]?)';
  47. $anchorPattern = '/(?:^|[\r\n。;;!?!?])\s*' . $stepLabelPattern . '/u';
  48. preg_match_all($anchorPattern, $text, $matches);
  49. $count = count($matches[0] ?? []);
  50. return max(0, $count);
  51. }
  52. /**
  53. * 当解析里没有步骤时,按题干中的小题“系列编号”估算判题卡方框数。
  54. * 仅在至少出现 2 个编号时生效,避免误把 f(1) 这类数学表达当作小题。
  55. */
  56. private function inferSubQuestionCount(?string $stem): int
  57. {
  58. $stem = (string) $stem;
  59. if ($stem === '') {
  60. return 1;
  61. }
  62. preg_match_all('/[((][1-9][0-9]*[))]/u', $stem, $allMarkers);
  63. $markerCount = count($allMarkers[0] ?? []);
  64. if ($markerCount < 2) {
  65. return 1;
  66. }
  67. // 与题干显示层保持一致:把小题起始语境标准化到 <br>(n)
  68. $normalized = preg_replace('/^\s*([((][1-9][0-9]*[))])\s*/u', '$1 ', $stem) ?? $stem;
  69. $normalized = preg_replace('/([。;;!?!?::.])\s*([((][1-9][0-9]*[))])\s*/u', '$1<br>$2 ', $normalized) ?? $normalized;
  70. $normalized = preg_replace('/(?:(?:\\\\r\\\\n|\\\\n)|(?:\r?\n)|(?:<br\s*\/?>)|\s)+\s*([((][1-9][0-9]*[))])\s*/u', '<br>$1 ', $normalized) ?? $normalized;
  71. $normalized = preg_replace('/(求出|求解|求|写出|计算|证明|判断|化简)\s*([((][1-9][0-9]*[))])\s*/u', '$1<br>$2 ', $normalized) ?? $normalized;
  72. // 统计“行首或换行后”的编号数量,作为子题数量
  73. preg_match_all('/(?:^|<br>\s*)([((][1-9][0-9]*[))])/u', $normalized, $series);
  74. $seriesCount = count($series[1] ?? []);
  75. return max(1, $seriesCount);
  76. }
  77. }