Bobbi Towers / Jun 12 2019

Algae

Now solves these two-step equations.

{:deps
 {org.clojure/clojure {:mvn/version "1.10.0"}
  org.clojure/core.match {:mvn/version "0.3.0"}
  instaparse {:mvn/version "1.4.10"}}}
deps.edn
Extensible Data Notation

Gonna refactor this based on what I've learned so far. The obvious problem: Leaving too much for the pattern matcher. We must tame the expressions before dealing with them whenever possible. Even the parser could be made simpler if we take the same approach of doing more pre-processing and only sending certain things on. For example, we won't bother parsing the entire equation, rather we'll split it first on the =, and carry it out based on this principle.

0.3s
Clojure
(ns algae
  (:require [instaparse.core :as insta]
            [clojure.core.match :refer [match]]
            [clojure.string :as str]))

(defn parse-num [s]
  (cond
    (str/includes? s "/") (clojure.edn/read-string s)
    (str/includes? s ".") (Float/parseFloat s)
    :else (Integer/parseInt s)))

(defn parse-var [& s]
  (if (number? (first s))
    {:var (last s) :factor (first s)}
    {:var (last s) :factor 1}))

(def parse-expr
  (insta/parser
   "<expr> = term | (expr '+' term) | (expr '-' term)
   <term> = factor | (term factor)
   <factor> = ('-'? number) | var | ratio | parens
   parens = <'('> expr <')'>
   number = ('-'? #'[0-9]+' #'[./]' #'[0-9]+') | '-'? #'[0-9]+'
   var = number? #'[A-Za-z]+'
   ratio = ('-'? var '/' '-'? number) | ('-'? number '/' '-'? var)"))

(defn eq-parse [s]
  (->> (parse-expr s)
    (insta/transform
     {:number (comp parse-num str)
      :var parse-var})))

(defn collect-nums [v]
  (loop [terms v nums [] collected []]
    (if (empty? terms)
      (conj collected (reduce + nums))
      (if (number? (first terms))
        (recur (rest terms) (conj nums (first terms)) collected)
        (recur (rest terms) nums (conj collected (first terms)))))))

(defn negate [x]
  (cond
    (number? x) (- x)
    (map? x) {:var (:var x) :factor (- (:factor x))}
    :else "error"))

(defn simp-nums [s]
  (loop [terms (mapcat eq-parse (str/split s #"\+")) coll []]
    (if (empty? terms)
      (collect-nums coll)
      (if (= "-" (first terms))
        (recur (drop 2 terms) (conj coll (negate (second terms))))
        (cond
          (number? (first terms))
          (recur (rest terms) (conj coll (first terms)))
          (map? (first terms))
          (recur (rest terms)
                 (conj coll {:var (:var (first terms))
                             :factor (:factor (first terms))})))))))

(defn simp-var [v]
   {:var (first v)
    :factor (reduce + (map :factor (first (rest v))))})

(defn simplify [s]
  (into [(first (filter number? (simp-nums s)))]
   (map simp-var (group-by #(:var %) (filter map? (simp-nums s))))))

(defn parse [s]
  (if (str/includes? s "=")
    {:left (simplify (first (str/split s #"=")))
     :right (simplify (last (str/split s #"=")))}
    (simplify (eq-parse s))))

(defn round [n]
  (if (float? n) (float (/ (Math/round (* n 100)) 100)) n))
(defn number [expr]
  (if (number? (first expr)) (first expr) (recur (rest expr))))
(defn variable [expr]
  (if (map? (first expr)) (:var (first expr)) (recur (rest expr))))
(defn factor [expr]
  (if (map? (first expr)) (:factor (first expr)) (recur (rest expr))))
(defn numer [expr]
  (if (map? (first expr)) (:numer (first expr)) (recur (rest expr))))
(defn denom [expr]
  (if (map? (first expr)) (:denom (first expr)) (recur (rest expr))))

(defn solve [s]
  (match (parse s)
         {:left [n1 {:var x :factor n2}] :right [n3]}
         (str x "=" (/ (- n3 n1) n2))
         {:left [n1] :right [n2 {:var x :factor n3}]}
         (str x "=" (/ (- n1 n2) n3))))

(solve "p-18=3")
"p=21"