Skip to content

Commit

Permalink
CLJ-1065: Allow duplicate set elements and map keys for all set and m…
Browse files Browse the repository at this point in the history
…ap constructors

Signed-off-by: Stuart Halloway <stu@thinkrelevance.com>
  • Loading branch information
jafingerhut authored and stuarthalloway committed Oct 4, 2012
1 parent 6a424a9 commit 7bc871f
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 10 deletions.
30 changes: 20 additions & 10 deletions src/clj/clojure/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -353,46 +353,54 @@

(defn hash-map
"keyval => key val
Returns a new hash map with supplied mappings."
Returns a new hash map with supplied mappings. If any keys are
equal, they are handled as if by repeated uses of assoc."
{:added "1.0"
:static true}
([] {})
([& keyvals]
(. clojure.lang.PersistentHashMap (createWithCheck keyvals))))
(. clojure.lang.PersistentHashMap (create keyvals))))

(defn hash-set
"Returns a new hash set with supplied keys."
"Returns a new hash set with supplied keys. Any equal keys are
handled as if by repeated uses of conj."
{:added "1.0"
:static true}
([] #{})
([& keys]
(clojure.lang.PersistentHashSet/createWithCheck keys)))
(clojure.lang.PersistentHashSet/create keys)))

(defn sorted-map
"keyval => key val
Returns a new sorted map with supplied mappings."
Returns a new sorted map with supplied mappings. If any keys are
equal, they are handled as if by repeated uses of assoc."
{:added "1.0"
:static true}
([& keyvals]
(clojure.lang.PersistentTreeMap/create keyvals)))

(defn sorted-map-by
"keyval => key val
Returns a new sorted map with supplied mappings, using the supplied comparator."
Returns a new sorted map with supplied mappings, using the supplied
comparator. If any keys are equal, they are handled as if by
repeated uses of assoc."
{:added "1.0"
:static true}
([comparator & keyvals]
(clojure.lang.PersistentTreeMap/create comparator keyvals)))

(defn sorted-set
"Returns a new sorted set with supplied keys."
"Returns a new sorted set with supplied keys. Any equal keys are
handled as if by repeated uses of conj."
{:added "1.0"
:static true}
([& keys]
(clojure.lang.PersistentTreeSet/create keys)))

(defn sorted-set-by
"Returns a new sorted set with supplied keys, using the supplied comparator."
"Returns a new sorted set with supplied keys, using the supplied
comparator. Any equal keys are handled as if by repeated uses of
conj."
{:added "1.1"
:static true}
([comparator & keys]
Expand Down Expand Up @@ -3927,11 +3935,13 @@
([env sym] (ns-resolve *ns* env sym)))

(defn array-map
"Constructs an array-map."
"Constructs an array-map. If any keys are equal, they are handled as
if by repeated uses of assoc."
{:added "1.0"
:static true}
([] (. clojure.lang.PersistentArrayMap EMPTY))
([& keyvals] (clojure.lang.PersistentArrayMap/createWithCheck (to-array keyvals))))
([& keyvals]
(clojure.lang.PersistentArrayMap/createAsIfByAssoc (to-array keyvals))))

;redefine let and loop with destructuring
(defn destructure [bindings]
Expand Down
62 changes: 62 additions & 0 deletions src/jvm/clojure/lang/PersistentArrayMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,68 @@ static public PersistentArrayMap createWithCheck(Object[] init){
}
return new PersistentArrayMap(init);
}

static public PersistentArrayMap createAsIfByAssoc(Object[] init){
// If this looks like it is doing busy-work, it is because it
// is achieving these goals: O(n^2) run time like
// createWithCheck(), never modify init arg, and only
// allocate memory if there are duplicate keys.
int n = 0;
for(int i=0;i< init.length;i += 2)
{
boolean duplicateKey = false;
for(int j=0;j<i;j += 2)
{
if(equalKey(init[i],init[j]))
{
duplicateKey = true;
break;
}
}
if(!duplicateKey)
n += 2;
}
if(n < init.length)
{
// Create a new shorter array with unique keys, and
// the last value associated with each key. To behave
// like assoc, the first occurrence of each key must
// be used, since its metadata may be different than
// later equal keys.
Object[] nodups = new Object[n];
int m = 0;
for(int i=0;i< init.length;i += 2)
{
boolean duplicateKey = false;
for(int j=0;j<m;j += 2)
{
if(equalKey(init[i],nodups[j]))
{
duplicateKey = true;
break;
}
}
if(!duplicateKey)
{
int j;
for (j=init.length-2; j>=i; j -= 2)
{
if(equalKey(init[i],init[j]))
{
break;
}
}
nodups[m] = init[i];
nodups[m+1] = init[j+1];
m += 2;
}
}
if (m != n)
throw new IllegalArgumentException("Internal error: m=" + m);
init = nodups;
}
return new PersistentArrayMap(init);
}
/**
* This ctor captures/aliases the passed array, so do not modify later
*
Expand Down
72 changes: 72 additions & 0 deletions test/clojure/test_clojure/data_structures.clj
Original file line number Diff line number Diff line change
Expand Up @@ -868,3 +868,75 @@
(into (range 7))
pop))))


(deftest test-duplicates
(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
(is (thrown? IllegalArgumentException
(read-string "#{1 2 3 4 1 5}")))
;; 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] (all-equal-sets-incl-meta s
(apply conj #{} x)
(set x)
(apply hash-set x)
(apply sorted-set x)
(apply sorted-set-by cmp-first x))
#{x1 y2} [x1 y2]
#{x1 z3a} [x1 z3a z3b]
#{w5b} [w5b w5a w5c]
#{z3a x1} [z3a z3b x1])

;; Maps
(is (thrown? IllegalArgumentException
(read-string "{:a 1, :b 2, :a -1, :c 3}")))
;; 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] (all-equal-maps-incl-meta h
(apply assoc {} x)
(apply hash-map x)
(apply sorted-map x)
(apply sorted-map-by cmp-first x)
(apply array-map 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])))

0 comments on commit 7bc871f

Please sign in to comment.