0% found this document useful (0 votes)
6 views12 pages

Python (Fractions)

The document describes a Python console application for mastering fractions, featuring educational tools like quizzes on fraction operations, simplification, and comparison. It includes functionalities for tracking scores, time, and mistakes, as well as saving sessions in JSON format. The app is designed solely for educational purposes.

Uploaded by

Kashmala Durrani
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views12 pages

Python (Fractions)

The document describes a Python console application for mastering fractions, featuring educational tools like quizzes on fraction operations, simplification, and comparison. It includes functionalities for tracking scores, time, and mistakes, as well as saving sessions in JSON format. The app is designed solely for educational purposes.

Uploaded by

Kashmala Durrani
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

"""

Fractions Mastery (Console App)


-------------------------------
A long, educational Python program for learning fractions.

Features:
- Fraction basics (simplify, compare)
- Convert improper <-> mixed numbers
- Operations: +, -, *, /
- Random practice questions (levels)
- Option for mixed-number answers
- Score + time tracking
- Mistake review
- Save sessions to JSON

Educational purpose only.


"""

from __future__ import annotations

import random
import time
import json
from dataclasses import dataclass, asdict
from fractions import Fraction
from pathlib import Path
from typing import List, Optional, Tuple
import math

# -----------------------------
# Data Models
# -----------------------------

@dataclass
class Q:
prompt: str
correct_value: Fraction
user_value: Optional[Fraction] = None
correct: Optional[bool] = None
time_taken_s: Optional[float] = None

def finalize(self, user_value: Fraction, elapsed: float) -> None:


self.user_value = user_value
[Link] = (user_value == self.correct_value)
self.time_taken_s = elapsed
@dataclass
class Session:
mode: str
difficulty: str
started_at: float
ended_at: float
total_questions: int
correct: int
wrong: int
accuracy_percent: float
avg_time_s: float
questions: List[Q]

# -----------------------------
# Helpers
# -----------------------------

def fmt_time(s: float) -> str:


