The introductory statement above just about says it all. Here's a concrete example of that principle applied to solving problem 22 of "Project Euler". (Make sure you read the question description to understand the following code. https://projecteuler.net/problem=22)
We start with the input:
(def input ["MARY", "PATRICIA", "LINDA", ...
We test-drive a function for calculating the 'alphabetic' score:
(defn alpha-score [word]
(->> (string/lower-case word)
(map #(inc (- (int %) (int \a))))
(apply +)))
This function does the following:
(it "computes alphabetic score"
(should= 6 (alpha-score "abc"))
(should= 53 (alpha-score "COLIN")))
We test-drive a function for calculating the 'name' score:
(defn name-score [rank word]
(* rank (alpha-score word)))
Supposing that the name "COLIN" is the 938th name in the list:
(it "computes name score"
(should= 49714 (name-score 938 "COLIN")))
Now, to solve the problem we must:
- Sort the input names in alphabetical order
- Interleave the indices of each name in the list with each name
- Make pairs of each index/name combo.
- Pass each index/name pair to the
name-score
function - Sum the results of each and ever call to
name-score
Here's a literal implementation of the above steps:
(defn solve-1 []
(let [indices (range 1 (inc (count input)))]
(as-> #_"step 1" (sort input) $
#_"step 2" (interleave indices $)
#_"step 3" (partition 2 $)
#_"step 4" (map #(name-score (first %1) (second %1)) $)
#_"step 5" (apply + $))))
It seems that steps 2 and 3 are really just a mapping of two collections over a function that receives two arguments...WHICH IS WHAT THE MAP FUNCTION ALREADY DOES!
Most of the time when we use map
we are mapping a single collection over a function that receives that argument. But map
is much more flexible in that it expects 'n' collections and a function that receives 'n' arguments.
Here's a more succinct implementation which makes use of this knowledge:
(defn solve-2 []
(let [indices (range 1 (inc (count input)))]
(as-> #_"step 1 " (sort input) $
#_"steps 2-4" (map name-score indices $)
#_"step 5 " (apply + $))))
(Of course, we could use ->>
('thread-last') instead of as->
('thread-as'), but I like how 'thread-as' overtly displays both collections being passed to the map
function in this case.)