From 199e94c6ac5789b9f8d2e8dce670c4a46c651ca2 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Tue, 9 Apr 2013 23:13:17 -0700 Subject: [PATCH] Change ordered-map assoc behavior to be like that of Clojure 1.5.1 If a key is assoc'd multiple times to the same map, the first key, along with any metadata it might have, is the one that stays in the map. The last value assoc'd to the key is the one that is in the map, along with any metadata it might have. Similar behavior was added to Clojure with the commit at this link, released with 1.5.0: https://github.com/clojure/clojure/commit/7bc871f16d90064a2c562523332267b4543d751e Refer to CLJ-1065: http://dev.clojure.org/jira/browse/CLJ-1065 --- src/flatland/ordered/map.clj | 28 ++++---- test/flatland/ordered/combined_test.clj | 85 +++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 test/flatland/ordered/combined_test.clj diff --git a/src/flatland/ordered/map.clj b/src/flatland/ordered/map.clj index 96c4bad..0a11ebf 100644 --- a/src/flatland/ordered/map.clj +++ b/src/flatland/ordered/map.clj @@ -93,12 +93,10 @@ (assoc [this k v] (if-let [^MapEntry e (.get ^Map backing-map k)] - (let [old-v (.val e)] - (if (= old-v v) - this - (let [i (.key e)] - (OrderedMap. (.cons backing-map (entry k v i)) - (.assoc order i (MapEntry. k v)))))) + (let [i (.key e) + old-k (.key ^MapEntry (order i))] + (OrderedMap. (.cons backing-map (entry old-k v i)) + (.assoc order i (MapEntry. old-k v)))) (OrderedMap. (.cons backing-map (entry k v (.count order))) (.cons order (MapEntry. k v))))) (without [this k] @@ -156,15 +154,15 @@ (.val e) not-found)) (assoc [this k v] - (let [^MapEntry e (.valAt backing-map k this) - vector-entry (MapEntry. k v) - i (if (identical? e this) - (do (change! order .conj vector-entry) - (dec (.count order))) - (let [idx (.key e)] - (change! order .assoc idx vector-entry) - idx))] - (change! backing-map .conj (entry k v i)) + (let [^MapEntry e (.valAt backing-map k this)] + (if-not (identical? e this) + (let [i (.key e) + old-k (.key ^MapEntry (order i))] + (change! backing-map .conj (entry old-k v i)) + (change! order .assoc i (MapEntry. old-k v))) + (do + (change! backing-map .conj (entry k v (.count order))) + (change! order .conj (MapEntry. k v)))) this)) (conj [this e] (let [[k v] e] diff --git a/test/flatland/ordered/combined_test.clj b/test/flatland/ordered/combined_test.clj new file mode 100644 index 0000000..ee5083f --- /dev/null +++ b/test/flatland/ordered/combined_test.clj @@ -0,0 +1,85 @@ +(ns flatland.ordered.combined-test + (:use clojure.test + [flatland.ordered.map :only [ordered-map]] + [flatland.ordered.set :only [ordered-set]])) + +(defn version->= [vers-a vers-b] + (>= (compare ((juxt :major :minor :incremental) vers-a) + ((juxt :major :minor :incremental) vers-b)) + 0)) + +(deftest test-duplicates-with-metadata + (let [equal-sets-incl-meta (fn [s1 s2] + (and (= s1 s2) + (let [ss1 (sort s1) + ss2 (sort s2)] + (every? identity + (map #(and (= %1 %2) + (= (meta %1) (meta %2))) + ss1 ss2))))) + all-equal-sets-incl-meta (fn [& ss] + (every? (fn [[s1 s2]] + (equal-sets-incl-meta s1 s2)) + (partition 2 1 ss))) + equal-maps-incl-meta (fn [m1 m2] + (and (= m1 m2) + (equal-sets-incl-meta (set (keys m1)) + (set (keys m2))) + (every? #(= (meta (m1 %)) (meta (m2 %))) + (keys m1)))) + all-equal-maps-incl-meta (fn [& ms] + (every? (fn [[m1 m2]] + (equal-maps-incl-meta m1 m2)) + (partition 2 1 ms))) + cmp-first #(> (first %1) (first %2)) + x1 (with-meta [1] {:me "x"}) + y2 (with-meta [2] {:me "y"}) + z3a (with-meta [3] {:me "z3a"}) + z3b (with-meta [3] {:me "z3b"}) + v4a (with-meta [4] {:me "v4a"}) + v4b (with-meta [4] {:me "v4b"}) + v4c (with-meta [4] {:me "v4c"}) + w5a (with-meta [5] {:me "w5a"}) + w5b (with-meta [5] {:me "w5b"}) + w5c (with-meta [5] {:me "w5c"})] + + ;; Sets + ;; If there are duplicate items when doing (conj #{} x1 x2 ...), + ;; the behavior is that the metadata of the first item is kept. + (are [s x] (apply all-equal-sets-incl-meta s + (concat (if (version->= *clojure-version* + {:major 1 :minor 5}) + [ (apply hash-set x) ] + []) + [ (apply conj #{} x) + (into #{} x) + (apply ordered-set x) + (apply conj (ordered-set) x) + (into (ordered-set) x) ])) + #{x1 y2} [x1 y2] + #{x1 z3a} [x1 z3a z3b] + #{w5b} [w5b w5a w5c] + #{z3a x1} [z3a z3b x1]) + + ;; Maps + ;; If there are duplicate keys when doing (assoc {} k1 v1 k2 v2 + ;; ...), the behavior is that the metadata of the first duplicate + ;; key is kept, but mapped to the last value with an equal key + ;; (where metadata of keys are not compared). + (are [h x] (apply all-equal-maps-incl-meta h + (concat (if (version->= *clojure-version* + {:major 1 :minor 5}) + [ (apply hash-map x) ] + []) + [ (apply assoc {} x) + (into {} (map vec (partition 2 x))) + (apply ordered-map x) + (apply assoc (ordered-map) x) + (ordered-map (partition 2 x)) ])) + {x1 2, z3a 4} [x1 2, z3a 4] + {x1 2, z3a 5} [x1 2, z3a 4, z3b 5] + {z3a 5} [z3a 2, z3a 4, z3b 5] + {z3b 4, x1 5} [z3b 2, z3a 4, x1 5] + {z3b v4b, x1 5} [z3b v4a, z3a v4b, x1 5] + {x1 v4a, w5a v4c, v4a z3b, y2 2} [x1 v4a, w5a v4a, w5b v4b, + v4a z3a, y2 2, v4b z3b, w5c v4c])))