Reputation:
I am trying to convert logical functions in clojure. I want the user to be able to type in (convert '(and x y z)
to produce (nor (nor x) (nor y) (nor z)
. So I am creating a list with first element nor
, and then trying to make the rest of the elements lists that are created when going through a for
loop. However the for
loop just combines all the lists, and keeps the nor
outside of it. I also want to know how to skip the first element in the list but that's not my priority right now. I'm kinda new to clojure and can't figure out how to just return all of the lists to be put into the bigger list. The not
and or
function aren't related to the problem.
(defn lookup
"Look up a value, i, in map m and returns the result if it exists.
Otherwise returns i."
[i m]
(get m i i))
(defn makelist
[l]
(for[i[l]] (list 'nor i)))
(defn convert
[l]
(let [p1 (first l)]
(cond
(= p1 'not) (map (fn [i] (lookup i '{not nor})) l)
(= p1 'or) (list 'nor (map(fn [i] (lookup i '{or nor})) l))
(= p1 'and) (list 'nor (makelist l))
:else (print "error"))))
The output I get is (nor ((nor (and x y z))))
. The output I want is (nor (nor and) (nor x) (nor y) (nor z)
. I don't want the (nor and)
either but until I can figure out how to skip the first element I just want to be able to separate the lists out.
Upvotes: 0
Views: 847
Reputation: 17859
just for fun: i would mentally expand and generalize your task to rewriting data structures according to some rules, so you could declare (possibly recursive) rewrite rules to transform any input to any desired output in general. (and to practice clojure)
let's start with simple conversion function:
(defn convert [rules data]
(if-let [res (some (fn [[condition rewrite]]
(when (condition data) (rewrite data)))
rules)]
res
data))
it finds first rule that suits your input (if any) and applies it's transformation function:
(def my-rules [[sequential? (fn [data] (map #(convert my-rules %) data))]
[number? inc]
[keyword? (comp clojure.string/upper-case name)]])
#'user/my-rules
user> (convert my-rules [:hello :guys "i am" 30 [:congratulate :me]])
;;=> ("HELLO" "GUYS" "i am" 31 ("CONGRATULATE" "ME"))
with this approach, your rules would look something like this:
(def rules
[[(every-pred coll? (comp #{'not} first)) (fn [data] (map (partial convert [[#{'not} (constantly 'nor)]]) data))]
[(every-pred coll? (comp #{'or} first)) (fn [data] (map (partial convert [[#{'or} (constantly 'nor)]]) data))]
[(every-pred coll? (comp #{'and} first)) (fn [[_ & t]] (cons 'nor (map #(list 'nor %) t)))]])
#'user/rules
user> (convert rules '(and x y z))
;;=> (nor (nor x) (nor y) (nor z))
ok it works, but looks rather ugly. Still we can elimnate some repetitions introducing couple of basic functions for checkers and transformers:
(defn first-is
"returns a function checking that the input is collection and it's head equals to value"
[value]
(every-pred coll? (comp #{value} first)))
transforming your rules to:
(def rules
[[(first-is 'not) (fn [data] (map (partial convert [[#{'not} (constantly 'nor)]]) data))]
[(first-is 'or) (fn [data] (map (partial convert [[#{'or} (constantly 'nor)]]) data))]
[(first-is 'and) (fn [[_ & t]] (cons 'nor (map #(list 'nor %) t)))]])
#'user/rules
user> (convert rules '(and x y z))
;;=> (nor (nor x) (nor y) (nor z))
and then introducing replacing rewrite rule:
(defn replacing
([new] [(constantly true) (constantly new)])
([old new] [#{old} (constantly new)]))
leading us to
(def rules
[[(first-is 'not) (fn [data] (map (partial convert [(replacing 'not 'nor)]) data))]
[(first-is 'or) (fn [data] (map (partial convert [(replacing 'or 'nor)]) data))]
[(first-is 'and) (fn [[_ & t]] (cons 'nor (map #(list 'nor %) t)))]])
now we can see that there is a demand on a function, transforming every item in collection. let's introduce it:
(defn convert-each [rules]
(fn [data] (map #(convert rules %) data)))
(def rules
[[(first-is 'not) (convert-each [(replacing 'not 'nor)])]
[(first-is 'or) (convert-each [(replacing 'or 'nor)])]
[(first-is 'and) (fn [[_ & t]] (cons 'nor (map #(list 'nor %) t)))]])
user> (convert rules '(or x y z))
;;=> (nor x y z)
user> (convert rules '(and x y z))
;;=> (nor (nor x) (nor y) (nor z))
now it is much better, but the last clause is still kind of ugly. I can think of introducing the function that transforms head and tail with separate rules and then conses transformed head and tail:
(defn convert-cons [head-rules tail-conversion]
(fn [[h & t]] (cons (convert head-rules h) (tail-conversion t))))
(defn transforming [transformer]
[(constantly true) transformer])
(def rules
[[(first-is 'not) (convert-each [(replacing 'not 'nor)])]
[(first-is 'or) (convert-each [(replacing 'or 'nor)])]
[(first-is 'and) (convert-cons [(replacing 'nor)]
(convert-each [(transforming #(list 'nor %))]))]])
user> (convert rules '(and x y z))
;;=> (nor (nor x) (nor y) (nor z))
Upvotes: 0
Reputation: 6666
There are two problems that I can see:
makelist
has (for [i [l]] ...)
so it only produces a single item where i
is bound to the whole of the incoming list l
-- what you want here is (for [i l] ...)
so that each element of l
is processed,convert
's clause for and
creates a list with two elements: nor
and the result of (makelist l)
-- what you want here is (cons 'nor (makelist l))
so that you get a list with nor
as the first element and then all of the elements of the result of calling makelist
.I haven't checked the other two parts of convert
to see whether you have similar errors, but with the two changes above (convert '(and x y z))
will produce (nor (nor and) (nor x) (nor y) (nor z))
Upvotes: 2