|
- use regex::Regex;
-
- pub fn extract_final_integer(text: &str) -> Option<String> {
- let boxed = Regex::new(r"\\boxed\{\s*(-?\d+)\s*\}").expect("valid boxed integer regex");
- if let Some(captures) = boxed.captures_iter(text).last() {
- return captures.get(1).map(|value| value.as_str().to_string());
- }
-
- let explicit =
- Regex::new(r"(?i)\b(?:final\s+answer|answer|the\s+answer\s+is)\s*(?:is|:|-)?\s*(-?\d+)\b")
- .expect("valid explicit integer answer regex");
- explicit
- .captures_iter(text)
- .last()
- .and_then(|captures| captures.get(1).map(|value| value.as_str().to_string()))
- }
-
- pub fn extract_choice(text: &str) -> Option<char> {
- let explicit =
- Regex::new(r"(?i)Answer[ \t]*:[ \t]*\$?([A-D])\$?").expect("valid GPQA answer regex");
- explicit
- .captures_iter(text)
- .last()
- .and_then(|captures| choice_from_capture(&captures, 1))
- }
-
- fn choice_from_capture(captures: ®ex::Captures<'_>, index: usize) -> Option<char> {
- captures
- .get(index)
- .and_then(|value| value.as_str().chars().next())
- .map(|choice| choice.to_ascii_uppercase())
- }
-
- pub fn judge_integer(output: &str, expected: &str) -> bool {
- extract_final_integer(output).is_some_and(|actual| actual == expected.trim())
- }
-
- pub fn judge_choice(output: &str, expected: char) -> bool {
- extract_choice(output).is_some_and(|actual| actual == expected.to_ascii_uppercase())
- }
-
- #[cfg(test)]
- mod tests {
- use super::*;
-
- #[test]
- fn extracts_final_integer_from_answer_prefix() {
- assert_eq!(
- extract_final_integer("Final answer: 42."),
- Some("42".to_string())
- );
- }
-
- #[test]
- fn extracts_final_integer_from_boxed_answer() {
- assert_eq!(
- extract_final_integer(r"Final: \boxed{17}"),
- Some("17".to_string())
- );
- }
-
- #[test]
- fn extracts_last_boxed_integer() {
- assert_eq!(
- extract_final_integer(r"First \boxed{12}, final \boxed{34}"),
- Some("34".to_string())
- );
- }
-
- #[test]
- fn ignores_trailing_integer_without_explicit_final_answer() {
- assert_eq!(
- extract_final_integer("The answer should be 393. I verified 30 cases."),
- None
- );
- }
-
- #[test]
- fn extracts_choice_from_answer_prefix() {
- assert_eq!(extract_choice("Answer: C"), Some('C'));
- }
-
- #[test]
- fn extracts_choice_from_dollar_wrapped_answer() {
- assert_eq!(extract_choice("Answer: $B$"), Some('B'));
- }
-
- #[test]
- fn extracts_last_answer_prefix() {
- assert_eq!(
- extract_choice("Answer: A was my first thought.\nAnswer: D"),
- Some('D')
- );
- }
-
- #[test]
- fn ignores_choice_without_answer_prefix() {
- assert_eq!(extract_choice("I choose (b)."), None);
- }
-
- #[test]
- fn ignores_bare_final_line_choice() {
- assert_eq!(extract_choice("<think>...</think>\n\nC"), None);
- }
-
- #[test]
- fn judges_integer_by_extracted_value() {
- assert!(judge_integer("Final answer: 42.", "42"));
- assert!(!judge_integer("Final answer: 43.", "42"));
- }
-
- #[test]
- fn judges_choice_by_extracted_value() {
- assert!(judge_choice("Answer: B", 'B'));
- assert!(!judge_choice("Answer: C", 'B'));
- }
- }
|