(diff a b)
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}