Last active
April 2, 2024 22:52
-
-
Save alpox/8377fcdd42d8ca8caa5577a56e64bfc3 to your computer and use it in GitHub Desktop.
FCC Challenge - Arithmetic expression calculator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns c11 | |
(:require | |
[clojure.spec.alpha :as s] | |
[clojure.walk :as walk] | |
[clojure.math :as math] | |
[clojure.test :as t])) | |
(def ops #{\* \/ \+ \- \^}) | |
(defn coerce [token] | |
(cond | |
(re-find #"^-?\d" token) (read-string token) | |
(ops (first token)) (symbol token) | |
:else (first token))) | |
(defn tokenize [s] | |
(->> (re-seq #"(?<!\d\s*)-?\d+(\.\d+)?|\+|\*|\-|\/|\^|\(|\)" s) | |
(map first) | |
(map coerce))) | |
(s/def ::parens-expr | |
(s/cat :parens-open #{\(} | |
:parens-expr ::expr | |
:parens-close #{\)})) | |
(s/def ::exp-expr | |
(s/cat :left (s/alt :number number? | |
:expr ::parens-expr) | |
:op #{(symbol "^")} | |
:right (s/alt :number number? | |
:expr ::parens-expr))) | |
(s/def ::dash-expr | |
(s/cat :left (s/alt :number number? | |
:expr ::parens-expr | |
:expr ::dot-expr | |
:expr ::exp-expr) | |
:op #{'+ '-} | |
:right (s/alt :number number? | |
:expr ::parens-expr | |
:expr ::expr))) | |
(s/def ::dot-expr | |
(s/cat :left (s/alt :number number? | |
:expr ::parens-expr | |
:expr ::exp-expr) | |
:op #{'* '/} | |
:right (s/alt :number number? | |
:expr ::parens-expr | |
:expr ::dot-expr | |
:expr ::exp-expr))) | |
(s/def ::expr | |
(s/alt | |
:number number? | |
:expr ::parens-expr | |
:expr ::dot-expr | |
:expr ::dash-expr | |
:expr ::exp-expr)) | |
(defn evaluate [expr] | |
(walk/postwalk | |
(fn [v] | |
(cond | |
(vector? v) | |
(if (#{:number :expr} (first v)) | |
(second v) | |
v) | |
(not (map? v)) | |
v | |
(:op v) | |
(let [{:keys [left right op]} v | |
op-map {'+ + | |
'- - | |
'/ / | |
'* * | |
(symbol "^") math/pow} | |
op-fn (op-map op)] | |
(op-fn left right)) | |
(:parens-expr v) | |
(:parens-expr v) | |
:else | |
(throw (ex-info "invalid expression" v)))) | |
expr)) | |
(defn calculator [expr] | |
(let [tokens (tokenize expr) | |
conformed-expr (s/conform ::expr tokens)] | |
(if (s/invalid? conformed-expr) | |
(throw (ex-info (str "invalid expression '" expr "':" | |
(s/explain ::expr tokens)) | |
{})) | |
(evaluate conformed-expr)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment