В процессе изучения Clojure.
У меня есть функция вытягивания случайной карты из колоды
(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)))}))
Запуск:
(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")}
Я хотел бы заколлировать его дважды и получить 2 карты, но во второй раз, когда он заколлирует, он передаст уменьшенную колоду из первого захода.
В конце концов, я хотел бы иметь 2 карты и уменьшенную колоду, чтобы использовать их позже.
Я бы подумал, что могу сделать что-то вроде:
(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))
Тем не менее, я явно делаю что-то глупое, когда получаю сообщение об ошибке:
Execution error (ClassCastException) at aceyducey.core/draw-random-card (form-init3789790823166246683.clj:5).
clojure.lang.LazySeq cannot be cast to clojure.lang.IPersistentVector
думаю проблема в линии
second-draw (draw-random-card (:remaining-cards first-draw))]
Потому что оставшиеся карты — это не вектор?
Что значит
concat (subvec cards 0 index)
(subvec cards (inc index)))}))
Разве не возвращается вектор? Скорее ленивая последовательность???
Но в этот момент я потерян.
Помощь!
concat
возвращает ленивую последовательность. Вы можете преобразовать его в вектор, используя:
(vec (concat ...))
Вот полный код с тестом:
(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))
)
и результат:
-------------------------------
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]
Если быть точным, проблема заключалась в вызове subvec
во 2-й итерации вашего кода. Вот пример:
(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) *****
с результатом:
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)>
поэтому, принуждая вывод concat
к вектору, вызов subvec
преуспевает в следующий раз через функцию.
Таким образом, оглядываясь назад, лучшим решением было бы принудить ввод к вектору следующим образом:
(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))))}))
Если вы не хотите принуждать свой ввод к vector
, вы можете использовать функцию .subList()
через взаимодействие Java:
(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 ***
с результатом
(.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]>
@amalloy делает хорошее замечание: встроенная функция Clojureshuffle
, вероятно, то, что вам нужно:
(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]
Это и многое другое доступно в CheatSheet по Clojure. Не забудьте добавить его в закладки и всегда держать открытой вкладку браузера.
Спасибо за этот дополнительный ответ и ссылку на шпаргалку, я этого раньше не видел.
Предлагаемые замены все правильные, но довольно дорогие. Я действительно предпочел бы предварительно перетасовать всю колоду, а затем взять «случайные» карты, просто сдвинув их с конца по дешевке.