Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New hash set #4550

Merged
merged 2 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions runtime/JavaScript/spec/HashSetSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import HashSet from "../src/antlr4/misc/HashSet.js";
import HashCode from "../src/antlr4/misc/HashCode.js";

class Thing {

value1 = Math.random();
value2 = Math.random();

hashCode() {
return HashCode.hashStuff(this.value1);
}

equals(other) {
return other instanceof Thing
&& other.value1 === this.value1
&& other.value2 === this.value2;
}
}
describe('test HashSet', () => {

it("adds a thing", () => {
const t1 = new Thing();
const t2 = new Thing();
const set = new HashSet();
set.add(t1);
expect(set.has(t1)).toBeTrue();
expect(set.has(t2)).toBeFalse();
expect(set.length).toEqual(1);
})

it("adds a thing once only", () => {
const t1 = new Thing();
const set = new HashSet();
set.add(t1);
set.add(t1);
expect(set.has(t1)).toBeTrue();
expect(set.length).toEqual(1);
})

it("adds 2 things with same hash code", () => {
const t1 = new Thing();
const t2 = new Thing();
t2.value1 = t1.value1;
const set = new HashSet();
set.add(t1);
set.add(t2);
expect(set.has(t1)).toBeTrue();
expect(set.has(t2)).toBeTrue();
expect(set.length).toEqual(2);
})

})
5 changes: 2 additions & 3 deletions runtime/JavaScript/spec/IntervalSetSpec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import antlr4 from "../src/antlr4/index.node.js";
const IntervalSet = antlr4.IntervalSet;
import IntervalSet from "../src/antlr4/misc/IntervalSet.js";

describe('IntervalSet', () => {
describe('test IntervalSet', () => {
it("computes interval set length", () => {
const s1 = new IntervalSet();
s1.addOne(20);
Expand Down
2 changes: 1 addition & 1 deletion runtime/JavaScript/src/antlr4/atn/ATNConfigSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export default class ATNConfigSet {
if (config.reachesIntoOuterContext > 0) {
this.dipsIntoOuterContext = true;
}
const existing = this.configLookup.add(config);
const existing = this.configLookup.getOrAdd(config);
if (existing === config) {
this.cachedHashCode = -1;
this.configs.push(config); // track order here
Expand Down
4 changes: 2 additions & 2 deletions runtime/JavaScript/src/antlr4/atn/ParserATNSimulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,7 @@ export default class ParserATNSimulator extends ATNSimulator {
}

c.reachesIntoOuterContext += 1;
if (closureBusy.add(c)!==c) {
if (closureBusy.getOrAdd(c)!==c) {
// avoid infinite recursion for right-recursive rules
continue;
}
Expand All @@ -1297,7 +1297,7 @@ export default class ParserATNSimulator extends ATNSimulator {
console.log("dips into outer ctx: " + c);
}
} else {
if (!t.isEpsilon && closureBusy.add(c)!==c){
if (!t.isEpsilon && closureBusy.getOrAdd(c)!==c){
// avoid infinite recursion for EOF* and EOF+
continue;
}
Expand Down
105 changes: 77 additions & 28 deletions runtime/JavaScript/src/antlr4/misc/HashSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,108 @@ import standardHashCodeFunction from "../utils/standardHashCodeFunction.js";
import standardEqualsFunction from "../utils/standardEqualsFunction.js";
import arrayToString from "../utils/arrayToString.js";

const HASH_KEY_PREFIX = "h-";
const DEFAULT_LOAD_FACTOR = 0.75;
const INITIAL_CAPACITY = 16

export default class HashSet {

constructor(hashFunction, equalsFunction) {
this.data = {};
this.buckets = new Array(INITIAL_CAPACITY);
this.threshold = Math.floor(INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
this.itemCount = 0;
this.hashFunction = hashFunction || standardHashCodeFunction;
this.equalsFunction = equalsFunction || standardEqualsFunction;
}

add(value) {
const key = HASH_KEY_PREFIX + this.hashFunction(value);
if (key in this.data) {
const values = this.data[key];
for (let i = 0; i < values.length; i++) {
if (this.equalsFunction(value, values[i])) {
return values[i];
}
}
values.push(value);
return value;
} else {
this.data[key] = [value];
get(value) {
if(value == null) {
return value;
}
const bucket = this._getBucket(value)
if (!bucket) {
return null;
}
for (const e of bucket) {
if (this.equalsFunction(e, value)) {
return e;
}
}
return null;
}

has(value) {
return this.get(value) != null;
add(value) {
const existing = this.getOrAdd(value);
return existing === value;
}

get(value) {
const key = HASH_KEY_PREFIX + this.hashFunction(value);
if (key in this.data) {
const values = this.data[key];
for (let i = 0; i < values.length; i++) {
if (this.equalsFunction(value, values[i])) {
return values[i];
}
getOrAdd(value) {
this._expand();
const slot = this._getSlot(value);
let bucket = this.buckets[slot];
if (!bucket) {
bucket = [value];
this.buckets[slot] = bucket;
this.itemCount++;
return value;
}
for (const existing of bucket) {
if (this.equalsFunction(existing, value)) {
return existing;
}
}
return null;
bucket.push(value);
this.itemCount++;
return value;

}

has(value) {
return this.get(value) != null;
}


values() {
return Object.keys(this.data).filter(key => key.startsWith(HASH_KEY_PREFIX)).flatMap(key => this.data[key], this);
return this.buckets.filter(b => b != null).flat(1);
}

toString() {
return arrayToString(this.values());
}

get length() {
return Object.keys(this.data).filter(key => key.startsWith(HASH_KEY_PREFIX)).map(key => this.data[key].length, this).reduce((accum, item) => accum + item, 0);
return this.itemCount;
}

_getSlot(value) {
const hash = this.hashFunction(value);
return hash & this.buckets.length - 1;
}
_getBucket(value) {
return this.buckets[this._getSlot(value)];
}

_expand() {
if (this.itemCount <= this.threshold) {
return;
}
const old_buckets = this.buckets;
const newCapacity = this.buckets.length * 2;
this.buckets = new Array(newCapacity);
this.threshold = Math.floor(newCapacity * DEFAULT_LOAD_FACTOR);
for (const bucket of old_buckets) {
if (!bucket) {
continue;
}
for (const o of bucket) {
const slot = this._getSlot(o);
let newBucket = this.buckets[slot];
if (!newBucket) {
newBucket = [];
this.buckets[slot] = newBucket;
}
newBucket.push(o);
}
}

}
}
Loading