Search code examples
clojure

Remove one set from another in Clojure


(def mine '(a b c))
(def yours '(a b))
(remove yours mine)
 ; should return (c)

I was advised to use remove in another thread but it doesn't work. Anyone can advise?


Solution

  • Assuming you want to remove from mine every element that also exists in yours, there are a few approaches. You can convert the lists to sets and use difference, like this:

    (require '[clojure.set :refer [difference]])
    
    (def mine '(a b c))
    (def yours '(a b))
    
    (difference (set mine) (set yours))
    ;; => #{c}
    

    But this does not preserve the order of elements that remain from mine. If you want to preserve the order, you can instead use remove. To do that, we first define a yours? predicate that will return true for an element iff that element occurs in yours:

    (def yours-set (set yours))
    (def yours? (partial contains? yours-set))
    

    If our set yours only contains truthy values like that are neither nil nor false, we could define it like (def yours? (set yours)) since a set implements the IFn interface but this approach will not work if yours contains elements such as nil or false as @amalloy pointed out.

    (remove yours? mine)
    ;; => (c)
    

    The above code means that we remove every element from mine for which yours? evaluates to true. Yet another, more verbose approach, is to use the opposite of remove, that is filter, and passing in the opposite of the predicate.

    (filter (complement yours?) mine)
    ;; => (c)
    

    but I see no gain of that more verbose approach here.

    If you know that you want a vector as a result, you can instead use into, passing in a removeing transducer as argument.

    (into [] (remove yours?) mine)
    ;; => [c]