if s < 60:
return f"{s:.2f}s"
m = int(s // 60)
r = s % 60
return f"{m}m {r:.2f}s"

def read_choice(prompt: str, choices: List[str]) -> str:


m = {[Link](): c for c in choices}
while True:
raw = input(prompt).strip().lower()
if raw in m:
return m[raw]
print(f"Choose one of: {', '.join(choices)}")

def read_int(prompt: str, min_val: int | None = None, max_val: int | None = None) -> int:
while True:
raw = input(prompt).strip()
try:
v = int(raw)
except ValueError:
print("Enter a valid integer.")
continue
if min_val is not None and v < min_val:
print(f"Must be >= {min_val}.")
continue
if max_val is not None and v > max_val:
print(f"Must be <= {max_val}.")
continue
return v

def clear() -> None:


print("\n" * 60)

# -----------------------------
# Fraction formatting
# -----------------------------

def frac_to_mixed(f: Fraction) -> Tuple[int, Fraction]:


"""Return (whole, remainder_fraction_positive)."""
sign = -1 if f < 0 else 1
f = abs(f)
whole = [Link] // [Link]
rem = Fraction([Link] % [Link], [Link])
return sign * whole, rem

def format_fraction(f: Fraction, style: str = "simple") -> str:


"""
style:
- simple: 'a/b' or '0' or '3'
- mixed: 'w r/d' if improper
"""
if style == "mixed":
if [Link] == 1:
return str([Link])
w, r = frac_to_mixed(f)
if abs(f) < 1:
return f"{[Link]}/{[Link]}"
if r == 0:
return str(w)
# handle negative mixed numbers: -2 1/3
sign = "-" if f < 0 else ""
return f"{sign}{abs(w)} {[Link]}/{[Link]}"
else:
# simple
if [Link] == 1:
return str([Link])
return f"{[Link]}/{[Link]}"

# -----------------------------
# Parsing user answers
# -----------------------------
def parse_fraction_input(s: str) -> Fraction:
"""
Accepts:
- "a/b" (e.g., 3/4, -10/7)
- "w a/b" mixed (e.g., 2 1/3, -2 1/3)
- integer (e.g., 5, -1)
- decimal (e.g., 0.25) -> converted to Fraction exactly as float string via Fraction(s)
"""
s = [Link]()
if not s:
raise ValueError("Empty input")

# Mixed number: "w a/b"


parts = [Link]()
if len(parts) == 2 and "/" in parts[1]:
w = int(parts[0])
num, den = parts[1].split("/", 1)
num_i = int(num)
den_i = int(den)
if den_i == 0:
raise ValueError("Denominator cannot be 0")
rem = Fraction(num_i, den_i)
if rem < 0:
raise ValueError("Use positive remainder in mixed form (e.g., -2 1/3, not -2 -1/3).")
if w < 0:
return Fraction(w, 1) - rem
return Fraction(w, 1) + rem

# Simple fraction "a/b"


if "/" in s:
num, den = [Link]("/", 1)
num_i = int(num)
den_i = int(den)
if den_i == 0:
raise ValueError("Denominator cannot be 0")
return Fraction(num_i, den_i)

# Integer or decimal
try:
return Fraction(int(s), 1)
except ValueError:
# decimal string
return Fraction(s)

def ask_fraction(prompt: str) -> Fraction:


while True:
raw = input(prompt).strip()
try:
return parse_fraction_input(raw)
except Exception as e:
print(f"Invalid fraction input: {e}")
print("Examples: 3/4 | 2 1/3 | -5/2 | 7 | 0.25")

# -----------------------------
# Generators
# -----------------------------

def difficulty_bounds(level: str) -> Tuple[int, int]:


if level == "easy":
return 1, 9
if level == "medium":
return 1, 15
if level == "hard":
return 1, 30
if level == "insane":
return 1, 60
return 1, 12

def random_fraction(level: str, *, proper: bool = False, allow_negative: bool = False) ->
Fraction:
lo, hi = difficulty_bounds(level)
den = [Link](lo, hi)
num = [Link](lo, hi)

if proper:
num = [Link](0, den - 1) if den > 1 else 0

if allow_negative and [Link]() < 0.30:


num = -num

# Ensure non-zero denominator


return Fraction(num, den)

def make_operation_question(level: str, op: str) -> Tuple[str, Fraction]:


a = random_fraction(level, proper=False, allow_negative=False)
b = random_fraction(level, proper=False, allow_negative=False)

# Avoid division by 0
if op == "/":
while b == 0:
b = random_fraction(level, proper=False, allow_negative=False)
expr = f"{format_fraction(a)} {op} {format_fraction(b)}"
if op == "+":
ans = a + b
elif op == "-":
ans = a - b
elif op == "*":
ans = a * b
elif op == "/":
ans = a / b
else:
raise ValueError("Bad op")
return expr, ans

def make_compare_question(level: str) -> Tuple[str, Fraction]:


a = random_fraction(level, proper=False, allow_negative=False)
b = random_fraction(level, proper=False, allow_negative=False)
# We store answer as a Fraction but for compare we’ll ask user to type 1, 0, -1
# 1 => a > b
# 0 => a == b
# -1 => a < b
if a > b:
correct = Fraction(1, 1)
symbol = " ? "
elif a < b:
correct = Fraction(-1, 1)
symbol = " ? "
else:
correct = Fraction(0, 1)
symbol = " ? "
prompt = (
f"Compare: {format_fraction(a)} {symbol} {format_fraction(b)}\n"
"Type 1 if left>right, 0 if equal, -1 if left<right: "
)
return prompt, correct

def make_simplify_question(level: str) -> Tuple[str, Fraction]:


lo, hi = difficulty_bounds(level)
# Make a reducible fraction by multiplying numerator and denominator
base = random_fraction(level, proper=False, allow_negative=False)
k = [Link](2, max(2, min(10, hi)))
f = Fraction([Link] * k, [Link] * k)
prompt = f"Simplify: {[Link]}/{[Link]} = "
return prompt, base # simplified is base
# -----------------------------
# Quiz Runner
# -----------------------------

def run_quiz(mode: str, difficulty: str, n: int, answer_style: str) -> Session:
started = [Link]()

questions: List[Q] = []
correct = 0
wrong = 0
total_time = 0.0

clear()
print("Fractions Quiz")
print("==============")
print(f"Mode: {mode} | Difficulty: {difficulty} | Questions: {n} | Answer style:
{answer_style}\n")

for i in range(1, n + 1):


if mode == "operations":
op = [Link](["+", "-", "*", "/"])
expr, ans = make_operation_question(difficulty, op)
prompt = f"Q{i}/{n}: {expr} = "
q = Q(prompt=expr, correct_value=ans)

t0 = [Link]()
user = ask_fraction(prompt)
elapsed = [Link]() - t0

[Link](user, elapsed)
total_time += elapsed


if [Link]:
print(" Correct!\n")
correct += 1


else:
print(f" Wrong. Correct: {format_fraction(q.correct_value, answer_style)}\n")
wrong += 1
[Link](q)

elif mode == "simplify":


prompt, ans = make_simplify_question(difficulty)
q = Q(prompt=prompt, correct_value=ans)

t0 = [Link]()
user = ask_fraction(prompt)
elapsed = [Link]() - t0
[Link](user, elapsed)
total_time += elapsed


if [Link]:
print(" Correct!\n")
correct += 1


else:
print(f" Wrong. Correct: {format_fraction(q.correct_value, answer_style)}\n")
wrong += 1
[Link](q)

elif mode == "compare":


prompt, ans = make_compare_question(difficulty)
q = Q(prompt="compare", correct_value=ans)

t0 = [Link]()
val = read_int(prompt, min_val=-1, max_val=1)
elapsed = [Link]() - t0

user = Fraction(val, 1)
[Link](user, elapsed)
total_time += elapsed


if [Link]:
print(" Correct!\n")
correct += 1
else:
# show actual relation
# reconstruct? simplest: compute from correct_value
if q.correct_value == 1:
rel = "left > right"
elif q.correct_value == -1:
rel = "left < right"
else:


rel = "left == right"
print(f" Wrong. Correct relation: {rel}\n")
wrong += 1
[Link](q)

else:
raise ValueError("Unknown mode")

ended = [Link]()
accuracy = (correct / n) * 100.0 if n else 0.0
avg_time = (total_time / n) if n else 0.0

return Session(
mode=mode,
difficulty=difficulty,
started_at=started,
ended_at=ended,
total_questions=n,
correct=correct,
wrong=wrong,
accuracy_percent=accuracy,
avg_time_s=avg_time,
questions=questions,
)

# -----------------------------
# Reports + Saving
# -----------------------------

def print_summary(s: Session) -> None:


print("\nSession Summary")
print("===============")
print(f"Mode: {[Link]}")
print(f"Difficulty: {[Link]}")
print(f"Questions: {s.total_questions}")
print(f"Correct: {[Link]}")
print(f"Wrong: {[Link]}")
print(f"Accuracy: {s.accuracy_percent:.2f}%")
print(f"Avg time/question: {fmt_time(s.avg_time_s)}")
print(f"Total duration: {fmt_time(s.ended_at - s.started_at)}")

def print_mistakes(s: Session, answer_style: str) -> None:


mistakes = [q for q in [Link] if [Link] is False]

🔥
if not mistakes:
print("\nNo mistakes — you cooked ")
return

print("\nMistakes Review")
print("==============")
for i, q in enumerate(mistakes, start=1):
if [Link] == "compare":
# user_value is -1/0/1
uv = int(q.user_value) if q.user_value is not None else None
cv = int(q.correct_value)
print(f"{i:>2}. Compare question | you: {uv} | correct: {cv}")
else:
uv = format_fraction(q.user_value, answer_style) if q.user_value is not None else "?"
cv = format_fraction(q.correct_value, answer_style)
print(f"{i:>2}. {[Link]} | you: {uv} | correct: {cv} | time: {fmt_time(q.time_taken_s or
0)}")
def save_session(s: Session, filepath: str = "fractions_session.json") -> Path:
payload = {
"mode": [Link],
"difficulty": [Link],
"started_at": s.started_at,
"ended_at": s.ended_at,
"total_questions": s.total_questions,
"correct": [Link],
"wrong": [Link],
"accuracy_percent": s.accuracy_percent,
"avg_time_s": s.avg_time_s,
"questions": [
{
"prompt": [Link],
"correct_value": str(q.correct_value),
"user_value": str(q.user_value) if q.user_value is not None else None,
"correct": [Link],
"time_taken_s": q.time_taken_s,
}
for q in [Link]
],
}
path = Path(filepath)
path.write_text([Link](payload, indent=2), encoding="utf-8")
return path

# -----------------------------
# Learning Tools (Non-quiz)
# -----------------------------

def show_fraction_table(denominator: int, upto_numerator: int = 12) -> None:


print(f"\nFractions with denominator {denominator}")
print("-" * 35)
for n in range(1, upto_numerator + 1):
f = Fraction(n, denominator)
print(f"{n}/{denominator} = {float(f):.4f} (simplified: {format_fraction(f)})")
print("-" * 35)

def demo_operations() -> None:


print("\nQuick Demo (Fractions)")
print("----------------------")
a = Fraction(3, 4)
b = Fraction(5, 6)
print(f"a = {a}, b = {b}")
print(f"a + b = {a + b} = {float(a + b):.4f}")
print(f"a - b = {a - b} = {float(a - b):.4f}")
print(f"a * b = {a * b} = {float(a * b):.4f}")
print(f"a / b = {a / b} = {float(a / b):.4f}")
print()

# -----------------------------
# Menu
# -----------------------------

def menu() -> None:


while True:
print("\nFractions Mastery")
print("=================")
print("1) Fraction table (fixed denominator)")
print("2) Demo operations")
print("3) Quiz: operations (+ - * /)")
print("4) Quiz: simplify fractions")
print("5) Quiz: compare fractions")
print("6) Exit")

c = read_choice("Choose (1-6): ", ["1", "2", "3", "4", "5", "6"])

if c == "1":
d = read_int("Denominator (1-50): ", min_val=1, max_val=50)
upto = read_int("Max numerator (1-50): ", min_val=1, max_val=50)
clear()
show_fraction_table(d, upto_numerator=upto)

elif c == "2":
clear()
demo_operations()

elif c in ("3", "4", "5"):


difficulty = read_choice("Difficulty (easy/medium/hard/insane): ", ["easy", "medium",
"hard", "insane"])
n = read_int("Number of questions (1-100): ", min_val=1, max_val=100)
answer_style = read_choice("Answer format (simple/mixed): ", ["simple", "mixed"])

if c == "3":
s = run_quiz(mode="operations", difficulty=difficulty, n=n,
answer_style=answer_style)
elif c == "4":
s = run_quiz(mode="simplify", difficulty=difficulty, n=n, answer_style=answer_style)
else:
s = run_quiz(mode="compare", difficulty=difficulty, n=n,
answer_style=answer_style)
print_summary(s)
print_mistakes(s, answer_style)

if read_choice("Save session? (y/n): ", ["y", "n"]) == "y":


path = save_session(s)
print(f"Saved to: {[Link]()}")

👋")
else:
print("Bye
return

if __name__ == "__main__":
[Link]()
menu()

You might also like