May 31, 2019
Algebraic Equation Solver
Solves one-step equations for a single variable. Works for the 4 basic operations, fractions and decimals.
0.3b = 0.9
(require '[clojure.string :as str])
(defn round [n] (if (float? n) (float (/ (Math/round (* n 100)) 100)) n))
(defn parse-term [s]
(if (float? (js/parseFloat s)) (js/parseFloat s) (symbol s)))
(defn term1 [s op] (parse-term (str/trim (first (str/split s op)))))
(defn term2 [s op] (parse-term (str/trim (last (str/split s op)))))
(defn num? [s] (or (float? s) (int? s)))
(defn div [s]
(let [left (first (str/split s #"/")) right (last (str/split s #"/"))]
(cond
(and (num? left) (num? right))
(/ (term1 s #"/") (term2 s #"/"))
(str/includes? left "-")
[(symbol (first (str/split left #"\-")))
(- (/ (parse-term (last (str/split left #"\-"))) (term2 s #"/")))]
(str/includes? right "-")
[(symbol (last (str/split right #"\-")))
(/ (term1 s #"/") (parse-term (first (str/split right #"\-"))))]
(str/includes? right "+")
[(/ (parse-term left) (parse-term (first (str/split right #"\+"))))
(symbol (last (str/split right #"\+")))]
(str/includes? left "+")
[(symbol (first (str/split left #"\+")))
(/ (parse-term (last (str/split left #"\+"))) (term2 s #"/"))]
:else {:numer left :denom right})))
(defn parse-exp [s]
(cond
(re-matches #"-*(\d+\.?\d*|\d*\.?\d+)[a-zA-Z]" s)
{:variable (re-find #"[a-zA-Z]" s)
:multiplier (parse-term (first (re-find #"(\d+\.?\d*|\d*\.?\d+)" s)))}
(str/includes? s "*") (* (term1 s #"\*") (term2 s #"\*"))
(str/includes? s "/") (div s)
(str/includes? s "+") [(term1 s #"\+") (term2 s #"\+")]
(str/includes? s "-")
(if (symbol? (term2 s #"\-"))
[(term1 s #"\-") (symbol (str "-" (term2 s #"\-")))]
[(term1 s #"\-")
(if (number? (term2 s #"\-"))
(- (term2 s #"\-")) (term2 s #"\-"))])
:else (parse-term (str/trim s))))
(defn parse-eq [s]
(let [left (str/trim (first (str/split s #"=")))
right (str/trim (last (str/split s #"=")))]
{:left (parse-exp left) :right (parse-exp right)}))
(defn solve [s]
(let [left (:left (parse-eq s)) right (:right (parse-eq s))]
(cond
(:numer left)
(str (:numer left) " = " (round (* (parse-term (:denom left)) right)))
(:numer right)
(str (:numer right) " = " (round (* (parse-term (:denom right)) left)))
(:multiplier left)
(str (:variable left) " = " (round (/ right (:multiplier left))))
(:multiplier right)
(str (:variable right) " = " (round (/ left (:multiplier right))))
(number? right)
(if (number? (last left))
(str (first left) " = " (round (- right (last left))))
(if (= "-" (str (first (str (last left)))))
(str (str/replace (last left) "-" "") " = "
(round (- (- right (first left)))))
(str (last left) " = " (round (- right (first left))))))
(number? left)
(if (number? (last right))
(str (first right) " = " (round (- left (last right))))
(if (= "-" (str (first (str (last right)))))
(str (str/replace (last right) "-" "") " = "
(round (- (- left (first right)))))
(str (last right) " = " (round (- left (first right)))))))))
(solve "0.3b=0.9")
Tested using these problems on Khan Academy. Next will be to make it handle two steps.
Two-step equations
We'll parse the expression into a list of terms, and map each variable to its appropriate multiplier. Then it can be solved in 2 steps.
-11b + 7 = 40
(defn terms [s]
(re-seq #"[\+-]?[\d./*a-zA-Z]+" s))
(defn variables [s]
(if (re-matches #"[\+-]?[\d./*]*[a-zA-Z]+" s)
{:variable (re-find #"[a-zA-Z]+" s)
:multiplier (if (re-find #"[\+-]*[\d./*]+" s)
(re-find #"[\-]*[\d./]+" s)
(if (re-find #"-" s) -1 1))} s))
(defn equation [s]
(let [left (str/trim (first (str/split s #"=")))
right (str/trim (last (str/split s #"=")))]
{:left (map variables (terms left))
:right (map variables (terms right))}))
(defn add-sub [s]
{:left (first (filter map? (:left (equation s))))
:right (- (js/parseFloat (first (:right (equation s))))
(js/parseFloat (first (filter string? (:left (equation s))))))})
(add-sub "-11b+7=40")
(defn solve [eq]
(str (:variable (:left eq)) " = "
(/ (:right eq)
(:multiplier (:left eq)))))
(solve (add-sub "-11b+7=40"))
Now we'll do parentheses. Expressions within expressions.
2(t + 1) = 10
{:left {:multiplier 2
:expr ["t" 1]}
:right 10}