File size: 4,203 Bytes
f2f7976
28ddd07
 
f2f7976
 
 
 
28ddd07
f2f7976
 
 
 
28ddd07
f2f7976
 
 
28ddd07
 
f2f7976
 
28ddd07
 
 
 
f2f7976
28ddd07
 
 
 
 
 
f2f7976
 
 
 
 
 
28ddd07
 
 
 
 
f2f7976
28ddd07
 
 
 
 
 
 
 
 
f2f7976
 
 
28ddd07
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f2f7976
 
28ddd07
 
 
 
 
 
 
 
f2f7976
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# unit_parser_fixed.py
import re
from typing import Optional

# =============================
# Currency / money patterns
# =============================
CURRENCY_PREFIX = r"[$£€₹¥₩₦]|A\$|C\$|CHF|د\.إ"
MONEY_RE = re.compile(
    rf"(?P<prefix>{CURRENCY_PREFIX})?\s*(?P<val>\d+(?:,\d{{3}})*(?:\.\d{{1,2}})?)\s*(?P<word>(?:[a-zA-Z]+(?:\s[a-zA-Z]+)?)s?)?",
    re.IGNORECASE,
)

# =============================
# Unit / group price patterns
# =============================
UNIT_LIKE_RE = re.compile(
    r"(?P<q1>\d+)\s+(?P<item>[\w\s]+?)\s+(?:is|are|cost|costs|priced at|=)\s+"
    r"(?P<cost>\d+(?:,\d{3})*(?:\.\d+)?(?:\s*\w+)?)"
    r"[,\.]?\s*(?:how much|what\s*'?s|price|cost).*?(?P<q2>\d+)\s+(?P=item)\b",
    re.IGNORECASE,
)

GROUP_LIKE_RE = re.compile(
    r"(?P<q1>\d+)\s+(?P<item>\w+?s?)\s+for\s+(?P<cost>.+?)\b[,\.]?\s*(?:how much|what\s*'?s|price|cost).*?(?P<q2>\d+)\s+(?P=item)\b",
    re.IGNORECASE,
)

ADD_RE = re.compile(r"^\s*([\d+\s=\?]+)\s*$")
NUM_RE = re.compile(r"\d+")

# =============================
# Helpers
# =============================
def money(v: float) -> str:
    return str(int(round(v))) if abs(v - round(v)) < 1e-9 else f"{v:.2f}"

def parse_money_str(s: str) -> str:
    m = MONEY_RE.search(s)
    if not m:
        return s.strip()
    prefix = (m.group('prefix') or '').strip()
    val = float((m.group('val') or '').replace(',', ''))
    word = (m.group('word') or '').strip()
    if prefix:
        return f"{prefix}{money(val)}"
    if word:
        if word.lower().endswith('s') and abs(val-1.0) < 1e-9:
            word = word[:-1]
        return f"{money(val)} {word}"
    return money(val)

# =============================
# Canonicalizers
# =============================
def canonicalize_nl(text: str) -> Optional[str]:
    t = text.strip()

    m = UNIT_LIKE_RE.search(t)
    if m:
        q1 = int(m.group('q1')); item = m.group('item').rstrip('.')
        cost = parse_money_str(m.group('cost'))
        q2 = int(m.group('q2'))
        return f"q: {q1} {item} cost {cost}. how much is {q2} {item}? a:"

    m = GROUP_LIKE_RE.search(t)
    if m:
        q1 = int(m.group('q1')); item = m.group('item').rstrip('.')
        cost = parse_money_str(m.group('cost'))
        q2 = int(m.group('q2'))
        return f"q: {q1} {item} for {cost}, what's the price for {q2} {item}? a:"

    return None

def is_addition_query(text: str) -> bool:
    t = text.strip()
    if not ADD_RE.match(t):
        return False
    return ('+' in t) and (len(NUM_RE.findall(t)) >= 2)

def canonicalize_addition(text: str) -> str:
    nums = [int(x) for x in NUM_RE.findall(text)]
    return " + ".join(str(n) for n in nums)

def sum_addends(text: str) -> int:
    return sum(int(x) for x in NUM_RE.findall(text))

# =============================
# Deterministic solver
# =============================
_NUMBER_TOKEN_RE = re.compile(r"\d+(?:,\d{3})*(?:\.\d+)?")

def try_solve_unit_like(text: str) -> Optional[str]:
    m = UNIT_LIKE_RE.search(text)
    if not m:
        return None

    q1 = int(m.group('q1'))
    q2 = int(m.group('q2'))
    item = (m.group('item') or '').strip().rstrip('.')
    cost_raw = (m.group('cost') or '').strip()

    nums = _NUMBER_TOKEN_RE.findall(cost_raw)
    if not nums:
        return None
    token = max(nums, key=lambda s: len(s.replace(',', '')))
    cost_val = float(token.replace(',', ''))

    if q1 == 0:
        return "final answer: undefined"

    unit = cost_val / q1
    total = unit * q2

    mcur = MONEY_RE.search(cost_raw)
    prefix = (mcur.group('prefix') or '').strip() if mcur else ''
    word   = (mcur.group('word') or '').strip()   if mcur else ''

    def _fmt(v: float) -> str:
        s = money(v)
        if prefix:
            return f"{prefix}{s}"
        if word:
            sing = word[:-1] if word.lower().endswith('s') and abs(v-1.0) < 1e-9 else word
            return f"{s} {sing}"
        return s

    base_item = item[:-1] if item.lower().endswith('s') else item
    return (
        f"step 1: find unit price: {_fmt(cost_val)} / {q1} = {_fmt(unit)} per {base_item}. "
        f"step 2: multiply by {q2}: {_fmt(unit)} × {q2} = {_fmt(total)}. "
        f"final answer: {_fmt(total)}"
    )