Skip to content

Instantly share code, notes, and snippets.

@alpox
Last active April 2, 2024 22:52
Show Gist options
  • Save alpox/8377fcdd42d8ca8caa5577a56e64bfc3 to your computer and use it in GitHub Desktop.
Save alpox/8377fcdd42d8ca8caa5577a56e64bfc3 to your computer and use it in GitHub Desktop.
FCC Challenge - Arithmetic expression calculator
(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