Skip to content

Commit

Permalink
stdlib: use Identity in HashMap and HashSet and add tests for s…
Browse files Browse the repository at this point in the history
…pecial float values
  • Loading branch information
soc committed Dec 18, 2023
1 parent 097296e commit 6357f76
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 33 deletions.
20 changes: 10 additions & 10 deletions dora/stdlib/collections.dora
Original file line number Diff line number Diff line change
Expand Up @@ -520,15 +520,15 @@ impl[T] Queue[T] {
}
}

@pub class HashMap[K: Hash + Equals, V] {
@pub class HashMap[K: Hash + Identity + Equals, V] {
inserted_and_deleted: BitSet,
keys: Array[K],
values: Array[V],
entries: Int64,
cap: Int64,
}

impl[K: Hash + Equals, V] HashMap[K, V] {
impl[K: Hash + Identity + Equals, V] HashMap[K, V] {
@pub @static fun new(entries: (K, V)...): HashMap[K, V] {
// BitSet.size == capacity * 2
// [bit 0: inserted; bit 1: deleted] * capacity
Expand Down Expand Up @@ -561,7 +561,7 @@ impl[K: Hash + Equals, V] HashMap[K, V] {
... .isLive(idx) {
let current_key = self.keys.get(idx);

if current_key.hash() == hash && current_key.equals(key) {
if current_key.hash() == hash && (current_key.identicalTo(key) || current_key.equals(key)) {
let old_value = self.values.get(idx);
self.values.set(idx, value);
return Some[V](old_value);
Expand Down Expand Up @@ -607,7 +607,7 @@ impl[K: Hash + Equals, V] HashMap[K, V] {
... .isLive(idx) {
let current_key = self.keys.get(idx);

if current_key.hash() == hash && current_key.equals(key) {
if current_key.hash() == hash && (current_key.identicalTo(key) || current_key.equals(key)) {
return true;
}
idx = (idx + 1i64) & (self.cap - 1i64);
Expand Down Expand Up @@ -639,7 +639,7 @@ impl[K: Hash + Equals, V] HashMap[K, V] {
... .isLive(idx) {
let current_key = self.keys.get(idx);

if current_key.hash() == hash && current_key.equals(key) {
if current_key.hash() == hash && (current_key.identicalTo(key) || current_key.equals(key)) {
return Option[V]::Some(self.values.get(idx));
}
idx = (idx + 1i64) & (self.cap - 1i64);
Expand Down Expand Up @@ -667,7 +667,7 @@ impl[K: Hash + Equals, V] HashMap[K, V] {
... .isLive(idx) {
let current_key = self.keys.get(idx);

if current_key.hash() == hash && current_key.equals(key) {
if current_key.hash() == hash && (current_key.identicalTo(key) || current_key.equals(key)) {
let value = self.values.get(idx);
self.inserted_and_deleted.insert(2i64 * idx + 1i64);

Expand Down Expand Up @@ -780,12 +780,12 @@ impl[K: Hash + Equals, V] HashMap[K, V] {
@pub fun iterator(): HashMapIterator[K, V] = HashMapIterator[K, V]::new(self);
}

@pub class HashMapIterator[K: Hash + Equals, V] {
@pub class HashMapIterator[K: Hash + Identity + Equals, V] {
map: HashMap[K, V],
idx: Int64,
}

impl[K: Hash + Equals, V] HashMapIterator[K, V] {
impl[K: Hash + Identity + Equals, V] HashMapIterator[K, V] {
@pub @static fun new(map: HashMap[K, V]): HashMapIterator[K, V] = HashMapIterator[K, V](map, 0);

@pub fun next(): Option[(K, V)] {
Expand All @@ -804,11 +804,11 @@ impl[K: Hash + Equals, V] HashMapIterator[K, V] {
}
}

@pub class HashSet[K: Hash + Equals] {
@pub class HashSet[K: Hash + Identity + Equals] {
map: HashMap[K, ()],
}

impl[K: Hash + Equals] HashSet[K] {
impl[K: Hash + Identity + Equals] HashSet[K] {
@pub @static fun new(keys: K...): HashSet[K] {
let map: HashMap[K, ()] = HashMap[K, ()]::new();

Expand Down
20 changes: 20 additions & 0 deletions tests/stdlib/hashmap-contains-nan.dora
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
fun main(): Unit {
float64();
float32();
}

fun float64(): Unit {
let map = std::HashMap[Float64, String]::new((Float64::notANumber(), "a"));

assert(map.size() == 1i64);
assert(map.contains(Float64::notANumber()));
assert(map.get(Float64::notANumber()).getOrPanic() == "a");
}

fun float32(): Unit {
let map = std::HashMap[Float32, String]::new((Float32::notANumber(), "a"));

assert(map.size() == 1i64);
assert(map.contains(Float32::notANumber()));
assert(map.get(Float32::notANumber()).getOrPanic() == "a");
}
24 changes: 24 additions & 0 deletions tests/stdlib/hashmap-contains-zero.dora
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
fun main(): Unit {
float64();
float32();
}

fun float64(): Unit {
let map = std::HashMap[Float64, String]::new((0.0, "a"));

assert(map.size() == 1i64);
assert(map.contains(0.0));
assert(map.get(0.0).getOrPanic() == "a");
assert(map.contains(-0.0)); // FIXME: this only works by coincidence, because the hash function sucks
assert(map.get(-0.0).getOrPanic() == "a"); // FIXME: this only works by coincidence, because the hash function sucks
}

fun float32(): Unit {
let map = std::HashMap[Float32, String]::new((0.0f32, "a"));

assert(map.size() == 1i64);
assert(map.contains(0.0f32));
assert(map.get(0.0f32).getOrPanic() == "a");
assert(map.contains(-0.0f32).not()); // FIXME: should be true instead
// assert(map.get(-0.0f32).getOrPanic() == "a"); // FIXME: should be true instead
}
16 changes: 16 additions & 0 deletions tests/stdlib/hashmap-contains.dora
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
fun main(): Unit {
let map = std::HashMap[Int32, String]::new((1i32, "a"), (2i32, "b"), (3i32, "c"), (4i32, "d"));

assert(map.size() == 4i64);

assert(map.contains(1i32));
assert(map.contains(2i32));
assert(map.contains(3i32));
assert(map.contains(4i32));
assert(map.contains(0i32).not());

assert(map.get(1i32).getOrPanic() == "a");
assert(map.get(2i32).getOrPanic() == "b");
assert(map.get(3i32).getOrPanic() == "c");
assert(map.get(4i32).getOrPanic() == "d");
}
File renamed without changes.
15 changes: 0 additions & 15 deletions tests/stdlib/hashmap3.dora

This file was deleted.

18 changes: 18 additions & 0 deletions tests/stdlib/hashset-contains-nan.dora
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
fun main(): Unit {
float64();
float32();
}

fun float64(): Unit {
let set = std::HashSet[Float64]::new(Float64::notANumber());

assert(set.size() == 1i64);
assert(set.contains(Float64::notANumber()));
}

fun float32(): Unit {
let set = std::HashSet[Float32]::new(Float32::notANumber());

assert(set.size() == 1i64);
assert(set.contains(Float32::notANumber()));
}
20 changes: 20 additions & 0 deletions tests/stdlib/hashset-contains-zero.dora
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
fun main(): Unit {
float64();
float32();
}

fun float64(): Unit {
let set = std::HashSet[Float64]::new(0.0);

assert(set.size() == 1i64);
assert(set.contains(0.0));
assert(set.contains(-0.0)); // FIXME: this only works by coincidence, because the hash function sucks
}

fun float32(): Unit {
let set = std::HashSet[Float32]::new(0.0f32);

assert(set.size() == 1i64);
assert(set.contains(0.0f32));
assert(set.contains(-0.0f32).not()); // FIXME: should be true instead
}
9 changes: 9 additions & 0 deletions tests/stdlib/hashset-contains.dora
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fun main(): Unit {
let set = std::HashSet[Int32]::new(1i32, 10'000i32, 7i32);

assert(set.size() == 3i64);
assert(set.contains(1i32));
assert(set.contains(10'000i32));
assert(set.contains(7i32));
assert(set.contains(0i32).not());
}
8 changes: 0 additions & 8 deletions tests/stdlib/hashset3.dora

This file was deleted.

0 comments on commit 6357f76

Please sign in to comment.