mirror of https://github.com/zulip/zulip.git
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:
parent
26168eaa98
commit
4e59937632
|
@ -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);
|
||||
});
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue