Filtering Set Intersections In Clojure

A convenient little trick!

August 30, 2021

My first real experience with a programming language was with Python, specifically, with Peter Norvig's Sudoku solver (which was way over my head at the time). Python actually provides many 'functional' building blocks and there are even examples of Norvig's Python spelling corrector code being translated to Clojure in a very straightforward way.

Fast-forward a few years and I got to spend 8+ years in Go, which touts itself as being 'simple', a word which often means 'lacking in basic data structures, generics, and other such conveniences'. (Don't get me wrong, Go is great!) One thing I really missed in Go was the set data structure. Well, now that I'm neck-deep in Clojure that's not a problem anymore.

One thing I did appreciate from Python was the convenience of built-in mathematic operators for common set operations:

>>> a = set('abracadabra')
>>> b = set('alacazam')

# letters in a but not in b (asymmetric difference)
>>> a - b
{'r', 'd', 'b'}

# letters in a or b or both (union)
>>> a | b
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}

# letters in both a and b (intersection)
>>> a & b
{'a', 'c'}

# letters in a or b but not both (symmetric difference)
>>> a ^ b
{'r', 'd', 'b', 'm', 'z', 'l'}

From https://docs.python.org/3/tutorial/datastructures.html#sets

In clojure you often have to reach for the set namespace to get the same work done (not a big deal, really):

user=> (def a (set "abracadabra"))
user=> (def b (set "alacazam"))

; letters in a but not in b (asymmetric difference)
user=> (clojure.set/difference a b)
#{\b \d \r}

; letters in a or b or both (union)
user=> (clojure.set/union a b)
#{\a \b \c \d \l \m \r \z}

; letters in both a and b (intersection)
user=> (clojure.set/intersection a b)
#{\a \c}

; letters in a or b but not both (symmetric difference)
user=> (clojure.set/union
         (clojure.set/difference a b)
         (clojure.set/difference b a))
#{\b \d \l \m \r \z}

Well, I'm a bit surprised that clojure doesn't provide the symmetric difference, but as you can see it wasn't hard to implement.

Ok, now the reason for this post: there's an easy, more 'built-in' way to achieve the set intersection without using clojure.set/intersection:

(describe "filtering for set intersections"
  (it "works both ways"
    (let [a #{0 1 2 3}
          b #{2 3 4 5}]
      (should= (set (filter a b))
               (set/intersection a b)))))

(But filter doesn't provide a set, so you have to convert...)

-Michael Whatcott