diff --git a/src/lighteval/metrics/utils/extractive_match_utils.py b/src/lighteval/metrics/utils/extractive_match_utils.py index da0d718d7..519369a0f 100644 --- a/src/lighteval/metrics/utils/extractive_match_utils.py +++ b/src/lighteval/metrics/utils/extractive_match_utils.py @@ -103,9 +103,10 @@ def lazy_expr_regex(expr_config: ExprExtractionConfig, language: Language) -> li # Basic number patterns (no LaTeX) number_re = ( - # Format 1: Numbers with thousand separators (e.g., "1,234.56" or "1 234.56") + # Format 1: Numbers with thousand separators (e.g., "1,234.56" or "1 234.56"). The space can also be a no-break, + # narrow no-break or thin space (U+00A0, U+202F, U+2009). The last group must not be followed by another digit. r"(?:" - r"(?P-?\d{1,3}(?:[ ,]\d{3})+)(?P\.\d+)?|" + r"(?P-?\d{1,3}(?:[ ,\u00a0\u202f\u2009]\d{3})+(?!\d))(?P\.\d+)?|" # Format 2: Simple numbers with decimal point or comma (e.g., "123.45" or "123,45") r"(?P-?\d+)(?P[.,]\d+)|" # Format 3: Decimal part only (e.g., ".123") @@ -430,7 +431,7 @@ def extract_expr(match: re.Match, timeout_seconds: int) -> tuple[str | sympy.Exp if integer or decimal: # This makes sure we can convert numbers like 0001 to 1. Do note that this can convert 0 to '', so we assume an empty string was 0 and convert it back afterwards. - integer = integer.translate(str.maketrans("", "", ", ")).lstrip("0") + integer = integer.translate(str.maketrans("", "", ", \u00a0\u202f\u2009")).lstrip("0") if len(integer) == 0: integer = "0" diff --git a/tests/unit/metrics/test_extractive_match.py b/tests/unit/metrics/test_extractive_match.py index d2fd71606..709e5333f 100644 --- a/tests/unit/metrics/test_extractive_match.py +++ b/tests/unit/metrics/test_extractive_match.py @@ -1146,3 +1146,26 @@ def test_math_extraction_additional_cases(gold, pred, expected): ) def test_math_numina_cases(gold, pred, expected): assert compare_strings(gold, pred, match_types=["latex", "expr"]) == expected + + +@pytest.mark.parametrize( + "gold,pred,expected", + [ + # No-break, narrow no-break and thin spaces are digit-group separators, like the ASCII space and the comma + ("2500", "ANSWER: 2\u202f500", 1), + ("2", "ANSWER: 2\u202f500", 0), + ("2500", "ANSWER: 2\u00a0500", 1), + ("2", "ANSWER: 2\u00a0500", 0), + ("2500", "The total is 2\u2009500.", 1), + ("500", "The total is 2\u2009500.", 0), + ("1250000", "ANSWER: 1\u202f250\u202f000", 1), + ("1234.5", "ANSWER: 1\u202f234.5", 1), + ("2500", "ANSWER: 2 500", 1), + # A group that is not exactly three digits is not joined + ("7", "ANSWER: 7\u202f1234", 1), + ("7", "ANSWER: 7 1234", 1), + ("12", "ANSWER: 12\u202fapples", 1), + ], +) +def test_typographic_digit_group_spaces(gold, pred, expected): + assert compare_strings(gold, pred, match_types=["latex", "expr"]) == expected