Recursively compares a and b, returning a tuple of [things-only-in-a things-only-in-b things-in-both]. Comparison rules: * For equal a and b, return [nil nil a]. * Maps are subdiffed where keys match and values differ. * Sets are never subdiffed. * All sequential things are treated as associative collections by their indexes, with results returned as vectors. * Everything else (including strings!) is treated as an atom and compared for equality.
(use 'clojure.data) (def uno {:same "same", :different "one"}) (def dos {:same "same", :different "two", :onlyhere "whatever"}) (diff uno dos) => ({:different "one"} {:onlyhere "whatever", :different "two"} {:same "same"}) ;; {different in uno} { different or unique in dos } {same in both} (diff {:a 1} {:a 1 :b 2}) => (nil {:b 2} {:a 1}) ;; the first contains nothing unique, but only the second contains :b ;; and both contain :a
(diff [1 2 3] [5 9 3 2 3 7]) ;;=> [[1 2] [5 9 nil 2 3 7] [nil nil 3]] (diff (set [1 2 3]) (set [5 9 3 2 3 7])) ;;=> [#{1} #{7 9 5} #{3 2}]
;; To invert a diff you can re-apply diff to its output and then merge this back with the prior state ;; This works in almost all cases (with the exception of preserving empty maps) (defn- seqzip "returns a sequence of [[ value-left] [value-right]....] padding with nulls for shorter sequences " [left right] (loop [list [] a left b right] (if (or (seq a) (seq b)) (recur (conj list [(first a) (first b)] ) (rest a) (rest b)) list))) (defn- recursive-diff-merge " Merge two structures recusively , taking non-nil values from sequences and maps and merging sets" [part-state original-state] (cond (sequential? part-state) (map (fn [[l r]] (recursive-diff-merge l r)) (seqzip part-state original-state)) (map? part-state) (merge-with recursive-diff-merge part-state original-state) (set? part-state) (set/union part-state original-state) (nil? part-state ) original-state :default part-state)) (defn undiff "returns the state of x after reversing the changes described by a diff against an earlier state (where before and after are the first two elements of the diff)" [x before after] (let [[a _ _] (clojure.data/diff x after)] (recursive-diff-merge a before))) ;; examples: ;; Simple data types (clojure.data/diff :before :after ) => [:before :after nil] (undiff :after :before :after) => :before ;; Lists (clojure.data/diff [1 2 3 4] [1 2 3 5] ) => [[nil nil nil 4] [nil nil nil 5] [1 2 3]] (undiff [1 2 3 5] [nil nil nil 4] [nil nil nil 5] ) => (1 2 3 4) ;; Nested complex data structures; (clojure.data/diff {:a 1 :b [1 2 3] :c {:d 4}} {:a 2 :b [1 2 3 4] :c {:d 3 :e 10}}) => ({:c {:d 4}, :a 1} {:c {:d 3, :e 10}, :b [nil nil nil 4], :a 2} {:b [1 2 3]}) (undiff {:a 2 :b [1 2 3 4] :c {:d 3 :e 10}} ; State after diff {:c {:d 4}, :a 1} ; first element of diff against previous state {:c {:d 3, :e 10}, :b [nil nil nil 4], :a 2}) ; second element of diff ; against previous state => {:b [1 2 3], :c {:d 4}, :a 1}
;; Beware that empty maps get turned into nil (clojure.data/diff {:a {:b 1} :c 2} {:a {} :c 2}) => ({:a {:b 1}} {:a nil} {:c 2})
;; The diff also applies for nested structures ;; Example creating a function that evaluates if a given value is subset of a map (defn map-subset? [map subset] (let [[_ subset_diff _] (clojure.data/diff map subset)] (nil? subset_diff))) (def a {"KEY" { :kv1 {:nested "X"} :kv2 "Y" }} ) (def b {"KEY" { :kv1 {:nested "X"} }}) (map-subset? a b) => true
clojure.data/diff
See also https://github.com/lambdaisland/deep-diff2 "Deep diff Clojure data structures and pretty print the result"
See https://github.com/juji-io/editscript "Editscript is a library designed to extract the differences between two Clojure/Clojurescript data structures as an 'editscript', which represents the minimal modification necessary to transform one to another"