Skip to content

Commit 34d5e17

Browse files
authored
Merge pull request #42 from lambdaisland/laurence/add-remove-unchanged
add remove-unchanged API
2 parents 61440d4 + 41d3153 commit 34d5e17

File tree

10 files changed

+250
-14
lines changed

10 files changed

+250
-14
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Added
44

55
- Enable print tests in babashka
6+
- Add a `lambdaisland.deep-diff2/minimize` function, which removes any items
7+
that haven't changed from the diff.
68

79
## Fixed
810

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ For fine grained control you can create a custom Puget printer, and supply it to
8888

8989
For more advanced uses like incorporating diffs into your own Fipp documents, see `lambdaisland.deep-diff2.printer/format-doc`, `lambdaisland.deep-diff2.printer/print-doc`.
9090

91+
### Minimizing
92+
93+
If you are only interested in the changes, and not in any values that haven't
94+
changed, then you can use `ddiff/minimize` to return a more compact diff.
95+
96+
This is especially useful for potentially large nested data structures, for
97+
example a JSON response coming from a web service.
98+
99+
```clj
100+
(-> (ddiff/diff {:a "apple" :b "pear"} {:a "apple" :b "banana"})
101+
ddiff/minimize
102+
ddiff/pretty-print)
103+
;; {:b -"pear" +"banana"}
104+
```
105+
91106
### Print handlers for custom or built-in types
92107

93108
In recent versions deep-diff2 initializes its internal copy of Puget with

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"dependencies": {
55
"react": "^16.13.1",
66
"react-dom": "^16.13.1",
7-
"ws": "^8.11.0"
7+
"ws": "^8.13.0"
88
}
99
}

