Skip to content

Instantly share code, notes, and snippets.

@ifesdjeen
Created November 25, 2012 19:35
Show Gist options
  • Save ifesdjeen/4144929 to your computer and use it in GitHub Desktop.
Save ifesdjeen/4144929 to your computer and use it in GitHub Desktop.
Check wether it's easier or harder to read the code that way
;;
;; Leaving defaccumulator/defcompound details aside, when you see following lines, is it clear what's going on?
;; Or is it easier to read a straightforward loop, without dispatcher-based magic?
;;
;; As for me, advantage is that rules are completely decouple from each other. It's very easy to understand and
;; debug parts of the processing, rather than try to figure out how the complete algorithm works at once.
;; When reading algorithm description, you see clear definition of steps and rules, although when you see
;; the implementation it's all combined into one humongous function, that's doing everything, and dispatch rules
;; are wowed, which makes it even more complex.
;;
;; If we have a number, put it to "output"
(defaccumulator output
:filter (fn [n] (in? (cs/split "0123456789" #"") n))
:inititial-value [])
;; If we have an operator or opening bracket, put it to "stack"
(defaccumulator stack
:filter (fn [n] (in? (cs/split "+-/*(" #"") n))
:commuter #(cons %2 %1)
:inititial-value [])
;; If it's an operator, keep popping items from stack to output one by one, if precedence allows
(defcompound stack-output-precedence
:filter (fn [n] (in? (cs/split "+-/*" #"") n))
:commuter (fn [states current]
(let [[s o] (keep-popping-until @(:stack states) @(:output states)
(fn [stack output]
(and (not (nil? (first stack)))
(<= (precedence-of current) (precedence-of (first stack))))))]
(assoc states :stack (ref s) :output (ref o)))))
;; If it's a closing bracket, keep popping items from stack
(defcompound closing-bracket
:filter (fn [n] (in? (cs/split ")" #"") n))
:commuter (fn [states current]
(let [[s o] (keep-popping-until @(:stack states) @(:output states)
(fn [stack output] (not (= "(" (first stack)))))]
(assoc states :stack (ref s) :output (ref o)))))
;;
;; Helper functions
;;
(defn keep-popping-until
"Keeps popping things from vec a to vec b until condition is true"
[from-orig to-orig condition]
(loop [from from-orig
to to-orig]
(if (condition from to)
(recur
(next from)
(conj to (first from)))
[from to])))
(defn in?
"Checks if item belongs to seq"
[seq elm]
(some #(= elm %) seq))
(defn precedence-of [operator]
"Determines the precedence of a given mathematical operator."
(case operator
("+" "-") 2
("*" "/") 3
0))
;;
;; Alternative implementation, not my code, taken form: http://www.fatvat.co.uk/2009/06/shunting-yard-algorithm.html
;; all attributions should be addressed to original author.
;;
;; Kind of represents straightforward/monolith implementation, as opposed to rule/state-based.
;; State is not aggregated, but rather passed around, no straightforward decoupling of different accumulators/processing fns, as
;; for me
;;
;;; Shunting Yard Algorithm
(ns uk.co.fatvat.algorithms.shuntingyard
(:use clojure.contrib.test-is))
(defstruct operator :name :precedence :associativity)
(def operators
#{(struct operator + '1 'left)
(struct operator - '1 'left)
(struct operator * '2 'left)
(struct operator / '2 'left)
(struct operator '^ 3 'right)})
(defn lookup-operator
[symb]
(first (filter (fn [x] (= (:name x) symb)) operators)))
(defn operator?
[op]
(not (nil? (lookup-operator op))))
(defn op-compare
[op1 op2]
(let [operand1 (lookup-operator op1)
operand2 (lookup-operator op2)]
(or
(and (= 'left (:associativity operand1)) (<= (:precedence operand1) (:precedence operand2)))
(and (= 'right (:associativity operand1)) (<= (:precedence operand1) (:precedence operand2))))))
(defn- open-bracket? [op]
(= op \())
(defn- close-bracket? [op]
(= op \)))
(defn shunting-yard
([expr]
(shunting-yard expr []))
([expr stack]
(if (empty? expr)
(if-not (some (partial = \() stack)
stack
(assert false))
(let [token (first expr)
remainder (rest expr)]
(cond
(number? token) (lazy-seq
(cons token (shunting-yard remainder stack)))
(operator? token) (if (operator? (first stack))
(lazy-seq
(concat (take-while (partial op-compare token) stack)
(shunting-yard remainder
(cons token
(drop-while (partial op-compare token) stack)))))
(shunting-yard remainder (cons token stack)))
(open-bracket? token) (shunting-yard remainder (cons token stack))
(close-bracket? token) (let [ops (take-while (comp not open-bracket?) stack)
ret (drop-while (comp not open-bracket?) stack)]
(assert (= (first ret) \())
(lazy-seq
(concat ops (shunting-yard remainder (rest ret)))))
:else (assert false))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment