ClojureDocs

Nav

Namespaces

diff

clojure.data

Available since 1.3
  • (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.
5 Examples
(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
See Also
No see-alsos for clojure.data/diff
2 Notes
    By , created 5.0 years ago

    See also https://github.com/lambdaisland/deep-diff2 "Deep diff Clojure data structures and pretty print the result"

    By , created 4.7 years ago

    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"