(use 'clojure.test)
(defn- match [prev-char next-char]
(condp = prev-char
\( (= next-char \))
\[ (= next-char \])
false))
; Need a better name for this function.
(defn- helper [s stack]
(if (empty? s)
(empty? stack)
(let [c (first s)
top (first stack)
stack (if (match top c) (next stack) (cons c stack))]
(helper (next s) stack))))
(defn balanced? [s] (helper s '()))
(doseq [arg *command-line-args*]
(println (balanced? arg)))
(deftest match-test
(is (match \( \)))
(is (match \[ \]))
(is (not (match \( \{))))
(deftest balanced-test
(is (balanced? "()"))
(is (balanced? "[]"))
(is (balanced? "([])"))
(is (balanced? "[()]"))
(is (balanced? "[]()"))
(is (balanced? "[][[([])]]"))
(is (not (balanced? "(")))
(is (not (balanced? ")")))
(is (not (balanced? "[")))
(is (not (balanced? "]")))
(is (not (balanced? "][")))
(is (not (balanced? ")(")))
(is (not (balanced? "( )")))
(is (not (balanced? "([)")))
(is (not (balanced? "[)]")))
(is (not (balanced? "([)]")))
(is (not (balanced? "({})")))
(is (not (balanced? "[())]"))))
(run-tests)
--
R. Mark Volkmann
Object Computing, Inc.
Great idea! Simply changing "helper" to "recur" works.
Yes, that's a nice improvement! Here's the new code without the tests
which didn't change. Now it is using a vector instead of a list. Too
bad there is no push function in core. I'm using conj.
(defn- match [prev-char next-char]
(condp = prev-char
\( (= next-char \))
\[ (= next-char \])
false))
(defn- balanced-helper [s stack]
(if (empty? s)
(empty? stack)
(let [c (first s)
top (peek stack)
stack (if (match top c) (pop stack) (conj stack c))]
(recur (next s) stack))))
(defn balanced? [s] (balanced-helper s []))
(doseq [arg *command-line-args*]
(println (balanced? arg)))
> As far as performance is on the table, I'm not sure whether of
> cons'ing over lists or conj'ing over vectors would have the better
> performance (maybe it's just comparable ! :-)
--
> Now it is using a vector instead of a list. Too
> bad there is no push function in core. I'm using conj.
conj is the correct thing to use for the push operation.
Here's my take on it based on yours. This bails early as soon as there
is a mismatch or an illegal character:
(ns challenge
(:use clojure.test))
(def lefts #{\( \[})
(def matches {\( \) \[ \]})
(defn balanced? [s]
(loop [s s stack ()]
(if (seq s)
(let [[c & s] s]
(if (lefts c)
(recur s (conj stack c))
(if (= (matches (peek stack)) c)
(recur s (pop stack))
false)))
(empty? stack))))
(deftest balanced-test
(are [text result]
(= (balanced? text) result)
"()" true
"[]" true
"([])" true
"[()]" true
"[]()" true
"[][[([])]]" true
"(" false
")" false
"[" false
"]" false
"][" false
")(" false
"( )" false
"([)" false
"[)]" false
"([)]" false
"({})" false
"[())]") false)
(defn main []
(run-tests 'challenge))
--Steve
Right, but the point Laurent was making is that using peek and pop are
good because they convey the fact that I'm using a vector as a stack.
For that reason it would be nice if there was a push function to go
along with peek and pop.
> Here's my take on it based on yours. This bails early as soon as there is a
> mismatch or an illegal character:
Yeah, I realized mine wasn't doing that, but was happy that it gave
the correct result anyway since the illegal characters get added to
the stack and are never popped off. Your implementation is certainly
more efficient though.
> (ns challenge
> (:use clojure.test))
>
> (def lefts #{\( \[})
> (def matches {\( \) \[ \]})
Cool use of a set and a map!
> (defn balanced? [s]
> (loop [s s stack ()]
> (if (seq s)
Interesting use of seq.
(empty? s) seems more clear to me though.
> (let [[c & s] s]
> (if (lefts c)
> (recur s (conj stack c))
> (if (= (matches (peek stack)) c)
> (recur s (pop stack))
> false)))
> (empty? stack))))
>
> (defn main []
> (run-tests 'challenge))
Very nice! Thanks for sharing!
ROFL. :-D
Brilliantly punny solution.
// Ben
(def push conj)
(def closing-counterpart { \( \), \[ \] })
(def opening-char? closing-counterpart)
(defn matching-pair? [left right] (= right (closing-counterpart left)))
(defn consume-one [stack c]
(if (or (opening-char? c)
(not (matching-pair? (peek stack) c)))
(push stack c)
(pop stack)))
(defn balanced? [s]
(empty? (reduce consume-one [] s)))
(defn main []
(apply print (map balanced? *command-line-args*)))
(when *command-line-args*
(main))
It's the second time I wish I had a version of reduce that allows to
quit the reduction early.
Indeed, not easy to be general, concise and elegant at the same time here ...
What about:
"(reduce-until pred reductor init-acc coll)" ?
pred would be a function of acc (the accumulator) and i (next item from coll),
be executed before reductor,
and return
* nil or false for continuing,
* a vector with a specific return value (which could be acc or something else)
* special meaning keyword :next for reduce-until to return the final
call of reductor on acc and i (this one could be got rid of, though ?)
e.g. :
my version would become
(defn consume-one [stack c]
(cond
(opening-char? c) (push stack c)
(matching-pair? (peek stack) c) (pop stack)))
(defn balanced? [s]
(empty? (reduce-until #(when-not (consume-one %1 %2) [[:error]])
consume-one [] s)))
or yours:
(defn balanced? [s]
(let [tr {\( \) \[ \]}]
(empty?
(reduce-until (fn [acc v] (when (nil? (get acc 0 true)) [acc]))
#(if (= (peek %1) %2)
(pop %1)
(conj %1 (tr %2))) [] s))))
But all in all I don't find this is much a readability or
composability improvement ..., or is this ?
Regards,
--
Laurent