Permalink
Browse files

DAVL-3: Enhance avl set & map hashes for Clojure 1.6 compatibility

Also add more tests that fail when using Clojure 1.6, without these
enhancements.
  • Loading branch information...
1 parent fffb65a commit 99c895e9e688452c91903d00c06b8f1201df7f8d @jafingerhut jafingerhut committed with michalmarczyk Feb 9, 2014
Showing with 54 additions and 21 deletions.
  1. +29 −10 src/main/clojure/clojure/data/avl.clj
  2. +25 −11 src/test/clojure/clojure/data/avl_test.clj
@@ -33,6 +33,11 @@
(set! ~hash-key (int h#))
h#))))
+(def clojure-16-hash
+ (>= (compare [(:major *clojure-version*) (:minor *clojure-version*)]
+ [1 6])
+ 0))
+
(defn ^:private hash-imap
[^IPersistentMap m]
(APersistentMap/mapHash m))
@@ -46,16 +51,22 @@
(loop [h (int 0) s (seq s)]
(if s
(let [e (first s)]
- (recur (unchecked-add-int h (hash e))
+ (recur (unchecked-add-int h (if (nil? e) 0 (.hashCode ^Object e)))
(next s)))
h)))
+(defmacro ^:private hasheq-iset* [s]
+ (if clojure-16-hash
+ `(-> (reduce unchecked-add-int 0 (map hash ~s))
+ (mix-collection-hash (count ~s)))
+ `(loop [h# (int 0) s# (seq ~s)]
+ (if s#
+ (recur (unchecked-add-int h# (Util/hasheq (first s#)))
+ (next s#))
+ h#))))
+
(defn ^:private hasheq-iset [^IPersistentSet s]
- (loop [h (int 0) s (seq s)]
- (if s
- (recur (unchecked-add-int h (Util/hasheq (first s)))
- (next s))
- h)))
+ (hasheq-iset* s))
(defn ^:private hash-seq
[s]
@@ -77,6 +88,12 @@
(next s))
h)))
+(def empty-set-hashcode (.hashCode #{}))
+(def empty-set-hasheq (hash #{}))
+(def empty-map-hashcode (.hashCode {}))
+(def empty-map-hasheq (hash {}))
+
+
(defn ^:private equiv-sequential
"Assumes x is sequential. Returns true if x equals y, otherwise
returns false."
@@ -1007,7 +1024,7 @@
(reduce conj this entry)))
(empty [this]
- (AVLMap. comp nil 0 _meta 0 0))
+ (AVLMap. comp nil 0 _meta empty-map-hashcode empty-map-hasheq))
(equiv [this that]
(equiv-map this that))
@@ -1290,7 +1307,7 @@
(AVLSet. _meta (assoc avl-map x x) -1 -1))
(empty [this]
- (AVLSet. _meta (empty avl-map) 0 0))
+ (AVLSet. _meta (empty avl-map) empty-set-hashcode empty-set-hasheq))
(equiv [this that]
(and
@@ -1426,9 +1443,11 @@
(count [this]
(.count transient-avl-map)))
-(def ^:private empty-map (AVLMap. RT/DEFAULT_COMPARATOR nil 0 nil 0 0))
+(def ^:private empty-map (AVLMap. RT/DEFAULT_COMPARATOR nil 0 nil
+ empty-map-hashcode empty-map-hasheq))
-(def ^:private empty-set (AVLSet. nil empty-map 0 0))
+(def ^:private empty-set (AVLSet. nil empty-map
+ empty-set-hashcode empty-set-hasheq))
(doseq [v [#'->AVLMapSeq
#'->AVLNode
@@ -44,28 +44,42 @@
(def even-numbers (apply avl/sorted-set (range 0 large-tree-size 2)))
+(defn is-same-coll [a b]
+ (let [msg (format "(class a)=%s (class b)=%s a=%s b=%s"
+ (.getName (class a)) (.getName (class b)) a b)]
+ (is (= (count a) (count b) (.size a) (.size b)) msg)
+ (is (= a b) msg)
+ (is (= b a) msg)
+ (is (.equals ^Object a b) msg)
+ (is (.equals ^Object b a) msg)
+ (is (= (hash a) (hash b)) msg)
+ (is (= (.hashCode ^Object a) (.hashCode ^Object b)) msg)))
+
(deftest sanity-checks
(testing "AVL collections look like regular sorted collections"
- (is (= rb-map avl-map))
- (is (= rb-set avl-set)))
+ (is-same-coll rb-map avl-map)
+ (is-same-coll rb-set avl-set)
+ ;; Check empty maps are equal, and hashes equal
+ (is-same-coll (empty rb-map) (empty avl-map))
+ (is-same-coll (empty rb-set) (empty avl-set)))
(testing "AVL collections with custom comparators looks like regular ones"
- (is (= rb-map-by-> avl-map-by->))
- (is (= rb-set-by-> avl-set-by->)))
+ (is-same-coll rb-map-by-> avl-map-by->)
+ (is-same-coll rb-set-by-> avl-set-by->))
(testing "AVL collection seqs look like regular sorted collection seqs"
(is (= (seq rb-map) (seq avl-map)))
(is (= (seq rb-set) (seq avl-set)))
(is (= (subseq rb-map > 100 < 1000) (subseq avl-map > 100 < 1000)))
(is (= (subseq rb-set > 100 < 1000) (subseq avl-set > 100 < 1000))))
(testing "non-transient construction works as expected"
- (is (= avl-map (reduce-kv assoc (avl/sorted-map) rb-map))))
+ (is-same-coll avl-map (reduce-kv assoc (avl/sorted-map) rb-map)))
(testing "dissoc/dissoc! work as expected"
- (is (= (reduce dissoc rb-map ks') (reduce dissoc avl-map ks')))
- (is (= (reduce dissoc rb-map ks')
- (persistent! (reduce dissoc! (transient avl-map) ks')))))
+ (is-same-coll (reduce dissoc rb-map ks') (reduce dissoc avl-map ks'))
+ (is-same-coll (reduce dissoc rb-map ks')
+ (persistent! (reduce dissoc! (transient avl-map) ks'))))
(testing "disj/disj! work as expected"
- (is (= (reduce disj rb-set ks') (reduce disj avl-set ks')))
- (is (= (reduce disj rb-set ks')
- (persistent! (reduce disj! (transient avl-set) ks')))))
+ (is-same-coll (reduce disj rb-set ks') (reduce disj avl-set ks'))
+ (is-same-coll (reduce disj rb-set ks')
+ (persistent! (reduce disj! (transient avl-set) ks'))))
(testing "*-by seqs look like they should"
(is (= (seq rb-map-by->) (seq avl-map-by->)))
(is (= (seq rb-set-by->) (seq avl-set-by->))))

0 comments on commit 99c895e

Please sign in to comment.