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}
Tags: KLIPSE mathematics Clojure