This is where Phel sings. map, filter, reduce, threading macros, list comprehensions, and destructuring turn data wrangling into a readable pipeline.
Exercise 1 medium
Increment every number in [4 7 9 10]. Use map.
;; pass a named function directly
(map inc [4 7 9 10])
;; => @[5 8 10 11]
;; or with the short anonymous form
(map #(+ % 1) [4 7 9 10])
;; or with fn
(map (fn [x] (+ x 1)) [4 7 9 10])
map applies a function to every element and returns a new collection. When a built-in already does what you need (like inc), pass it by name.
Learn more: Functions and Recursion
Exercise 2 medium
Uppercase every name:
["ada" "grace" "alan"]
;; => ["ADA" "GRACE" "ALAN"]
Hint: php/strtoupper does the lifting.
(map php/strtoupper ["ada" "grace" "alan"])
;; => @["ADA" "GRACE" "ALAN"]
PHP functions are reachable via the php/ prefix - the entire PHP stdlib is at your fingertips.
Learn more: PHP Interop
Exercise 3 medium
Keep only the even numbers in [1 2 3 4 5 6 7 8 9 10].
(filter even? [1 2 3 4 5 6 7 8 9 10])
;; => @[2 4 6 8 10]
filter keeps the elements for which the predicate returns truthy.
Exercise 4 medium
From [1 2 3 4 5 6 7 8 9 10], keep the evens and double each.
(map #(* % 2) (filter even? [1 2 3 4 5 6 7 8 9 10]))
;; => @[4 8 12 16 20]
Composing filter and map is a daily pattern. Read it inside-out: filter first, then map.
Exercise 5 medium
Rewrite the previous solution with the threading macro ->> so it reads top-to-bottom.
(->> [1 2 3 4 5 6 7 8 9 10]
(filter even?)
(map #(* % 2)))
;; => @[4 8 12 16 20]
->> (thread-last) feeds each result as the last argument to the next call. Nested calls become a clear pipeline.
Learn more: Functions and Recursion
Exercise 6 medium
Use the other threading macro, ->, to build a user step by step:
(-> {}
(assoc :name "Ada")
(assoc :age 36)
(assoc :role :admin))
;; => {:name "Ada" :age 36 :role :admin}
(-> {}
(assoc :name "Ada")
(assoc :age 36)
(assoc :role :admin))
;; => {:name "Ada" :age 36 :role :admin}
-> (thread-first) inserts each result as the first argument of the next call. Use -> for "build up a value" pipelines (often with maps), and ->> for "transform a sequence" pipelines.
Learn more: Functions and Recursion
Exercise 7 medium
Use reduce to sum [1 2 3 4 5].
(reduce + 0 [1 2 3 4 5])
;; => 15
reduce collapses a collection to a single value: it takes a function, an initial value, and a collection. Here it computes (+ (+ (+ (+ (+ 0 1) 2) 3) 4) 5).
Exercise 8 medium
Use reduce to find the longest string in ["cat" "elephant" "dog" "hippopotamus"].
(reduce
(fn [longest s]
(if (> (php/strlen s) (php/strlen longest)) s longest))
""
["cat" "elephant" "dog" "hippopotamus"])
;; => "hippopotamus"
Any time you need to fold a collection down to a single value, reduce is the tool.
Exercise 9 medium
Use for to extract every :value from this vector of maps:
(def data [{:id 1 :value 10.3} {:id 2 :value 20.06} {:id 7 :value 30.1}])
Expected result: (10.3 20.06 30.1).
(def data [{:id 1 :value 10.3} {:id 2 :value 20.06} {:id 7 :value 30.1}])
(for [m :in data] (:value m))
;; => @[10.3 20.06 30.1]
for is a list comprehension - it generates a new sequence by transforming each element.
Learn more: Control Flow
Exercise 10 medium
Use for with two bindings to generate every combination of suit and rank:
(def suits [:hearts :diamonds :clubs :spades])
(def ranks [:ace :king :queen])
;; => build all 12 [suit rank] pairs
(def suits [:hearts :diamonds :clubs :spades])
(def ranks [:ace :king :queen])
(for [s :in suits
r :in ranks]
[s r])
;; => @[[:hearts :ace] [:hearts :king] [:hearts :queen]
;; [:diamonds :ace] [:diamonds :king] [:diamonds :queen]
;; [:clubs :ace] [:clubs :king] [:clubs :queen]
;; [:spades :ace] [:spades :king] [:spades :queen]]
Multiple bindings act like nested loops: the right-most binding varies fastest. Add :when clauses to filter, :let clauses to bind locals.
Learn more: Control Flow
Exercise 11 medium
Sort a list of people by age:
(def people [{:name "Charlie" :age 30}
{:name "Ada" :age 36}
{:name "Bob" :age 25}])
(def people [{:name "Charlie" :age 30}
{:name "Ada" :age 36}
{:name "Bob" :age 25}])
(sort-by :age people)
;; => [{:name "Bob" :age 25} {:name "Charlie" :age 30} {:name "Ada" :age 36}]
sort-by takes a key function and a collection. Since keywords are functions, :age extracts the value to compare on.
Exercise 12 medium
Use update-in to bump the balance from 3 to 4:
(def data {:shops [:shop-1]
:customers [{:id "Bob"
:account {:balance 3}}]})
(def data {:shops [:shop-1]
:customers [{:id "Bob"
:account {:balance 3}}]})
(update-in data [:customers 0 :account :balance] inc)
;; => {:shops [:shop-1] :customers [{:id "Bob" :account {:balance 4}}]}
update-in walks into a nested structure and applies a function at the path.
Learn more: Data Structures
Exercise 13 medium
Use frequencies to count fruit appearances:
["apple" "banana" "apple" "cherry" "banana" "apple"]
(frequencies ["apple" "banana" "apple" "cherry" "banana" "apple"])
;; => {"apple" 3 "banana" 2 "cherry" 1}
frequencies returns a map from each distinct element to its count. Perfect for tallies.
Exercise 14 medium
Use group-by to split numbers into evens and odds:
[1 2 3 4 5 6 7 8]
(group-by even? [1 2 3 4 5 6 7 8])
;; => {false [1 3 5 7] true [2 4 6 8]}
group-by runs the function on each element and bundles values that share a result.
Exercise 15 hard
Define area so it accepts a map {:width w :height h} and returns w * h. Use destructuring in the parameter list:
(area {:width 5 :height 3}) ; => 15
(defn area [{:keys [width height]}]
(* width height))
{:keys [width height]} destructures the map directly in the parameter list - no let required. This pattern is everywhere in idiomatic Phel: tidy callers, self-documenting signatures.
Learn more: Destructuring
Exercise 16 hard
Define combine that merges a vector of maps into one:
(combine [{:a 1 :b 2} {:c 3} {:d 4 :e 5}])
;; => {:a 1 :b 2 :c 3 :d 4 :e 5}
(defn combine [maps]
(apply merge maps))
apply "spreads" the vector as individual arguments. So (apply merge [{:a 1} {:b 2}]) is the same as (merge {:a 1} {:b 2}). This trick works with any variadic function.
Learn more: Functions and Recursion