Search code examples
clojurefunctional-programmingstatistics

Monty Hall and Lazy Sequences (?)


I'm just starting to learn Clojure and I'm trying to simulate the Monty Hall problem:

(defn create-contest
  "Creates a monty hall doors contest"
  [n]
  (do (def door-with-car (rand-int n))
      (map
       (fn [i] (if (= i door-with-car) :car :closed))
       (range n)))) 

(defn create-doors
  "Create a collection of monty hall contests"
  [doors contests]
  (doall
   (map
     (fn [i] (create-contest i))
     (repeat contests doors))))

But every time I execute the create-doors function all the doors with cars end up in the same position:

broker.core> (create-doors 4 10)
((:car :closed :closed :closed)
 (:car :closed :closed :closed)
 (:car :closed :closed :closed)
 (:car :closed :closed :closed)
 (:car :closed :closed :closed)
 (:car :closed :closed :closed)
 (:car :closed :closed :closed)
 (:car :closed :closed :closed)
 (:car :closed :closed :closed)
 (:car :closed :closed :closed))
broker.core> (create-doors 4 10)
((:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed))
broker.core> (create-doors 4 10)
((:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed)
 (:closed :car :closed :closed))

What am I doing wrong?


Solution

  • You're very close, you should just use a let form instead of def here:

    (defn create-contest
      "Creates a monty hall doors contest"
      [n]
      (let [door-with-car (rand-int n)] ;; let instead of def
        (map
          (fn [i] (if (= i door-with-car) :car :closed))
          (range n))))
    

    def will bind some value to a var in the current namespace, but you want to bind a value in the scope of this function, and that's what let is good for. See this Q&A for another explanation.

    (defn create-doors
      "Create a collection of monty hall contests"
      [doors contests]
      (map
        create-contest ;; no need to wrap create-contest in another function here
        (repeat contests doors)))
    

    You don't need to wrap create-contest in another function for use with map — you can just pass the function directly to map as a value. The doall is only necessary to force realization of the lazy sequence from map, so you probably don't need/want that inside create-doors.

    (create-doors 4 10)
    =>
    ((:closed :closed :car :closed)
     (:closed :closed :closed :car)
     (:closed :car :closed :closed)
     (:closed :closed :closed :car)
     (:closed :car :closed :closed)
     (:closed :car :closed :closed)
     (:closed :car :closed :closed)
     (:closed :closed :closed :car)
     (:closed :closed :car :closed)
     (:closed :closed :car :closed))