From 680c59513ceb521129f56b63111e164cffc5c761 Mon Sep 17 00:00:00 2001 From: Accusedbold Date: Wed, 29 Apr 2026 06:27:05 -0400 Subject: [PATCH] a third through Step Generator --- __pycache__/algebraic_steps.cpython-313.pyc | Bin 0 -> 7283 bytes __pycache__/problem_generator.cpython-313.pyc | Bin 12222 -> 13194 bytes __pycache__/steps.cpython-313.pyc | Bin 0 -> 760 bytes __pycache__/steps_generator.cpython-313.pyc | Bin 0 -> 9240 bytes algebraic_steps.py | 197 ++++++++++++++ main.py | 35 +-- problem_generator.py | 79 +++--- requirements.txt | 3 +- steps_generator.py | 244 ++++++++++++++++++ 9 files changed, 492 insertions(+), 66 deletions(-) create mode 100644 __pycache__/algebraic_steps.cpython-313.pyc create mode 100644 __pycache__/steps.cpython-313.pyc create mode 100644 __pycache__/steps_generator.cpython-313.pyc create mode 100644 algebraic_steps.py create mode 100644 steps_generator.py diff --git a/__pycache__/algebraic_steps.cpython-313.pyc b/__pycache__/algebraic_steps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c77c17dd6c445c93f0727e6b0ea775b65ac30af6 GIT binary patch literal 7283 zcmdT}No*U}8J>m9aFfJM)M8t-<&BakyYbRSUQ)wL9m!N%)|!+^(E-Vjsg_*ssI1TAt{@- z)zm#DInAH8?PlY^N$euu#Sn*R1L_p*Kn2kO)FnEBx8r&t`fnjG-v;aR>9CU7W;;j6 zUe3(hRA)1?t9z8oJXEXYOX(Mx4<(l2JYw$aC)2S`YOy;s0vB8%RFAx|>(@D%_3JUbW zpGw-W+L@XoL{W9qmQ|*7(@rweXLN#aHC(nj+ zF_^N)XB@fL(r)~q9A0;!e&Vk4md2-b5+!H z6!k=anF(Tn!sZz9K=u(2@Fp#gI~O>VPCo`~3cYjdp&^4vYzk=-z!8H$u6alVUZx$7 zjzasA4r~Nb%8)K3h*#2$WE~LGhY+o!w*`xJ1Z%iJ3%2kk8?aA{oK7e4^ie!&1kd$) zc(h*wc~OA87g}EoZ7~w%a8EJ3%@{>sCQ6aMLVN$!T{rZbqoo~(3xh`rp`#W?x81N^ zm9Bdhe_p5oGx$;iLP->s_bovvN?t@LzQ1vVl1;F26rrRQb0`goA4v^(q!)_K(Gt|E z0=p41F<{?`UA|e!pjg{-ed21O5FBbI)SdyEt!qlC)rA#^H#oGGI8t2=E$3IXxF_&% z(n9)?D^soAylE7juvhd$|c0rv3W9 ztMNkcdlpW=t)dM~QYzZ;y@o{Fxk#d7t}ir2VQ;myYRo@J4?hEL!KzoV&*avcvTCpGH5bYStt1p@MMtrJs>YmAa>TOcnE%H8=_OQAu8 z^1A#_O5vTW(dv6|=^r2a<6Ae?LJ*x8>QZle0@Y2$trx;QYbR7BImXbbeJt&%mQ?{l zS}sWlGRJqJ&X?R{HIZBIVsX=O)}JKTn6KVu zDkaJ!z%|0TCW02D}FyeQASz@k?#63IOFQ_r?GGeUbc<_bEHGx3)6@$^W%? zCh#?PCQ#d%;6%8#U#(Ux1QneP^}%{QRk{KVB)UN9x@pb>IWOiU7J@6j(dJ4QWo(1y z4OoKgMCNBo^prwos*1P$aBgNYqbgJ-XR#52jnIf5E=Z()=HC4Ya>-(T{_jp2KNkTLSPFLHq`w?{ru-&ITP zTQ7Pp*vmq2N!U;nHstliledL!P%a6bd8sID`c&wA=wwFjH2JU!wJhU?%{a-nla7VtS6xwDaU?suzOM!SX z5HAIG7$f(BVdKQ-zK$nX5?T^^ibBt#ZBf20Y%dF;OQ%XgS6(R!y`KtQxRy>>OQh5I zVL8&f6p0lhu{)9e6>I5U@@^`6H!beDvj2{EC$41x*3#aY?_7jnyM+tyv}Oe4o2fZ zMnlLw=NH+UjsUw^VqfDD5V=JSpm8d@7dAxWD)}iU#*Kb~-w@--FuY8gOr|v`j9`Js z0!~>Cs*)3sCkY^bgGcKH0$rkGC*Ld!T!SF$wFK#21KLvOIs`3_T?r&kgZQ818m#!X;xs{=$!urllaL| literal 0 HcmV?d00001 diff --git a/__pycache__/problem_generator.cpython-313.pyc b/__pycache__/problem_generator.cpython-313.pyc index 83b0ce7cc12eed143ef74c746065b448c6e3be60..48805c73f9201b3465421154db81e02fb54e98e5 100644 GIT binary patch delta 4350 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 delta 3320 zcmai0Z)jWB6@OQHdU{X)|Jt!F+mdZ$%XZ?lN!Bz=oF#VtG+9luSFh_LB4p_~RwGN^ zeRAd)f;=|1v>D9w5|$5T^$Kkr+6{xZkuexc23tOCpy3bm-twWNg`zJdjiHPTmYs8- zBgw{L58a>6d-uL~f9H44IrqMK>G!GfhTH8B;L4Og&pm1pgirA1>E+rqHhu`kO+gh@ za#o=DZ#hd|utWvb+A2_sDgs(n8=$C4fHu_*D5)}_T~z>O)d8rePC$q10(7cwz+so_ zfj_tE1@x#sKyT8Lv?j%*Eh#1KNja$`9Z6@>m2?lesP7U<_^ZfjEI|y?&kD{a^2@hk zr`dah-(vr?&aw8scJ^bjh1l5p;*dugw|fNDJ}!EMeb#Zd(AmcRD>`G!SoO;%Po7kL z@F8jes5$`*GG|EnTtQ1yHfS3;s1%BHI-SqWYYEvPIfJAPYr3EtWWpdBD&rfXHa9~J zqU(4U-MrXQ%RsmHl2*`Yx}>GdFVc1xVTY#Q1y~S1l|AgQwr{edaX%ZDVmAL`&kZlj zN_+M{r#Hcfca#xX{zwA2+w2^1%lGWgk${|mp4z2=WremeVPPcp0sBSo475SkA&>I^ zpUJ~hli5l*yTsEr}Qj_USsYoemLcIV92Mwd$ zjIav<8B&NASXlO~pY7mXm2&*a)UoSkwfx3@yx7ioA0d^q}VHX`Qu%aW( zu0<90J4a8?Yq#dt!u^%d!1DeTvaGMfe)ZCw*Vp#HRM~gte$V~+`}vCJ0<${%hF;VY z@V$4OqwVrtuQJ*t-;GFs_w34OuY4~gAs$sm2jxGyB*4{zbDflHI?zsdnMMH;R&(ir zmrw<9o-_#K%I5SE%}vafa>W9T;VWGTbxG_El5jP+E~X?nvIjuN_2!C*T#rAv+kAuY zXb<-N2>`-?>#@soIgnU7v(`FPY1z9XuAE;PsRR$Qo1P;W;Hw*+E0*ycJZK=Sl)hU` z>A9??Q(T4yqRLK04U#p828(yc-%BFwWB)GSK6q^yiMh8d)NWX$IJ+Jg-_^M!uQeqq z;r`|Bk8*dEwLQlxyMg8?+X$$f=2URXvXjttnkR1)L)NaO^Rti`h9jT5tffjCoo*m~ zTcSKG{pdviAqZetk7cto1Xe=c$saQohcF+&sh_tt^+)inU_%ntraA;Y=b-7fZH}HC z=w+^E)!(@!vSKA1U&<`UmV=cbNYHG0_(}7*|Kp*dd}lVDg}l#Y9*dAud-+Nvc6LhE zbJH{V+~n0p&kPHF4%+huDbO~c$upIUui5gl)D+IxmlZ>9*41M9RC;qm`Ec1CDN>jg`kN4p10o+GBxJ@ZbtG{RaR*(BWh6 zwWr)$=k|59^UylqF-FR@%;l1tqzB+d!!@tbVrqsKv$L5}ozycFx4t}^H^sJ@eCw%O z9+t-*7NZgdo_A`8}ARJ2Ue@mi;Z;$i&hmkRC)pd zr!(v{oy}#^`FhSigSSYF#ga*UQ|LNzUX|$NgUngbG_zNtIeB0oRaMcBzsU@WAO^6mJ~*yR-do&h}f`rI9sfUq$TOzUEliua+}V^(OM#G@XKPeF{y-zy!|T@K3J2dF}1j*1YB3h136V25%gyh+&$A z-h`jeW!Utq(=%83p_KN*bBgB@ie~_d`y<~z=qa?Y;!!Li6f=`zbW&VAQ!bpEP6FH( z`1j4#mJd%uJInk$djO*R)(ch_f93}( z?gEIo0Xq1Q5Lkn}mypCG$wHbQIs{v2U2I(!Th_& diff --git a/__pycache__/steps.cpython-313.pyc b/__pycache__/steps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..993e8fd038fa50e863348a75b6acff3478313809 GIT binary patch literal 760 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 Il&-^n0Rpv>=l}o! literal 0 HcmV?d00001 diff --git a/algebraic_steps.py b/algebraic_steps.py new file mode 100644 index 0000000..3ce887f --- /dev/null +++ b/algebraic_steps.py @@ -0,0 +1,197 @@ +#All Rights Reserved John Salguero +#Steps that are generated + +from sympy import * +from sympy.parsing.sympy_parser import ( + parse_expr, + standard_transformations, + implicit_multiplication_application +) +transformations = standard_transformations + (implicit_multiplication_application,) + +def move_all_to_one_side(equation): + step = {} + + current = equation + step["before"] = current + left, right = current.split("=") + x = symbols('x') + + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right, transformations=transformations, evaluate=False) + new_expr = left_expr - right_expr + step["after"] = f"{sstr(new_expr)} = 0" + + step["step"] = f"Subtract both sides by {sstr(right_expr)}" + step["rule"] = "Subtraction Property of Equality" + return step + +def add_both_sides(equation, value): + step = {} + + current = equation + step["before"] = current + left, right = current.split("=") + x = symbols('x') + + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right, transformations=transformations, evaluate=False) + + new_left_expr = left_expr + value + new_right_expr = right_expr + value + step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}" + + step["step"] = f"Add both sides by {sstr(value)}" + step["rule"] = "Addition Property of Equality" + return step + +def subtract_both_sides(equation, value): + step = {} + + current = equation + step["before"] = current + left, right = current.split("=") + x = symbols('x') + + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right, transformations=transformations, evaluate=False) + + new_left_expr = left_expr - value + new_right_expr = right_expr - value + step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}" + + step["step"] = f"Subtract both sides by {sstr(value)}" + step["rule"] = "Subtraction Property of Equality" + return step + +def divide_both_sides(equation, value): + step = {} + + current = equation + step["before"] = current + left, right = current.split("=") + x = symbols('x') + + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right, transformations=transformations, evaluate=False) + + new_left_expr = clean(left_expr / value) + new_right_expr = clean(right_expr / value) + step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}" + + step["step"] = f"Divide both sides by {sstr(value)}" + step["rule"] = "Division Property of Equality" + return step + +def multiply_both_sides(equation, value): + step = {} + + current = equation + step["before"] = current + left, right = current.split("=") + x = symbols('x') + + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right, transformations=transformations, evaluate=False) + + new_left_expr = left_expr * value + new_right_expr = right_expr * value + step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}" + + step["step"] = f"Multiply both sides by {sstr(value)}" + step["rule"] = "Multiplication Property of Equality" + return step + +def factor_collect(equation): + step = {} + + current = equation + step["before"] = current + left, right = current.split("=") + x = symbols('x') + + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right, transformations=transformations, evaluate=False) + + new_left_expr = factor(left_expr) + new_right_expr = factor(right_expr) + step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}" + + step["step"] = f"Collect the factors" + step["rule"] = "Factoring by grouping" + return step + +def factor_form_collection(equation, factor): + step = {} + + current = equation + step["before"] = current + left, right = current.split("=") + x = symbols('x') + + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right, transformations=transformations, evaluate=False) + + new_left_expr = collect(left_expr, factor) + new_right_expr = collect(right_expr, factor) + step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}" + + step["step"] = f"Collect the factors using factor {sstr(factor)}" + step["rule"] = "Factor by grouping" + return step + +def combine_like_terms(equation): + step = {} + + current = equation + step["before"] = current + left, right = current.split("=") + x = symbols('x') + + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right, transformations=transformations, evaluate=False) + + ## Combine Left Terms + left_terms = left_expr.as_ordered_terms() + # group by base + left_groups = {} + for t in left_terms: + coeff, rest = t.as_coeff_Mul() + left_groups.setdefault(rest, 0) + left_groups[rest] += coeff + # rebuild manually + new_left_terms = [] + for base, coeff in left_groups.items(): + if coeff != 0: + new_left_terms.append(coeff * base) + new_left_expr = sum(new_left_terms) + + ## Comnine Right Terms + right_terms = right_expr.as_ordered_terms() + # group by base + right_groups = {} + for t in right_terms: + coeff, rest = t.as_coeff_Mul() + right_groups.setdefault(rest, 0) + right_groups[rest] += coeff + # rebuild manually + new_right_terms = [] + for base, coeff in right_groups.items(): + if coeff != 0: + new_right_terms.append(coeff * base) + new_right_expr = sum(new_right_terms) + + step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}" + step["step"] = "Collect Like Terms" + step["rule"] = "Combine the like terms" + return step + +def clean(expr): + + # remove explicit 1 multipliers + expr = expr.replace( + lambda e: isinstance(e, Mul), + lambda e: Mul(*[arg for arg in e.args if arg != 1]) + ) + + return expr \ No newline at end of file diff --git a/main.py b/main.py index 07d947f..b2d253d 100644 --- a/main.py +++ b/main.py @@ -1,44 +1,19 @@ #All Rights Reserved John Salguero #Starts the backend to my Youtube stream -from problem_generator import generate_problem, normalize -from mathhook import parse, solve -from sympy import sympify +from problem_generator import generate_problem +from steps_generator import generate_steps #define the entry point to the programs def main(): problem = generate_problem() - + steps = generate_steps(problem); print("Generated Problem:") print(problem) - print("Solve:") - equation = apply_strategy(problem) - expr = parse(equation) - result = solve(expr, "x") - print(result) - -def square_both_sides(problem): - lhs, rhs = problem["problem"].split("=") - - lhs = sympify(lhs.strip()) - rhs = sympify(rhs.strip()) - - lhs = lhs ** 2 - rhs = rhs ** 2 - - return f"{normalize(lhs)} = {normalize(rhs)}" - -def factor_or_formula(problem) : - return problem["problem"] - -def apply_strategy(problem): - if problem["type"] == "radical": - return square_both_sides(problem) - if problem["type"] == "quadratics": - return factor_or_formula(problem) - return problem["problem"] + print("Steps:") + print(steps) #Starts the program if __name__ == "__main__": diff --git a/problem_generator.py b/problem_generator.py index dd38fbf..1e2af0a 100644 --- a/problem_generator.py +++ b/problem_generator.py @@ -4,17 +4,26 @@ import random from sympy import * +TEMPLATES = {} + +def register_problem_generator(problem_type): + def decorator(func): + TEMPLATES[problem_type] = func + return func + return decorator + +@register_problem_generator("linear") def generate_linear(): #ax + b = c a = random.choice([i for i in range(-10, 16) if i != 0]) ans = random.choice([i for i in range(-10, 11)]) - b = random.choice([i for i in range(-10, 11)]) + b = random.choice([i for i in range(-10, 11) if a != 1 or i != 0]) c = a * ans + b x = symbols('x') expr = a * x + b # expanded = n - s = normalize(expr) + s = sstr(expr) return { "type": "linear", @@ -22,20 +31,21 @@ def generate_linear(): "solution": ans } +@register_problem_generator("hidden_factor") def generate_hidden_factor(): #a(x + b) + c(x + b) = d ans = random.choice([i for i in range(-10, 16)]) b = random.choice([i for i in range(-5, 6) if i != 0]) a = random.choice([i for i in range(-5, 6) if i != 0]) - c = random.choice([i for i in range(-5, 6) if i != 0]) + c = random.choice([i for i in range(-5, 6) if i != 0 and i != -a]) x = symbols('x') inner_expr = x + b inner_value = ans + b right_side = a * inner_value + c * inner_value - problem = f"{a}({normalize(inner_expr)}) + {c}({normalize(inner_expr)}) = {right_side}" + problem = f"{a}({sstr(inner_expr)}) + {c}({sstr(inner_expr)}) = {right_side}" return { "type": "hidden_factor", @@ -43,6 +53,7 @@ def generate_hidden_factor(): "solution": ans } +@register_problem_generator("distribution") def generate_distribution (): #a(x + b) = c ans = random.choice([i for i in range(-10, 16)]) @@ -55,10 +66,11 @@ def generate_distribution (): return { "type": "distribution", - "problem": f"{a}({normalize(inner_expr)}) = {c}", + "problem": f"{a}({sstr(inner_expr)}) = {c}", "solution": ans } +@register_problem_generator("two_sides") def generate_two_sides (): #ax + b = dx + e : a != d ans = random.choice([i for i in range(-10, 16)]) @@ -73,10 +85,11 @@ def generate_two_sides (): return { "type": "two_sides", - "problem": f"{normalize(left_exp)} = {normalize(right_exp)}", + "problem": f"{sstr(left_exp)} = {sstr(right_exp)}", "solution": ans } +@register_problem_generator("like_terms") def generate_like_terms (): #ax + bx + c = d ans = random.choice([i for i in range(-10, 16)]) @@ -90,10 +103,11 @@ def generate_like_terms (): return { "type": "like_terms", - "problem": f"{normalize(expr)} = {d}", + "problem": f"{sstr(expr)} = {d}", "solution": ans } +@register_problem_generator("quadratic") def generate_quadratic (): #ax² + bx + c = 0 r1 = random.choice([i for i in range(-10, 16)]) @@ -103,7 +117,7 @@ def generate_quadratic (): x = symbols('x') expr = n *(x - r1) * (x - r2) - expr = simplify(expr) + expr = expand(expr) if r1 == r2: solution = r1 else: @@ -111,10 +125,11 @@ def generate_quadratic (): return { "type": "quadratic", - "problem": f"{normalize(expr)} = 0", + "problem": f"{sstr(expr)} = 0", "solution": solution } +@register_problem_generator("difference_squares") def generate_difference_squares (): #x² - a² = 0 ans = random.choice([i for i in range(0, 13)]) @@ -130,10 +145,11 @@ def generate_difference_squares (): return { "type": "difference_squares", - "problem": f"{normalize(expr)} = 0", + "problem": f"{sstr(expr)} = 0", "solution": solution } +@register_problem_generator("zero_product") def generate_zero_product (): #(x + a)(x + b) = 0 a = random.choice([i for i in range(-5, 6)]) @@ -144,10 +160,11 @@ def generate_zero_product (): return { "type": "zero_product", - "problem": f"{normalize(expr)} = 0", + "problem": f"{sstr(expr)} = 0", "solution": [-a, -b] } +@register_problem_generator("radical") def generate_radical (): #√(x + a) = b a = random.choice([i for i in range(-10, 16)]) @@ -159,10 +176,11 @@ def generate_radical (): return { "type": "radical", - "problem": f"{normalize(expr)} = {b}", + "problem": f"{sstr(expr)} = {b}", "solution": ans } +@register_problem_generator("fraction") def generate_fraction (): #(x/a) + b = c a = random.choice([i for i in range(-7, 8) if i != 0 and i != 1]) @@ -175,10 +193,11 @@ def generate_fraction (): return { "type": "fraction", - "problem": f"{normalize(expr)} = {c}", + "problem": f"{sstr(expr)} = {c}", "solution": ans } +@register_problem_generator("binomial") def generate_binomial (): #a(x + b) + c(x + d) = e ans = random.choice([i for i in range(-15, 16)]) @@ -197,12 +216,13 @@ def generate_binomial (): return { "type": "binomial", - "problem": f"{normalize(expr)} = {e}", + "problem": f"{sstr(expr)} = {e}", "solution": ans } +@register_problem_generator("tricky") def generate_tricky (): - #generate random numbers + #(x² - x - a) / (x + b) = c n = random.choice([i for i in range(-5, 6) if i != 0]) r1 = random.choice([i for i in range(-10, 16)]) r2 = random.choice([i for i in range(-10, 16) if i != r1]) @@ -214,7 +234,7 @@ def generate_tricky (): expanded = expanded - expr expanded = expanded / (x - r1) # expanded = n - s = normalize(expanded) + s = sstr(expanded) return { "type": "tricky", @@ -222,24 +242,15 @@ def generate_tricky (): "solution": r2 } -def normalize(expr): - return sstr(expr).replace("**", "^") def generate_problem(): - template = random.choice(TEMPLATES) - return template() + types = list(TEMPLATES.keys()) + #print(types) + # ['linear', 'hidden_factor', 'distribution', 'two_sides', 'like_terms', 'quadratic', 'difference_squares', 'zero_product', 'radical', 'fraction', 'binomial', 'tricky'] + weights = [0.80 , 0.90 , 0.70 , 1.00 , 0.9 , 1.0 , 0.80 , 0.70 , 0.70 , 1.00 , 1.00 , 0.65] -TEMPLATES = [ - generate_linear, - generate_hidden_factor, - generate_distribution, - generate_two_sides, - generate_like_terms, - generate_quadratic, - generate_difference_squares, - generate_zero_product, - generate_radical, - generate_fraction, - generate_binomial, - generate_tricky - ] \ No newline at end of file + problem_type = random.choices(types, weights=weights)[0] + template = TEMPLATES[problem_type] + return generate_like_terms() + #return template() + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7fafc8e..2425d3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -sympy -mathhook \ No newline at end of file +sympy \ No newline at end of file diff --git a/steps_generator.py b/steps_generator.py new file mode 100644 index 0000000..8472c2a --- /dev/null +++ b/steps_generator.py @@ -0,0 +1,244 @@ +#All Rights Reserved John Salguero +#Generates steps depending on the problem + +import algebraic_steps +from sympy import * +from sympy.parsing.sympy_parser import ( + parse_expr, + standard_transformations, + implicit_multiplication_application +) +transformations = standard_transformations + (implicit_multiplication_application,) + +STEP_GENERATORS = {} + +def register_steps_generator(problem_type): + def decorator(func): + STEP_GENERATORS[problem_type] = func + return func + return decorator + +@register_steps_generator("linear") +def generate_linear_steps(problem): + #ax + b = c + steps = [] + + x = symbols('x') + current = problem["problem"] + left, right = current.split("=") + expr = parse_expr(left) + a = expr.coeff(x) + b = expr.subs(x, 0) + + ## First Step + if b.is_zero == False: + if b.is_negative: + steps.append(algebraic_steps.add_both_sides(current, -b)) + elif b.is_positive: + steps.append(algebraic_steps.subtract_both_sides(current, b)) + current = steps[-1]["after"] + ## Second Step + if a != 1: + steps.append(algebraic_steps.divide_both_sides(current, a)) + + return steps + +@register_steps_generator("hidden_factor") +def generate_hidden_factor_steps(problem): + #a(x + b) + c(x + b) = d + steps = [] + + x = symbols('x') + current = problem["problem"] + left, right = current.split("=") + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right) + terms = left_expr.as_ordered_terms() + #factors = [term.as_ordered_factors() for term in terms] + #common = set(factors[0]) & set(factors[1]) + #base = list(common)[0] + base = terms[0].args[1] + + ## First Step + steps.append(algebraic_steps.factor_collect(current)) + current = steps[-1]["after"] + + ## Second Step + div = simplify(left_expr / base) + steps.append(algebraic_steps.divide_both_sides(current, div)) + current = steps[-1]["after"] + + ## Third Step + b = base.subs(x, 0) + if b.is_negative: + steps.append(algebraic_steps.add_both_sides(current, -b)) + elif b.is_positive: + steps.append(algebraic_steps.subtract_both_sides(current, b)) + + current = steps[-1]["after"] + left, right = current.split("=") + left_expr = parse_expr(left, transformations=transformations) + right_expr = parse_expr(right) + steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" + + return steps + + +@register_steps_generator("distribution") +def generate_distribution_steps (problem): + #a(x + b) = c + steps = [] + + x = symbols('x') + current = problem["problem"] + left, right = current.split("=") + left_expr = parse_expr(left, transformations=transformations, evaluate=False) + right_expr = parse_expr(right) + terms = left_expr.as_ordered_terms() + base = terms[0].args[1] + div = simplify(left_expr / base) + + ## First Step + steps.append(algebraic_steps.divide_both_sides(current, div)) + current = steps[-1]["after"] + + ## Second Step + b = base.subs(x, 0) + if b.is_negative: + steps.append(algebraic_steps.add_both_sides(current, -b)) + elif b.is_positive: + steps.append(algebraic_steps.subtract_both_sides(current, b)) + + current = steps[-1]["after"] + left, right = current.split("=") + left_expr = parse_expr(left, transformations=transformations) + right_expr = parse_expr(right) + steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" + + return steps + +@register_steps_generator("two_sides") +def generate_two_sides_steps (problem): + #ax + b = dx + e : a != d + steps = [] + + x = symbols('x') + current = problem["problem"] + left, right = current.split("=") + left_expr = parse_expr(left) + right_expr = parse_expr(right) + a = left_expr.coeff(x) + b = left_expr.subs(x, 0) + d = right_expr.coeff(x) + e = right_expr.subs(x, 0) + + ## First Step + if d.is_negative: + steps.append(algebraic_steps.add_both_sides(current, -d*x)) + elif d.is_positive: + steps.append(algebraic_steps.subtract_both_sides(current, d*x)) + current = steps[-1]["after"] + left, right = current.split("=") + left_expr = parse_expr(left) + right_expr = parse_expr(right) + steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" + current = steps[-1]["after"] + + ## Second Step + if b.is_negative: + steps.append(algebraic_steps.add_both_sides(current, -b)) + elif b.is_positive: + steps.append(algebraic_steps.subtract_both_sides(current, b)) + current = steps[-1]["after"] + left, right = current.split("=") + left_expr = parse_expr(left, transformations=transformations) + right_expr = parse_expr(right) + steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" + current = steps[-1]["after"] + + ## Third Step + new_left, new_right = current.split("=") + new_left_expr = parse_expr(left, transformations=transformations, evaluate=False) + new_right_expr = parse_expr(right, transformations=transformations, evaluate=False) + div = left_expr.coeff(x) + if div != 1 and div != -1: + steps.append(algebraic_steps.divide_both_sides(current, div)) + elif div == -1: + steps.append(algebraic_steps.multiply_both_sides(current, div)) + + return steps + +@register_steps_generator("like_terms") +def generate_like_terms_steps (problem): + #ax + bx + c = d + steps = [] + + current = problem["problem"] + + ## First Step + steps.append(algebraic_steps.combine_like_terms(current)) + current = steps[-1]["after"] + + ## Second Step + left, right = current.split("=") + left_expr = parse_expr(left) + b = left_expr.subs(x, 0) + + return steps + +@register_steps_generator("quadratic") +def generate_quadratic_steps (problem): + #ax² + bx + c = 0 + steps = [] + + return steps + +@register_steps_generator("difference_squares") +def generate_difference_squares_steps (problem): + #x² - a² = 0 + steps = [] + + return steps + +@register_steps_generator("zero_product") +def generate_zero_product_steps (problem): + #(x + a)(x + b) = 0 + steps = [] + + return steps + +@register_steps_generator("radical") +def generate_radical_steps (problem): + #√(x + a) = b + steps = [] + + return steps + +@register_steps_generator("fraction") +def generate_fraction_steps (problem): + #(x/a) + b = c + steps = [] + + return steps + +@register_steps_generator("binomial") +def generate_binomial_steps (problem): + #a(x + b) + c(x + d) = e + steps = [] + + return steps + +@register_steps_generator("tricky") +def generate_tricky_steps (problem): + #(x² - x - a) / (x + b) = c + steps = [] + + return steps + +def generate_steps(problem): + problem_type = problem["type"] + + if problem_type not in STEP_GENERATORS: + raise ValueError(f"No step generator for type: {problem_type}") + + return STEP_GENERATORS[problem["type"]](problem) \ No newline at end of file