Reputation: 4268
In the process of learning Clojure.
I have a function to draw a random card from the deck
(defn draw-random-card
[cards]
(let [card (rand-nth cards)
index (.indexOf cards card)]
{:card card :remaining-cards (concat (subvec cards 0 index)
(subvec cards (inc index)))}))
Running it:
(draw-random-card ["Ace" 2 3 4 5 6 7 8 9 10 "Jack" "Queen" "King"])
=> {:card 4, :remaining-cards ("Ace" 2 3 5 6 7 8 9 10 "Jack" "Queen" "King")}
I'd like to call it twice and get 2 cards out but the second time it calls it, it will pass the reduced deck from the first call.
IN the end I'd like to have the 2 cards and the reduced deck to use later.
I would have thought I could do something like:
(def full-deck ["Ace" 2 3 4 5 6 7 8 9 10 "Jack" "Queen" "King"])
(let [first-draw (draw-random-card full-deck)
first-card (:drawn-card first-draw)
second-draw (draw-random-card (:remaining-cards first-draw))
second-card (:drawn-card second-draw)
remaining-deck (:remaining-cards second-draw)]
(println "First card: " first-card)
(println "Second card: " second-card)
(println "Remaining deck:" remaining-deck))
However, I'm obviously doing something dumb here as I get the error:
Execution error (ClassCastException) at aceyducey.core/draw-random-card (form-init3789790823166246683.clj:5).
clojure.lang.LazySeq cannot be cast to clojure.lang.IPersistentVector
I think the problem is in the line
second-draw (draw-random-card (:remaining-cards first-draw))]
Because remaining-cards isn't a vector?
Which means
concat (subvec cards 0 index)
(subvec cards (inc index)))}))
Isn't returning a vector? Rather a lazy sequence ???
But at this point I'm lost.
Help!
Upvotes: 0
Views: 342
Reputation: 29976
@amalloy makes a good point: the Clojure built-in function shuffle
is probably what you want:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test) )
(def cards [:ace 2 3 4 5 6 7 8 9 10 :jack :queen :king] )
(dotest
(dotimes [i 3]
(spyx (shuffle cards))))
=>
Testing tst.demo.core
(shuffle cards) => [:king :jack 6 2 9 10 :ace 4 8 5 3 :queen 7]
(shuffle cards) => [2 :jack 7 9 :queen 8 5 3 4 :ace 10 :king 6]
(shuffle cards) => [7 :queen :jack 4 3 :king 6 :ace 2 10 5 8 9]
This and much more is available at the Clojure CheatSheet. Be sure to bookmark it and always keep a browser tab open with it.
Upvotes: 3
Reputation: 29976
concat
returns a lazy sequence. You can coerce it into a vector by using:
(vec (concat ...))
Here is the full code with a test:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn draw-random-card
[cards]
(let [card (rand-nth cards)
index (.indexOf cards card)]
{:drawn-card card :remaining-cards (vec (concat (subvec cards 0 index)
(subvec cards (inc index))))}))
(def full-deck ["Ace" 2 3 4 5 6 7 8 9 10 "Jack" "Queen" "King"])
(dotest
(let [first-draw (draw-random-card full-deck)
first-card (:drawn-card first-draw)
second-draw (draw-random-card (:remaining-cards first-draw))
second-card (:drawn-card second-draw)
remaining-deck (:remaining-cards second-draw)]
(println "First card: " first-card)
(println "Second card: " second-card)
(println "Remaining deck:" remaining-deck))
)
and result:
-------------------------------
Clojure 1.10.0 Java 12
-------------------------------
Testing tst.demo.core
First card: Queen
Second card: King
Remaining deck: [Ace 2 3 4 5 6 7 8 9 10 Jack]
To be specific, the problem was the call to subvec
in the 2nd iteration of your code. Here is an example:
(dotest
(let [vals (vec (range 10)) ; a vector
s1 (subvec vals 2 4) ; so `subvec` works
s2 (subvec vals 6) ; and again
lazies (concat s1 s2)] ; creates a lazy sez
(is= [2 3] (spyxx s1))
(is= [6 7 8 9] (spyxx s2))
(is= [2 3 6 7 8 9] (spyxx lazies))
(throws? (subvec lazies 0 2)))) ; ***** can't call `subvec` on a non-vector (lazy sequence here) *****
with result:
s1 => <#clojure.lang.APersistentVector$SubVector [2 3]>
s2 => <#clojure.lang.APersistentVector$SubVector [6 7 8 9]>
lazies => <#clojure.lang.LazySeq (2 3 6 7 8 9)>
so by coercing the output of concat
to a vector, the call to subvec
succeeds on the next time through the function.
So, in hindsight, a better solution would have been to coerce the input to a vector like so:
(let [cards (vec cards)
card (rand-nth cards)
index (.indexOf cards card)]
{:drawn-card card
:remaining-cards (vec (concat (subvec cards 0 index)
(subvec cards (inc index))))}))
If you don't want to coerce your input to a vector
, you can use the .subList()
function via Java interop:
(dotest
(spyxx (.subList (concat (range 5) (range 10 15)) 5 10))
(spyxx (.subList (range 10) 2 5))
(spyxx (.subList (vec (range 10)) 2 5))
(spyxx (subvec (vec (range 10)) 2 5))
(throws? (subvec (range 10) 2 5))) ; *** not allowed ***
with result
(.subList (concat (range 5) (range 10 15)) 5 10)
=> <#java.util.ArrayList$SubList [10 11 12 13 14]>
(.subList (range 10) 2 5)
=> <#java.util.Collections$UnmodifiableRandomAccessList [2 3 4]>
(.subList (vec (range 10)) 2 5)
=> <#clojure.lang.APersistentVector$SubVector [2 3 4]>
(subvec (vec (range 10)) 2 5)
=> <#clojure.lang.APersistentVector$SubVector [2 3 4]>
Upvotes: 1