For a third take on this problem, you can use the generator/yield style of programming popular in Python. This solution makes use of the lazy-gen
& yield
combination from the Tupelo library:
(ns tst.clj.core
(:use clojure.test tupelo.test)
(:require [tupelo.core :as t] ))
(t/refer-tupelo)
(defn new-deck []
(lazy-gen
(let [id (atom 0)]
(doseq [suit [:spade :heart :diamond :club]]
(doseq [rank [:2 :3 :4 :5 :6 :7 :8 :9 :10 :jack :queen :king :ace] ]
(yield {:id (swap! id inc) :suit suit :rank rank}))))))
(pprint (new-deck))
({:suit :spade, :rank :2, :id 1}
{:suit :spade, :rank :3, :id 2}
{:suit :spade, :rank :4, :id 3}
{:suit :spade, :rank :5, :id 4}
{:suit :spade, :rank :6, :id 5}
{:suit :spade, :rank :7, :id 6}
{:suit :spade, :rank :8, :id 7}
{:suit :spade, :rank :9, :id 8}
{:suit :spade, :rank :10, :id 9}
{:suit :spade, :rank :jack, :id 10}
{:suit :spade, :rank :queen, :id 11}
{:suit :spade, :rank :king, :id 12}
{:suit :spade, :rank :ace, :id 13}
{:suit :heart, :rank :2, :id 14}
{:suit :heart, :rank :3, :id 15}
{:suit :heart, :rank :4, :id 16}
{:suit :heart, :rank :5, :id 17}
<snip>
{:suit :club, :rank :10, :id 48}
{:suit :club, :rank :jack, :id 49}
{:suit :club, :rank :queen, :id 50}
{:suit :club, :rank :king, :id 51}
{:suit :club, :rank :ace, :id 52})
I just had to use keywords for the card names since using :rank 13
for the ace (or is it the king?) is just wrong...(shudder)...on so many levels! ;) Note that it is legal for a keyword in Clojure to consist of only digits.
Since the yield
is putting the individual items on the output queue, we don't need to return a sequence from for
and can just use doseq
. Also, we don't need to worry about a multi-variate for
, nor about using flatten
, concat
, or mapcat
constructs. Use of the atom for id
is as simple as it gets, although you could build the whole thing out of loop/recur
instead of the atom & 2 doseq
forms if you really wanted to be "pure".
Under the covers lazy-gen/yield
uses core.async
to create an output stream with a default buffer size of 32.
While for
and map-indexed
solutions work fine for this example, sometimes it may be clearer to the reader to be extra explicit about the looping constructs, etc. Also, if there were other operations in the inner loop before & after the yield
statement, it might be awkward to force the solution into a map
or for
style solution.
Enjoy!
P.S. Before the purists complain about the use of mutable state & imperative loops, please remember that for some problems (not all!) this may yield a simpler solution with fewer mental stack frames required. Also, the solution is a pure function overall as seen from the outside, since it returns a non-side-effecting lazy-sequence, and neither the state atom nor the imperative loops escape the function body. This is similar to the implementation of many functions in clojure.core itself.