Reputation: 5205
I am working on the solution for this koan, which states:
Write a function which replicates each element of a sequence a variable number of times.
To do this, I want to:
With that in mind, I wrote the following method:
(fn dupSeqX [aseq x]
(fn dupx [v x]
(if (= x 1) (list v) (concat v (dupx v (- x 1))))
)
(reverse (reduce #(concat %1 (dupx %2 x)) '() aseq)))
When running this code, I get the following error:
java.security.PrivilegedActionException: java.lang.Exception: Unable to resolve symbol: dupx in this context (NO_SOURCE_FILE:0)
How do I go about creating a local method that will allow me to finish this koan?
Is there a "clojure-esque" way of doing this that I'm not aware of?
Upvotes: 2
Views: 203
Reputation: 5881
First of all, we don't talk about methods: we talk about functions. There is something in clojure that you could call a method but it's different from a function. If you stop using the OO lingo you will lose the OO style of thinking too.
What you are trying to do is possible. You basiclly want to create a new function with the name dupx
in the dupseqx
function. What you are doing right now is creating a function and then throwing it away (you don't do anything with the return value and only the last form in a function gets returned). Since a function is just like any other value, you can use the same mecanism like with any other value: create a local "variable". What's the mechanism for this? It's local binding and it works like this (The name in the fn is just so that you can call it from itself; it doesn't need to be the same as the let
-bound name):
(let [dupx (fn dupx [v x]
(if (= x 1)
(list v)
(cons v (dupx v (dec x)))))]
(dupx 5 3))
Notice that I corrected some other things.
A shorter form of this (fixing the double name ugliness):
(letfn [(dupx [v x] (if (= x 1)
(list v)
(cons v (dupx v (dec x)))))]
(dupx 5 3))
Ok in everything between "(let [...]" and the matching ")" we now have a dupx
function.
So now the rest of your code works:
(fn dupSeqX [aseq x]
(letfn [(dupx [v x] (if (= x 1) (list v) (cons v (dupx v (dec x)))))]
(reverse (reduce #(concat %1 (dupx %2 x)) '() aseq))))
This code can be made a little more idiomatic:
coll
instead of aseq
How did I go about writing this?
First the basic fn. coll
is the standard for naming function that expect sequences.
(fn [coll times] )
If you read this "each element of a sequence" your brain has to go MAP.
(fn [coll times]
(map (fn ....) coll))
"replicates each ... " is basically a description of what you have to put into the map function. We can use repeat
(your dubx function but with some extra goodies like that it's lazy).
(fn [coll times]
(map (fn [val] (repeat times val)) coll))
There is one problem left (from the koan). It wants one seq back, not a sequence in a sequence for each element. This means we have to concat the result together.
(fn [coll times]
(apply concat (map (fn [val] (repeat times val)) coll)))
You will often see the (apply concat (map ....))
pattern. There is a better function for that in the standard library, called mapcat
, and I'll make the inner function into the short syntax.
(fn [coll times]
(mapcat #(repeat times %) coll))
Hope that helps!
Upvotes: 8
Reputation: 91577
Clojure has a lot or really great functions to help you get in the habbit of "thinking in seqs". When you find your self writing something that iterates through a list think "can i map this?", and when you find your self doing anything else to a list check out this list of useful seq functions.
in this case that inner function happens to be build in so you get lucky :) often you will need to write it your self and store it in a let
though if you make it a proper function then you may find use for it elsewhere in your code.
heres a hint to get started
(flatten (map #(repeat 3 %) [1 2 3 4]))
(1 1 1 2 2 2 3 3 3 4 4 4)
ps: #(repeat 3 %)
is shorthand for (fn [n] (repeat 3 n))
Upvotes: 2