April 8, 2020

Clojure Goodness: Checking Predicate For Every Or Any Element In A Collection

In Clojure we can use several functions to see if at least one or all elements in a collection return true or false for a predicate. The function every? only returns true if the predicate function returns true for all elements in the collection. To function not-every? return true if a predicate function return false for all elements in a collection. The some function is a bit different (notice there is no ?) and returns the first logical true value from the predicate function for the elements in the collection. So the return type of the predicate doesn't have to be a Boolean value and then the return type of some is also not a Boolean. If the predicate returns a Boolean value we can use some like a any function (any is not part of Clojure). Clojure provides a not-any? function that returns true if the predicate function returns false for one element in the collection and false otherwise.

The following example uses the different functions on a vector with some cartoon names:

(ns mrhaki.seq.pred
  (:require [clojure.test :refer [is]]
            [clojure.string :as str]))

;; Vector of toons to check predicates on.
(def toons ["Daffy Duck" "Bugs Bunny" "Elmer Fudd" "Yosemite Sam"])

;; Helper function to count number of names.
(defn count-names
  (count (str/split name #" ")))

;; Every toon has two names.
(is (true? (every? #(= 2 (count-names %)) toons)))

;; Not every toon name starts with "A".
(is (true? (not-every? #(str/starts-with? "A" %) toons)))

;; Helper function to check if the first letter
;; of both names is the same.
(defn same-first-letters?
  (let [names (str/split name #" ")
        first-letter (first (first names))
        second-letter (first (second names))]
    (= first-letter second-letter)))

;; Some toons have the same first letter
;; for their first and last name.
(is (true? (some same-first-letters? toons)))

;; Using set as function to check toon is in the toons vector.
;; Notice some function return the first value from the predicate function
;; that is not nil or false, instead of a boolean like with
;; every?, not-every? and not-any?.
(is (not (true? (some #{"Yosemite Sam", "Road Runner"} toons))))
(is (= "Yosemite Sam" (some #{"Yosemite Sam", "Road Runner"} toons)))

;; As seen on a
;; possible implementation for any that returns true or false.
(defn any [pred coll] ((comp boolean some) pred coll))
(is (true? (any #{"Yosemite Sam", "Road Runner"} toons)))

;; There is a toon name where their name length is 10.
(is (false? (not-any? #(= (count %) 10) toons)))

Written with Clojure 1.10.1.