Skip to content

Commit 23b0cf6

Browse files
yoshi-monsterlpil
authored andcommitted
fix: fold/map overflow
fix #896
1 parent 77455d0 commit 23b0cf6

File tree

2 files changed

+74
-4
lines changed

2 files changed

+74
-4
lines changed

src/dict.mjs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,9 @@ function nextGeneration(dict) {
417417
node[generationKey] = 0;
418418

419419
// queue all other referenced nodes
420-
const nodeStart = Math.imul(popcount(node.datamap), 2);
420+
// We need to query the length from the nodemap, as we don't know if this
421+
// is an overflow node or not! if it is, it will never have datamap set!
422+
const nodeStart = data.length - popcount(node.nodemap);
421423
for (let i = nodeStart; i < node.data.length; ++i) {
422424
queue.push(node.data[i]);
423425
}
@@ -521,7 +523,7 @@ function insertIntoNode(transient, node, key, value, hash, shift) {
521523
const childShift = shift + bits;
522524

523525
let child = emptyNode;
524-
child = insertIntoNode(transient, emptyNode, key, value, hash, childShift);
526+
child = insertIntoNode(transient, child, key, value, hash, childShift);
525527

526528
const key2 = data[dataidx];
527529
const value2 = data[dataidx + 1];
@@ -638,7 +640,9 @@ export function map(dict, fun) {
638640
const node = queue.pop();
639641
const data = node.data;
640642
// every node contains popcount(datamap) direct entries
641-
const edgesStart = Math.imul(popcount(node.datamap), 2);
643+
// We need to query the length from the nodemap, as we don't know if this
644+
// is an overflow node or not! if it is, it will never have datamap set!
645+
const edgesStart = data.length - popcount(node.nodemap);
642646
for (let i = 0; i < edgesStart; i += 2) {
643647
// we copied the node while queueing it, so direct mutation here is safe.
644648
data[i + 1] = fun(data[i], data[i + 1]);
@@ -662,7 +666,9 @@ export function fold(dict, state, fun) {
662666
const node = queue.pop();
663667
const data = node.data;
664668
// every node contains popcount(datamap) direct entries
665-
const edgesStart = Math.imul(popcount(node.datamap), 2);
669+
// We need to query the length from the nodemap, as we don't know if this
670+
// is an overflow node or not! if it is, it will never have datamap set!
671+
const edgesStart = data.length - popcount(node.nodemap);
666672
for (let i = 0; i < edgesStart; i += 2) {
667673
state = fun(state, data[i], data[i + 1]);
668674
}

test/gleam/dict_test.gleam

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,70 @@ pub fn hash_collision_overflow_test() {
417417
assert dict.get(d, CollidingKey2) == Ok(2)
418418
}
419419

420+
type Colour {
421+
Red
422+
Green
423+
Blue
424+
}
425+
426+
pub fn hash_collision_fold_test() {
427+
let colours =
428+
dict.from_list([
429+
#(Red, "red"),
430+
#(Green, "green"),
431+
#(Blue, "blue"),
432+
])
433+
434+
let str =
435+
dict.fold(colours, "", fn(acc, _color, name) { acc <> name <> "\n" })
436+
assert string.contains(str, "green\n")
437+
assert string.contains(str, "red\n")
438+
assert string.contains(str, "blue\n")
439+
}
440+
441+
pub fn hash_collision_map_test() {
442+
let colours =
443+
dict.from_list([
444+
#(Red, "red"),
445+
#(Green, "green"),
446+
#(Blue, "blue"),
447+
])
448+
449+
let uppercase =
450+
dict.map_values(colours, fn(_colour, name) { string.uppercase(name) })
451+
452+
assert dict.size(uppercase) == 3
453+
assert dict.get(uppercase, Red) == Ok("RED")
454+
assert dict.get(uppercase, Green) == Ok("GREEN")
455+
assert dict.get(uppercase, Blue) == Ok("BLUE")
456+
}
457+
458+
pub fn hash_collision_values_test() {
459+
let colours =
460+
dict.from_list([
461+
#(Red, "red"),
462+
#(Green, "green"),
463+
#(Blue, "blue"),
464+
])
465+
466+
assert list.sort(dict.values(colours), string.compare)
467+
== ["blue", "green", "red"]
468+
}
469+
470+
pub fn hash_collision_keys_test() {
471+
let colours =
472+
dict.from_list([
473+
#(Red, "red"),
474+
#(Green, "green"),
475+
#(Blue, "blue"),
476+
])
477+
478+
assert dict.keys(colours)
479+
|> list.filter_map(dict.get(colours, _))
480+
|> list.sort(string.compare)
481+
== ["blue", "green", "red"]
482+
}
483+
420484
fn test_random_operations(
421485
initial_seed: Int,
422486
num_ops: Int,

0 commit comments

Comments
 (0)