Create solver_probability.py
Browse files- solver_probability.py +155 -0
solver_probability.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
from math import comb, perm
|
| 5 |
+
from typing import Optional, List
|
| 6 |
+
|
| 7 |
+
from models import SolverResult
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _nums(text: str) -> List[int]:
|
| 11 |
+
return [int(x) for x in re.findall(r"-?\d+", text)]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def solve_probability(text: str) -> Optional[SolverResult]:
|
| 15 |
+
lower = (text or "").lower()
|
| 16 |
+
|
| 17 |
+
prob_words = [
|
| 18 |
+
"probability", "chance", "likely", "dice", "die", "coin", "cards",
|
| 19 |
+
"card", "deck", "random", "at random", "marble", "ball", "urn",
|
| 20 |
+
"without replacement", "with replacement"
|
| 21 |
+
]
|
| 22 |
+
if not any(w in lower for w in prob_words):
|
| 23 |
+
return None
|
| 24 |
+
|
| 25 |
+
nums = _nums(lower)
|
| 26 |
+
|
| 27 |
+
# Pattern 1: simple favorable / total
|
| 28 |
+
m = re.search(
|
| 29 |
+
r"probability.*?(\d+).*?out of.*?(\d+)",
|
| 30 |
+
lower,
|
| 31 |
+
)
|
| 32 |
+
if m:
|
| 33 |
+
fav = int(m.group(1))
|
| 34 |
+
total = int(m.group(2))
|
| 35 |
+
if total == 0:
|
| 36 |
+
return None
|
| 37 |
+
result = fav / total
|
| 38 |
+
return SolverResult(
|
| 39 |
+
domain="quant",
|
| 40 |
+
solved=True,
|
| 41 |
+
topic="probability",
|
| 42 |
+
answer_value=f"{result:g}",
|
| 43 |
+
internal_answer=f"{result:g}",
|
| 44 |
+
steps=[
|
| 45 |
+
"Use probability = favorable outcomes ÷ total outcomes.",
|
| 46 |
+
],
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
# Pattern 2: one die
|
| 50 |
+
if ("die" in lower or "dice" in lower) and "even" in lower:
|
| 51 |
+
result = 3 / 6
|
| 52 |
+
return SolverResult(
|
| 53 |
+
domain="quant",
|
| 54 |
+
solved=True,
|
| 55 |
+
topic="probability",
|
| 56 |
+
answer_value=f"{result:g}",
|
| 57 |
+
internal_answer=f"{result:g}",
|
| 58 |
+
steps=[
|
| 59 |
+
"A fair die has 6 equally likely outcomes.",
|
| 60 |
+
"Even outcomes are 2, 4, and 6, so there are 3 favorable outcomes.",
|
| 61 |
+
"Probability = 3/6.",
|
| 62 |
+
],
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
if ("coin" in lower) and ("head" in lower or "heads" in lower):
|
| 66 |
+
# one fair coin if not otherwise specified
|
| 67 |
+
if "twice" not in lower and "two" not in lower:
|
| 68 |
+
result = 1 / 2
|
| 69 |
+
return SolverResult(
|
| 70 |
+
domain="quant",
|
| 71 |
+
solved=True,
|
| 72 |
+
topic="probability",
|
| 73 |
+
answer_value=f"{result:g}",
|
| 74 |
+
internal_answer=f"{result:g}",
|
| 75 |
+
steps=[
|
| 76 |
+
"A fair coin has 2 equally likely outcomes.",
|
| 77 |
+
"Heads is 1 favorable outcome out of 2.",
|
| 78 |
+
],
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
# Pattern 3: at least one head in n fair tosses
|
| 82 |
+
m = re.search(r"(?:coin.*?tossed|toss.*?coin).*?(\d+)\s+times", lower)
|
| 83 |
+
if m and "at least one head" in lower:
|
| 84 |
+
n = int(m.group(1))
|
| 85 |
+
result = 1 - (1 / 2) ** n
|
| 86 |
+
return SolverResult(
|
| 87 |
+
domain="quant",
|
| 88 |
+
solved=True,
|
| 89 |
+
topic="probability",
|
| 90 |
+
answer_value=f"{result:g}",
|
| 91 |
+
internal_answer=f"{result:g}",
|
| 92 |
+
steps=[
|
| 93 |
+
"Use the complement: at least one head = 1 − P(no heads).",
|
| 94 |
+
"For a fair coin, P(no heads in n tosses) = (1/2)^n.",
|
| 95 |
+
],
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
# Pattern 4: drawing one item from a set
|
| 99 |
+
if any(w in lower for w in ["marble", "ball", "urn", "bag"]) and len(nums) >= 2:
|
| 100 |
+
if any(w in lower for w in ["red", "blue", "green", "white", "black"]):
|
| 101 |
+
total = sum(nums[:-1]) if "total" not in lower else nums[-1]
|
| 102 |
+
# conservative: if exactly 2 nums, assume favorable then total
|
| 103 |
+
if len(nums) == 2:
|
| 104 |
+
favorable, total = nums[0], nums[1]
|
| 105 |
+
else:
|
| 106 |
+
favorable = nums[0]
|
| 107 |
+
if total == 0:
|
| 108 |
+
total = sum(nums)
|
| 109 |
+
if total == 0:
|
| 110 |
+
return None
|
| 111 |
+
result = favorable / total
|
| 112 |
+
return SolverResult(
|
| 113 |
+
domain="quant",
|
| 114 |
+
solved=True,
|
| 115 |
+
topic="probability",
|
| 116 |
+
answer_value=f"{result:g}",
|
| 117 |
+
internal_answer=f"{result:g}",
|
| 118 |
+
steps=[
|
| 119 |
+
"Probability = number of favorable items ÷ total number of items.",
|
| 120 |
+
],
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# Pattern 5: combinations-based probability
|
| 124 |
+
# Example: choose 2 from 5 red and 3 blue, both red
|
| 125 |
+
m = re.search(
|
| 126 |
+
r"(\d+)\s+red.*?(\d+)\s+blue.*?choose\s+(\d+).*?both red",
|
| 127 |
+
lower,
|
| 128 |
+
)
|
| 129 |
+
if m:
|
| 130 |
+
red = int(m.group(1))
|
| 131 |
+
blue = int(m.group(2))
|
| 132 |
+
choose_n = int(m.group(3))
|
| 133 |
+
total = red + blue
|
| 134 |
+
if choose_n != 2 or total < 2 or red < 2:
|
| 135 |
+
return None
|
| 136 |
+
result = comb(red, 2) / comb(total, 2)
|
| 137 |
+
return SolverResult(
|
| 138 |
+
domain="quant",
|
| 139 |
+
solved=True,
|
| 140 |
+
topic="probability",
|
| 141 |
+
answer_value=f"{result:g}",
|
| 142 |
+
internal_answer=f"{result:g}",
|
| 143 |
+
steps=[
|
| 144 |
+
"Count favorable selections.",
|
| 145 |
+
"Count total possible selections.",
|
| 146 |
+
"Probability = favorable ÷ total.",
|
| 147 |
+
],
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
# Pattern 6: at least / exactly from combinations
|
| 151 |
+
if "exactly" in lower and "probability" in lower and len(nums) >= 4:
|
| 152 |
+
# too ambiguous, skip unless explicit structure is added later
|
| 153 |
+
return None
|
| 154 |
+
|
| 155 |
+
return None
|