diff

added
1.3

ns
clojure.data

type
function

(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}