use regex::Regex; pub fn extract_final_integer(text: &str) -> Option { 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 { 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 { 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("...\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')); } }