Created
November 25, 2012 19:35
-
-
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
This file contains 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
;; | |
;; 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)) |
This file contains 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
;; | |
;; 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