Reputation: 21261
I'm trying to go through the problem set at 4clojure.org. I'm on number 23 (reverse a string without using 'reverse' function). I'm getting this error:
Don't know how to create ISeq from: java.lang.Long
here's my code:
(fn rev [coll]
(if (= () coll)
nil
((cons(rev (rest coll))(first coll)))))
edited now to this:
(fn rev [coll]
(if (empty? coll)
coll
(concat(rev (rest coll))((list first coll)))))
presumably this is from trying to cons the head of the sequence to the end of the rest of the sequence.
What's the right way of doing this?
Upvotes: 2
Views: 803
Reputation: 3802
Like the OP, I've become frustrated trying to construct recursive solutions with when-let
and if
, next
and last
, etc, etc. So now I'm trying to understand @ntalbs answer and see if I can take this further, studying the Clojure documentation at http://clojuredocs.org/quickref/Clojure%20Core.
Part #1 -- Observations:
cons
is a function of lists, whereas conj
is a generic function of collections, and concat
is listed under 'Use(modification)' of sequences, but it returns a Lazy Sequence. So how do these things differ in practice?
Argument order matters: you can't conj
a value and a sequence, only a sequence and a value. For cons
it's the other way around. concat
appears to be more forgiving.
user=> (type (conj 1 '(2 3 4 5)))
ClassCastException java.lang.Long cannot be cast to clojure.lang.IPersistentCollection clojure.core/conj (core.clj:83)
user=> (type (cons '(2 3 4 5) 1))
IllegalArgumentException Don't know how to create ISeq from: java.lang.Long clojure.lang.RT.seqFrom (RT.java:505)
Three different return types: Cons, PersistentList or LazySeq
user=> (type (cons 1 '(2 3 4 5)))
clojure.lang.Cons
user=> (type (conj '(2 3 4 5) 1))
clojure.lang.PersistentList
user=> (type (concat 1 '(2 3 4 5)))
clojure.lang.LazySeq
Different behaviour for different types of collections:
user=> (cons 3 (sorted-set 5 7 2 7))
(3 2 5 7) ; type = Cons, 3 is just appended to the list,
user=> (conj (sorted-set 5 7 2 7) 3)
#{2 3 5 7} ; type = PersistentTreeSet, with 3 in the correct position.
user=> (concat 3 (sorted-set 5 7 2 7)) ; LazySeq can't be directly returned, so...(order doesn't matter)
IllegalArgumentException Don't know how to create ISeq from: java.lang.Long clojure.lang.RT.seqFrom (RT.java:505)
To my mind, conj
has the most straightforward behaviour, so I will use this by preference, unless I actually want a lazy sequence or specifically a list.
Part #2 -- @Ramy's 'too dense for me' Understanding @ntalbs's solution.
The description above suggests that conj
is the most appropriate method for adding things into a collection, which is exactly what @ntalbs's solution does. The usage for reduce
is at http://clojuredocs.org/clojure_core/clojure.core/reduce It's an effective way of applying a function while collecting together the values in a collection.
(reduce f val coll)
So, reduce
will apply f
for each member of the collection, starting with val
. The call (reduce conj () coll)
thus takes the collection and first applies (conj () (first coll))
. Then it applies (conj result (second coll))
, and (conj result (third coll))
, etcetera, where result
is the result of the previous step.
reduce
looks like a very powerful command.
Part #3 -- yet another solution
(fn rev [coll]
(into () coll))
From the documentation, into
appears to be syntactic sugar for (reduce conj to-coll from-coll)
. I'm not sure if this is elegant or just dense. It does work, though, with minimal key-strokes.
Upvotes: 1
Reputation: 26446
This error is because you are trying to cons
a seq
to an element rather than an element to a seq
. In other words, your arguments to cons
are in the wrong order. But, if you corrected the order, you'd just be piecing a list back together in the same order.
Using the same general idea you have, you could turn the second argument into a list by wrapping it in (list ...)
and then concat
the two lists together:
(fn rev [coll]
(if (empty? coll)
coll
(concat
(rev (rest coll))
(list (first coll)))))
You'll discover more concise solutions as you go along.
Upvotes: 3
Reputation: 29438
Second argument of cons
in clojure should be a sequence, however, (first coll)
is not a sequence but an element of a collection. Perhaps you pass the collection of numbers, so (first coll)
spit a number (long) and clojure cannot create ISeq
from the number.
user=> (doc cons)
-------------------------
clojure.core/cons
([x seq])
Returns a new seq where x is the first element and seq is
the rest.
You can implement reverse simply like the following:
(fn rev [coll]
(reduce conj () coll))
I check the above code and it passed all three test cases in 4clojure site.
Upvotes: 3