repl_sessions/poke.clj

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
(ns repl-sessions.poke
2+
(:require [lambdaisland.deep-diff2 :as ddiff]))
3+
4+
(seq #{{:foo 1M} {:bar 2}}) ;; => ({:foo 1M} {:bar 2})
5+
(seq #{{:foo 1} {:bar 2}}) ;; => ({:bar 2} {:foo 1})
6+
7+
(def d1 {{:foo 1M} {:bar 2}})
8+
(def d2 {{:foo 1} {:bar 2}})
9+
(ddiff/pretty-print (ddiff/diff d1 d2))
10+
;; #{+{:foo 1} -{:foo 1M} {:bar 2}}
11+
12+
(def d1 #{{:foo 1M}})
13+
(def d2 #{{:foo 1}})
14+
(ddiff/pretty-print (ddiff/diff d1 d2))
15+
16+
(-> (ddiff/diff {:a "apple" :b "pear"} {:a "apple" :b "banana"})
17+
ddiff/minimize
18+
ddiff/pretty-print)
19+
;; {:b -"pear" +"banana"}
20+
21+
;; {:b -2 +3}
22+
23+
[#{1.1197369622161879e-14 1.3019822841584656e-21 0.6875
24+
#uuid "a907a7fe-d2eb-482d-b1cc-3acfc12daf55"
25+
-30
26+
:X/*!1:3
27+
:u7*A/p?2IG5d*!Nl
28+
:**d7ws
29+
"ý"
30+
"ÔB*àñS�¬ÚûV¡ç�¯±·á£H�
31+
�û?'V$ëY;CL�k-oOV"
32+
!U-h_C*A7/x0_n1
33+
A-*wn./o_?4w18-!
34+
"ìêܼà4�^¤mÐðkt�ê1_ò�· À�4\n@J\"2�9)cd-\t®"
35+
y3W-2
36+
#uuid "6d507164-f8b9-401d-8c44-d6b0e310c248"
37+
"M"
38+
:cy7-3
39+
:w4/R.-s?9V5
40+
#uuid "1bcb00c9-88b9-4eae-9fea-60600dfaefa0"
41+
-20
42+
#uuid "269ab6f9-f19d-4c9d-a0cb-51150e52e9f7"
43+
-235024979
44+
:O:m_9.9+A/N+usPa6.HA*G
45+
228944.657438457
46+
:x/w?
47+
:__+o+sut9!t/?0l
48+
"�â��«"
49+
false
50+
#uuid "b6295f83-8176-47b5-946e-466f74226629"
51+
e3zQ!E*5
52+
:T5rb
53+
:++y:2
54+
-7364
55+
zG/ex23
56+
"¡"
57+
-4318364480
58+
:D+?2?!/Hrc!jA7z_2
59+
:z-I/!8Uq+d?
60+
-0.5588235294117647
61+
-0.5925925925925926
62+
-0.8108108108108109}]

src/lambdaisland/deep_diff2.cljc

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
(ns lambdaisland.deep-diff2
2-
(:require [lambdaisland.deep-diff2.diff-impl :as diff-impl]
3-
[lambdaisland.deep-diff2.printer-impl :as printer-impl]))
2+
(:require
3+
[lambdaisland.deep-diff2.diff-impl :as diff-impl]
4+
[lambdaisland.deep-diff2.printer-impl :as printer-impl]
5+
[lambdaisland.deep-diff2.minimize-impl :as minimize]))
46

57
(defn diff
68
"Compare two values recursively.
@@ -41,3 +43,8 @@
4143
(-> diff
4244
(printer-impl/format-doc printer)
4345
(printer-impl/print-doc printer))))
46+
47+
(defn minimize
48+
"Return a minimal diff, removing any values that haven't changed."
49+
[diff]
50+
(minimize/minimize diff))

src/lambdaisland/deep_diff2/diff_impl.cljc

+23-6
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,31 @@
9797
(diff-seq-insertions ins)
9898
(into []))))
9999

100+
(defn diff-set [exp act]
101+
(into
102+
(into #{}
103+
(map (fn [e]
104+
(if (contains? act e)
105+
e
106+
(->Deletion e))))
107+
exp)
108+
(map ->Insertion)
109+
(remove #(contains? exp %) act)))
110+
111+
(let [exp {false 0, 0 0}
112+
act {false 0, 0 0}
113+
exp-ks (keys exp)
114+
act-ks (concat (filter #(contains? (set (keys act)) %) exp-ks)
115+
(remove #(contains? (set exp-ks) %) (keys act)))
116+
[del ins] (del+ins exp-ks act-ks)]
117+
[del ins])
118+
(del+ins [0 false] [0 false])
119+
100120
(defn diff-map [exp act]
101121
(first
102122
(let [exp-ks (keys exp)
103-
act-ks (concat (filter (set (keys act)) exp-ks)
104-
(remove (set exp-ks) (keys act)))
123+
act-ks (concat (filter #(contains? (set (keys act)) %) exp-ks)
124+
(remove #(contains? (set exp-ks) %) (keys act)))
105125
[del ins] (del+ins exp-ks act-ks)]
106126
(reduce
107127
(fn [[m idx] k]
@@ -162,10 +182,7 @@
162182
(extend-protocol Diff
163183
#?(:clj java.util.Set :cljs cljs.core/PersistentHashSet)
164184
(-diff-similar [exp act]
165-
(let [exp-seq (seq exp)
166-
act-seq (seq act)]
167-
(set (diff-seq exp-seq (concat (filter act exp-seq)
168-
(remove exp act-seq))))))
185+
(diff-set exp act))
169186
#?@(:clj
170187
[java.util.List
171188
(-diff-similar [exp act] (diff-seq exp act))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
(ns lambdaisland.deep-diff2.minimize-impl
2+
"Provide API for manipulate the diff structure data "
3+
(:require [clojure.walk :refer [postwalk]]
4+
#?(:clj [lambdaisland.deep-diff2.diff-impl]
5+
:cljs [lambdaisland.deep-diff2.diff-impl :refer [Mismatch Deletion Insertion]]))
6+
#?(:clj (:import [lambdaisland.deep_diff2.diff_impl Mismatch Deletion Insertion])))
7+
8+
(defn diff-item?
9+
"Checks if x is a Mismatch, Deletion, or Insertion"
10+
[x]
11+
(or (instance? Mismatch x)
12+
(instance? Deletion x)
13+
(instance? Insertion x)))
14+
15+
(defn has-diff-item?
16+
"Checks if there are any diff items in x or sub-tree of x"
17+
[x]
18+
(or (diff-item? x)
19+
(and (map? x) (some #(or (has-diff-item? (key %))
20+
(has-diff-item? (val %))) x))
21+
(and (coll? x) (some has-diff-item? x))))
22+
23+
(defn minimize
24+
"Postwalk diff, removing values that are unchanged"
25+
[diff]
26+
(let [y (postwalk
27+
(fn [x]
28+
(cond
29+
(map-entry? x)
30+
;; Either k or v of a map-entry contains/is? diff-item,
31+
;; keep the map-entry. Otherwise, remove it.
32+
(when (or (has-diff-item? (key x))
33+
(has-diff-item? (val x)))
34+
x)
35+
36+
(map? x)
37+
x
38+
39+
(coll? x)
40+
(into (empty x) (filter has-diff-item?) x)
41+
42+
:else
43+
x))
44+
diff)]
45+
(cond
46+
(coll? y) y
47+
:else nil)))

test/lambdaisland/deep_diff2/diff_test.cljc

+10-4
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,16 @@
212212
(gen/such-that (complement NaN?) gen/simple-type)))
213213

214214
(defspec round-trip-diff 100
215-
(prop/for-all [x gen-any-except-NaN
216-
y gen-any-except-NaN]
217-
(let [diff (diff/diff x y)]
218-
(= [x y] [(diff/left-undiff diff) (diff/right-undiff diff)]))))
215+
(prop/for-all
216+
[x gen-any-except-NaN
217+
y gen-any-except-NaN]
218+
(let [diff (diff/diff x y)]
219+
(= [x y] [(diff/left-undiff diff) (diff/right-undiff diff)]))))
220+
221+
(defspec diff-same-is-same 100
222+
(prop/for-all
223+
[x gen-any-except-NaN]
224+
(= x (diff/diff x x))))
219225

220226
(deftest diff-seq-test
221227
(is (= [(diff/->Insertion 1) 2 (diff/->Insertion 3)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
(ns lambdaisland.deep-diff2.minimize-test
2+
(:require [clojure.test :refer [deftest testing is are]]
3+
[lambdaisland.deep-diff2.diff-test :as diff-test]
4+
[clojure.test.check.clojure-test :refer [defspec]]
5+
[clojure.test.check.generators :as gen]
6+
[clojure.test.check.properties :as prop]
7+
[lambdaisland.deep-diff2.diff-impl :as diff]
8+
[lambdaisland.deep-diff2 :as ddiff]))
9+
10+
(deftest basic-strip-test
11+
(testing "diff without minimize"
12+
(let [x {:a 1 :b 2 :d {:e 1} :g [:e [:k 14 :g 15]]}
13+
y {:a 1 :c 3 :d {:e 15} :g [:e [:k 14 :g 15]]}]
14+
(is (= (ddiff/diff x y)
15+
{:a 1
16+
(diff/->Deletion :b) 2
17+
:d {:e (diff/->Mismatch 1 15)}
18+
:g [:e [:k 14 :g 15]]
19+
(diff/->Insertion :c) 3}))))
20+
(testing "diff with minimize"
21+
(let [x {:a 1 :b 2 :d {:e 1} :g [:e [:k 14 :g 15]]}
22+
y {:a 1 :c 3 :d {:e 15} :g [:e [:k 14 :g 15]]}]
23+
(is (= (ddiff/minimize (ddiff/diff x y))
24+
{(diff/->Deletion :b) 2
25+
:d {:e (diff/->Mismatch 1 15)}
26+
(diff/->Insertion :c) 3})))))
27+
28+
(deftest minimize-on-diff-test
29+
(testing "diffing atoms"
30+
(testing "when different"
31+
(is (= (ddiff/minimize
32+
(ddiff/diff :a :b))
33+
(diff/->Mismatch :a :b))))
34+
35+
(testing "when equal"
36+
(is (= (ddiff/minimize
37+
(ddiff/diff :a :a))
38+
nil))))
39+
40+
(testing "diffing collections"
41+
(testing "when different collection types"
42+
(is (= (ddiff/minimize
43+
(ddiff/diff [:a :b] #{:a :b}))
44+
(diff/->Mismatch [:a :b] #{:a :b}))))
45+
46+
(testing "when equal with clojure set"
47+
(is (= (ddiff/minimize
48+
(ddiff/diff #{:a :b} #{:a :b}))
49+
#{})))
50+
51+
(testing "when different with clojure set"
52+
(is (= (ddiff/minimize
53+
(ddiff/diff #{:a :b :c} #{:a :b :d}))
54+
#{(diff/->Insertion :d) (diff/->Deletion :c)})))
55+
56+
(testing "when equal with clojure vector"
57+
(is (= (ddiff/minimize
58+
(ddiff/diff [:a :b] [:a :b]))
59+
[])))
60+
61+
(testing "when equal with clojure hashmap"
62+
(is (= (ddiff/minimize
63+
(ddiff/diff {:a 1} {:a 1}))
64+
{})))
65+
66+
(testing "when equal with clojure nesting vector"
67+
(is (= (ddiff/minimize
68+
(ddiff/diff [:a [:b :c :d]] [:a [:b :c :d]]))
69+
[])))))
70+
71+
;; "diff itself and minimize yields empty"
72+
(defspec diff-itself 100
73+
(prop/for-all
74+
[x diff-test/gen-any-except-NaN]
75+
(if (coll? x)
76+
(= (ddiff/minimize (ddiff/diff x x))
77+
(empty x))
78+
(nil? (ddiff/minimize (ddiff/diff x x))))))

tests.edn

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#kaocha/v1
22
{:tests [{:id :clj}
33
{:id :cljs
4-
:type :kaocha.type/cljs}]}
4+
:type :kaocha.type/cljs}]
5+
:kaocha/bindings {kaocha.stacktrace/*stacktrace-filters* []}
6+
}

0 commit comments

Comments
 (0)