js: Add IntDict class.

We don't use this yet, but we will soon.

We report errors if users pass in strings instead of
ints, but we try to still use the key.
This commit is contained in:
Steve Howell 2019-12-29 13:40:02 +00:00 committed by Tim Abbott
parent 26168eaa98
commit 4e59937632
4 changed files with 243 additions and 0 deletions

View File

@ -0,0 +1,157 @@
set_global('blueslip', global.make_zblueslip());
const IntDict = zrequire('int_dict').IntDict;
run_test('basic', () => {
const d = new IntDict();
assert(d.is_empty());
assert.deepEqual(d.keys(), []);
d.set(101, 'bar');
assert.equal(d.get(101), 'bar');
assert(!d.is_empty());
d.set(101, 'baz');
assert.equal(d.get(101), 'baz');
assert.equal(d.num_items(), 1);
d.set(102, 'qux');
assert.equal(d.get(101), 'baz');
assert.equal(d.get(102), 'qux');
assert.equal(d.num_items(), 2);
assert.equal(d.has(102), true);
assert.equal(d.has(999), false);
assert.deepEqual(d.keys(), [101, 102]);
assert.deepEqual(d.values(), ['baz', 'qux']);
d.del(102);
assert.equal(d.has(102), false);
assert.strictEqual(d.get(102), undefined);
assert.deepEqual(d.keys(), [101]);
const val = ['fred'];
const res = d.set(103, val);
assert.equal(val, res);
});
run_test('each', () => {
const d = new IntDict();
d.set(4, 40);
d.set(5, 50);
d.set(6, 60);
let unseen_keys = d.keys();
let cnt = 0;
d.each(function (v, k) {
assert.equal(v, d.get(k));
unseen_keys = _.without(unseen_keys, k);
cnt += 1;
});
assert.equal(cnt, d.keys().length);
assert.equal(unseen_keys.length, 0);
});
/*
run_test('benchmark', () => {
const d = new IntDict();
const n = 5000;
const t1 = new Date().getTime();
_.each(_.range(n), (i) => {
d.set(i, i);
});
_.each(_.range(n), (i) => {
d.get(i, i);
});
const t2 = new Date().getTime();
const elapsed = t2 - t1;
console.log('elapsed (milli)', elapsed);
console.log('per (micro)', 1000 * elapsed / n);
});
*/
run_test('undefined_keys', () => {
blueslip.clear_test_data();
blueslip.set_test_data('error', 'Tried to call a IntDict method with an undefined key.');
const d = new IntDict();
assert.equal(d.has(undefined), false);
assert.strictEqual(d.get(undefined), undefined);
assert.equal(blueslip.get_test_logs('error').length, 2);
});
run_test('non integers', () => {
blueslip.clear_test_data();
blueslip.set_test_data('error', 'Tried to call a IntDict method with a non-integer.');
const d = new IntDict();
assert.equal(d.has('some-string'), false);
assert.equal(blueslip.get_test_logs('error').length, 1);
// verify stringified ints still work
blueslip.clear_test_data();
blueslip.set_test_data('error', 'Tried to call a IntDict method with a non-integer.');
d.set('5', 'five');
assert.equal(d.has(5), true);
assert.equal(d.has('5'), true);
assert.equal(d.get(5), 'five');
assert.equal(d.get('5'), 'five');
assert.equal(blueslip.get_test_logs('error').length, 3);
});
run_test('num_items', () => {
const d = new IntDict();
assert.equal(d.num_items(), 0);
assert(d.is_empty());
d.set(101, 1);
assert.equal(d.num_items(), 1);
assert(!d.is_empty());
d.set(101, 2);
assert.equal(d.num_items(), 1);
assert(!d.is_empty());
d.set(102, 1);
assert.equal(d.num_items(), 2);
d.del(101);
assert.equal(d.num_items(), 1);
});
run_test('clear', () => {
const d = new IntDict();
function populate() {
d.set(101, 1);
assert.equal(d.get(101), 1);
d.set(102, 2);
assert.equal(d.get(102), 2);
}
populate();
assert.equal(d.num_items(), 2);
assert(!d.is_empty());
d.clear();
assert.equal(d.get(101), undefined);
assert.equal(d.get(102), undefined);
assert.equal(d.num_items(), 0);
assert(d.is_empty());
// make sure it still works after clearing
populate();
assert.equal(d.num_items(), 2);
});

View File

@ -33,6 +33,7 @@ import "../lightbox_canvas.js";
import "../rtl.js";
import "../lazy_set.js";
import "../dict.ts";
import "../int_dict.ts";
import "../fold_dict.ts";
import "../scroll_util.js";
import "../components.js";

84
static/js/int_dict.ts Normal file
View File

@ -0,0 +1,84 @@
import * as _ from 'underscore';
/*
If we know our keys are ints, the
map-based implementation is about
20% faster than if we have to normalize
keys as strings. Of course, this
requires us to be a bit careful in the
calling code. We validate ints, which
is cheap, but we don't handle them; we
just report errors.
This has a subset of methods from our old
Dict class, so it's not quite a drop-in
replacement. For things like setdefault,
it's easier to just use a two-liner in the
calling code. If your Dict uses from_array,
convert it to a Set, not an IntDict.
*/
export class IntDict<V> {
private _map = new Map();
get(key: number): V | undefined {
key = this._convert(key);
return this._map.get(key);
}
set(key: number, value: V): V {
key = this._convert(key);
this._map.set(key, value);
return value;
}
has(key: number): boolean {
key = this._convert(key);
return this._map.has(key);
}
del(key: number): void {
key = this._convert(key);
this._map.delete(key);
}
keys(): number[] {
return Array.from(this._map.keys());
}
values(): V[] {
return Array.from(this._map.values());
}
num_items(): number {
return this._map.size;
}
is_empty(): boolean {
return this._map.size === 0;
}
each(f: (v: V, k?: number) => void): void {
this._map.forEach(f);
}
clear(): void {
this._map.clear();
}
private _convert(key: number): number {
// These checks are cheap! (at least on node.js)
if (key === undefined) {
blueslip.error("Tried to call a IntDict method with an undefined key.");
return key;
}
if (typeof key !== 'number') {
blueslip.error("Tried to call a IntDict method with a non-integer.");
// @ts-ignore
return parseInt(key, 10);
}
return key;
}
}

View File

@ -51,6 +51,7 @@ enforce_fully_covered = {
'static/js/hash_util.js',
'static/js/keydown_util.js',
'static/js/input_pill.js',
'static/js/int_dict.ts',
'static/js/list_cursor.js',
'static/js/markdown.js',
'static/js/message_store.js',