Daniel Neal
Daniel Neal

Reputation: 4173

Set difference using a projected function

I've got two databases that I'm attempting to keep in sync using a bit of Clojure glue code.

I'd like to make something like a clojure.set/difference that operates on values projected by a function.

Here's some sample data:

(diff #{{:name "bob smith" :favourite-colour "blue"} 
        {:name "geraldine smith" :age 29}}

      #{{:first-name "bob" :last-name "smith" :favourite-colour "blue"}} 

       :name 
       (fn [x] (str (:first-name x) " " (:last-name x))))

 ;; => {:name "geraldine smith" :age 29}

The best I've got is:

(defn diff
  "Return members of l who do not exist in r, based on applying function
   fl to left and fr to right"
  [l r fl fr]
  (let [l-project (into #{} (map fl l))
        r-project (into #{} (map fr r))
        d (set/difference l-project r-project)
        i (group-by fl l)]
     (map (comp first i) d)))

But I feel that this is a bit unwieldly, and I can't imagine it performs very well. I'm throwing away information that I'd like to keep, and then looking it up again.

I did have a go using metadata, to keep the original values around during the set difference, but I can't seem put metadata on primitive types, so that didn't work...

I'm not sure why, but I have this tiny voice inside my head telling me that this kind of operation on the side is what monads are for, and that I should really get around to finding out what a monad is and how to use it. Any guidance as to whether the tiny voice is right is very welcome!

Upvotes: 1

Views: 103

Answers (1)

noisesmith
noisesmith

Reputation: 20194

(defn diff
  [l r fl fr]
  (let [r-project (into #{} (map fr r))]
    (set (remove #(contains? r-project (fl %)) l))))

This no longer exposes the difference operation directly (it is now implicit with the remove / contains combination), but it is succinct and should give the result you are looking for.

example usage and output:

user> (diff #{{:name "bob smith" :favourite-colour "blue"} 
              {:name "geraldine smith" :age 29}}

            #{{:first-name "bob" :last-name "smith" :favourite-colour "blue"}} 

            :name 
            (fn [x] (str (:first-name x) " " (:last-name x))))

#{{:age 29, :name "geraldine smith"}}

Upvotes: 2

Related Questions