List Comprehension

In general terms, list comprehensions should:

  • be distinct from (nested) for loops and the use of map & filter functions within the syntax of the language.
  • return either a list or an iterator (an iterating being something that returns successive members of a collection, in order),

In Clojure, list comprehension is via the for function. This is different to the for in other languages as you will see.

  1. (for [number [1 2 3]] (* number 2))

The for function should be read as follows:

“for each number in the collection [1 2 3], apply the function (* number 2)”

Couldnt we just do this with map? Yes, we could.

  1. (map #(* % 2) [1 2 3])

So why do we need for function? It really shows its value when you are working with multiple collections

  1. (for [number [1 2 3]
  2. letter [:a :b :c]]
  3. (str number letter))

Again we could use map function for this as follows

  1. (mapcat (fn [number] (map (fn [letter] (str number letter)))))

So with the for function we can do the same calculation with much easier code to reason about.

Filtering results with predicates

With the for function we can add a filter on the results by using a predicate, to test if a condition is true or false. Any values that meet the condition as true are returned, values that are false are ommitted.

  1. (for [x (range 10) :when (odd? x)] x)
  2. (for [x (range 10) :while (even? x)] x)

To do this kind of filtering with maps would be possible, however the code would be harder for humans to parse and understand.

Note Create a 3-tumbler combination padlock, with each tumbler having a range of 0 to 9. Count the number of possible combinations. Then add a predicate that filters out some of the combinations

Lets just model all the possible combinations

  1. (for [tumbler-1 (range 10)
  2. tumbler-2 (range 10)
  3. tumbler-3 (range 10)]
  4. [tumbler-1 tumbler-2 tumbler-3])

Now lets count the combinations

  1. (count (for [tumbler-1 (range 10)
  2. tumbler-2 (range 10)
  3. tumbler-3 (range 10)]
  4. [tumbler-1 tumbler-2 tumbler-3]))

Now add a predicate using :when to filter out the combinations that do not match.

  1. (count (for [tumbler-1 (range 10)
  2. tumbler-2 (range 10)
  3. tumbler-3 (range 10)
  4. :when (or (= tumbler-1 tumbler-2)
  5. (= tumbler-2 tumbler-3)
  6. (= tumbler-3 tumbler-1))]
  7. [tumbler-1 tumbler-2 tumbler-3]))

Note Create a 2 character prefix for tickets, using capital letters from the English alphabet. However, exclude I and O as they can be mistaken for numbers

Lets just model all the possible combinations

  1. (for [letter-1 capital-letters
  2. letter-2 capital-letters
  3. :when (and (not (blacklisted letter-1))
  4. (not (blacklisted letter-2)))]
  5. (str letter-1 letter-2))