Traveling the world of applicative operators, section 7.8 of David Touretzky’s Gentle Introduction to Common Lisp introduces the remove-if and the remove-if-not operator. Both work like the operators we have met in part one, two, and three of this chapter: they take a function (or better: a predicate) and a list as input. Where remove-if removes all elements of the input list that are true for the given predicate, remove-if-not leaves exactly these and removes those elements that don’t match the predicate. Instead of using a predicate like #’oddp you can also use lambda expression. Nothing new so far if you have read the past articles and tried the exercises.
Exercise 7.11 asks you to write a function to pick out numbers in a list that are greater than 1 and less than 5.
Exercise 7.12 lets you write a function that’s counting how often the word ‘the’ appears in a sentence.
Exercise 7.13 wants you to pick only lists of length two in a list of lists.
Exercise 7.14 makes you implement functions intersection and union using remove-if and remove.
This is the Lisp code for these exercises:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
;; ex 7.11 (defun gt1-lt5 (x) (remove-if-not #'(lambda (num) (and (> num 1) (< num 5))) x)) ;;; ex 7.12 (defun count-the-the (words) (length (remove-if-not #'(lambda (word) (equal word 'the)) words))) ;;; ex 7.13 (defun no-more-than-two (list-of-lists) (remove-if-not #'(lambda (list) (= (length list) 2)) list-of-lists)) ;; ex 7.14 ;; a version of set-difference using remove-if (defun my-setdiff (x y) (remove-if #'(lambda (e) (member e y)) x)) ;; a version of intersection using remove-if (defun my-intersection (x y) (remove-if-not #'(lambda (e) (member e y)) x)) ;; a version of union using remove-if (defun my-union (x y) (remove-duplicates (append x y))) ;; or else: ;; (append x (remove-if #'(lambda (a) (member a x)) y))) |
And this is the same code in Clojure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
;; ex 7.11 (defn gt1-lt5 [x] (remove (fn [num] (not (and (> num 1) (< num 5)))) x)) ;;; ex 7.12 (defn count-the-the [words] (count (remove (fn [word] (not= word 'the)) words))) ;; ex 7.13 (defn no-more-than-two [list-of-lists] ;; call: (no-more-than-two '((a b c) (c d) (e) (f g) (h i j))) (remove (fn [list] (not= (count list) 2)) list-of-lists)) ;; ex 7.14 (defn member [element set] (if (not= (first set) element) (member element (rest set)) set)) ;; best one (defn member [x sq] (if (seq sq) (if (= x (first sq)) sq (recur x (rest sq))))) (defn member [elem s] (contains? (set s) elem)) (defn my-setdiff [x y] (remove (fn [e] (member e y)) x)) (defn my-intersection [x y] (remove (fn [e] (not (member e y))) x)) (defn my-union [x y] (flatten (conj x (remove (fn [a] (member a x)) y)))) |
Let’s discuss this code briefly: gt1-gt5 is almost identical in both languages, apart from a few syntax differences. However, Clojure doesn’t know a remove-if-not, so the “not” part wanders from remove to the lambda part, but the result is the same.
Counting the “the”s in a sentence (a list) is also almost identical in both cases: we eliminate every element not equal to ‘the’ in the list and count the remaining elements. The differences are subtle, though: Lisp’s length is a count in Clojure. We already know there’s no remove-if-not, so Clojure’s remove makes us use a slightly different test for the word comparison: (not= word ‘the’) is the shortcut version of (not (= word ‘the)). Note the simplified = that subsumes the many other equality tests in Lisp.
The same applies to the no-more-than-two function.
We have to reach back a bit for exercise 7.14: the implementations of setdiff and intersection in Lisp make use of Lisp’s practical member function which doesn’t exist in Clojure. Member, as the name implies, tells you if an element is present in a list. It does so by not telling you just true or false, but by returning the element and the following elements of the input list. You can see three simple emulations of this member function for Clojure. All of them are rather primitive, because they don’t support keywords like the Lisp original, but each one will do what we want from it.
Having this member defined, the Clojure versions of setdiff, intersection, and union strongly look like the versions written in lisp. Only my-union appears to be a bit more complex. Maybe somebody wants to send me a better implementation.
Exercise 7.15 is a mini keyboard exercise that makes you write a simple card game. I won’t summarize the problem definitions here, you will find them in Toretzky’s book which is available for download on his website.
This is the Lisp code for exercise 7.15:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
;;; ex 7.15 ;;; a (defun rank (card) (first card)) (defun suit (card) (second card)) ;;; b (defparameter my-hand '((3 hearts) (5 clubs) (2 diamonds) (4 diamonds) (ace spades))) ;; call this with (count-suit 'diamonds my-hand) (defun count-suit (s hand) "Count the various suits of a given hand of cards" (length (remove-if-not #'(lambda (card) (equal (suit card) s)) hand))) ;;; c (defparameter colors '((clubs black) (diamonds red) (hearts red) (spades black))) ;; call this with (color-of '(2 clubs)) (defun color-of (x) "Get color a given card." (let ((card (second x))) (second (assoc card colors)))) ;;; d ;; call this with (first-red my-hand) (defun first-red (hand) "Show first card in given hand." (find-if #'(lambda (card) (equal 'red (color-of card))) hand)) ;;; e ;; call this with (black-cards my-hand) (defun black-cards (hand) "Reveal all black cards of a given hand." (remove-if-not #'(lambda (card) (equal 'black (color-of card))) hand)) ;;; f ;; call this with (what-ranks 'spades my-hand) (defun what-ranks (suit hand) (let ((cards (remove-if-not #'(lambda (card) (equal suit (second card))) hand))) (mapcar #'first cards))) ;; or: (defun _what-ranks (suit hand) (let ((cards (remove-if-not #'(lambda (card) (equal (suit card) suit)) hand))) (mapcar #'(lambda (card) (rank card)) cards))) ;;; g (defparameter all-ranks '(2 3 4 5 6 7 8 9 10 jack queen king ace)) ;; call this with (higher-rank-p '(king diamonds) '(3 diamonds)) (defun higher-rank-p (card1 card2) "Returns t if the 1st card has a higher rank." (if (member (rank card1) (member (rank card2) all-ranks)) t nil)) ;;; h (defun high-card (hand) "Return the highest ranked card of the given hand." (let ((revranks (reverse all-ranks))) (assoc (find-if #'(lambda (rank) (assoc rank hand)) revranks) hand))) |
And this is the same code written in Clojure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
;;; ex 7.15 ;;; a (defn rank [card] (get card :rank)) (defn suit [card] (get card :suit)) ;;; b (def my-hand [{:rank 3 :suit 'hearts} {:rank 5 :suit 'clubs} {:rank 2 :suit 'diamonds} {:rank 4 :suit 'diamonds} {:rank 'ace :suit 'spades}]) (defn count-suit [s hand] (count (remove (fn [card] (not= (suit card) s)) hand))) ;;; c (def colors {:clubs 'black :diamonds 'red :hearts 'red :spades 'black}) (defn color-of [x] (get colors (keyword x))) ;;; d (defn first-red [hand] (first (filter (fn [card] (= 'red (color-of (:suit card)))) hand))) ;;; e (defn black-cards [hand] (filter (fn [card] (= 'black (color-of (:suit card)))) hand)) ;;; f (defn what-ranks [s hand] (let [cards (remove (fn [card] (not= (suit card) s)) hand)] (map (fn [card] (rank card)) cards))) ;;; g (def all-ranks '(2 3 4 5 6 7 8 9 10 jack queen king ace)) (defn higher-rank-p [card1 card2] ;; call: (higher-rank-p {:rank 'ace :suit 'hearts} {:rank 'king :suit 'diamonds}) (if (member (rank card1) (member (rank card2) all-ranks)) true false)) ;;; h (defn high-card [hand] (let [revranks (reverse all-ranks)] (first (filter (fn [rank] (member rank (map :rank my-hand))) revranks)))) |
In 7.15b you can see that we again transformed Lisp’s association list into a map for Clojure. Clojure doesn’t know association lists, but maps are more powerful, anyway. my-hand and colors have become maps, and the bigger differences between the Lisp and Clojure versions concern accessing data: where it’s list accessor function like first and second in Lisp, you are using get for accessing map data in Lisp. The overall logic of the Clojure functions is the same like in its Lisp counterparts.