A recent post to StackOverflow
<http://stackoverflow.com/questions/42798706/clojure-clojurescript-elegant-way-to-generate-a-deck-of-cards/42801711#42801711>
asked for clarification on different ways of generating a deck of cards.
The OP had used nested `for` with `flatten` and `map-indexed` but thought
there must be a better (clearer & more concise) solution.  In addition to
the first two solutions offered, this problem also shows an example of when
Python-style *generator functions* with a *yield* statement may also be
considered. Pasted below is the example:

---------------------------------------------------------
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
<https://github.com/cloojure/tupelo#generator-functions-for-lazy-sequences-a-la-python>
:


(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 *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 (modeled after lazy chunking
in clojure.core).

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!

Alan

P.S.  I noticed after searching that Alex Engelberg worked on something
similar a few years ago. This version is slightly different from his
(hopefully improved!), although I did switch some symbols to gensyms after
seeing his implementation.  :)

P.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.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to