Merge pull request #746 from rafael-atticlabs/setIntersect

NomsSet.intersect
This commit is contained in:
Rafael Weinstein
2015-12-14 08:08:41 -08:00
3 changed files with 154 additions and 30 deletions
+97
View File
@@ -2,6 +2,7 @@
import type {ChunkStore} from './chunk_store.js';
import type {valueOrPrimitive} from './value.js'; // eslint-disable-line no-unused-vars
import {equals, less} from './value.js';
import {invariant} from './assert.js';
import {Kind} from './noms_kind.js';
import {OrderedSequence} from './ordered_sequence.js';
@@ -25,6 +26,43 @@ export class NomsSet<K:valueOrPrimitive, T> extends OrderedSequence<K, T> {
get size(): number {
return this.items.length;
}
async intersect(...sets: Array<NomsSet>): Promise<NomsSet> {
if (sets.length === 0) {
return this;
}
// Can't intersect sets of different element type.
for (let i = 0; i < sets.length; i++) {
invariant(sets[i].type.equals(this.type));
}
let cursor = await this.newCursorAt(null);
if (!cursor.valid) {
return this;
}
let values: Array<K> = [];
for (let i = 0; cursor.valid && i < sets.length; i++) {
let first = cursor.getCurrent();
let next = await sets[i].newCursorAt(first);
if (!next.valid) {
break;
}
cursor = new SetIntersectionCursor(cursor, next);
await cursor.align();
}
while (cursor.valid) {
values.push(cursor.getCurrent());
await cursor.advance();
}
// TODO: Chunk the resulting set.
return new SetLeaf(this.cs, this.type, values);
}
}
export class SetLeaf<K:valueOrPrimitive> extends NomsSet<K, K> {
@@ -56,3 +94,62 @@ export class CompoundSet<K:valueOrPrimitive> extends NomsSet<K, MetaTuple<K>> {
}
registerMetaValue(Kind.Set, (cs, type, tuples) => new CompoundSet(cs, type, tuples));
type OrderedCursor<K: valueOrPrimitive> = {
valid: boolean;
getCurrent(): K;
advanceTo(key: K): Promise<boolean>;
advance(): Promise<boolean>;
}
class SetIntersectionCursor<K: valueOrPrimitive> {
s1: OrderedCursor<K>;
s2: OrderedCursor<K>;
valid: boolean;
constructor(s1: OrderedCursor<K>, s2: OrderedCursor<K>) {
invariant(s1.valid && s2.valid);
this.s1 = s1;
this.s2 = s2;
this.valid = true;
}
getCurrent(): K {
invariant(this.valid);
return this.s1.getCurrent();
}
async align(): Promise<boolean> {
let v1 = this.s1.getCurrent();
let v2 = this.s2.getCurrent();
while (!equals(v1, v2)) {
if (less(v1, v2)) {
if (!await this.s1.advanceTo(v2)) {
return this.valid = false;
}
v1 = this.s1.getCurrent();
continue;
}
if (!await this.s2.advanceTo(v1)) {
return this.valid = false;
}
v2 = this.s2.getCurrent();
}
return this.valid = true;
}
async advanceTo(key: K): Promise<boolean> {
invariant(this.valid);
return this.valid = await this.s1.advanceTo(key) && await this.align();
}
async advance(): Promise<boolean> {
invariant(this.valid);
return this.valid = await this.s1.advance() && await this.align();
}
}
+47 -23
View File
@@ -5,7 +5,7 @@ import {suite} from 'mocha';
import MemoryStore from './memory_store.js';
import test from './async_test.js';
import {CompoundSet, SetLeaf} from './set.js';
import {CompoundSet, NomsSet, SetLeaf} from './set.js';
import {notNull} from './assert.js';
import {Kind} from './noms_kind.js';
import {makeCompoundType, makePrimitiveType} from './type.js';
@@ -38,33 +38,37 @@ suite('SetLeaf', () => {
});
suite('CompoundSet', () => {
function build(): Array<CompoundSet> {
function build(values: Array<string>): NomsSet {
let ms = new MemoryStore();
let tr = makeCompoundType(Kind.Set, makePrimitiveType(Kind.String));
let l1 = new SetLeaf(ms, tr, ['a', 'b']);
let r1 = writeValue(l1, tr, ms);
let l2 = new SetLeaf(ms, tr, ['e', 'f']);
let r2 = writeValue(l2, tr, ms);
let l3 = new SetLeaf(ms, tr, ['h', 'i']);
let r3 = writeValue(l3, tr, ms);
let l4 = new SetLeaf(ms, tr, ['m', 'n']);
let r4 = writeValue(l4, tr, ms);
assert.isTrue(values.length > 1 && Math.log2(values.length) % 1 === 0);
let m1 = new CompoundSet(ms, tr, [new MetaTuple(r1, 'b'), new MetaTuple(r2, 'f')]);
let rm1 = writeValue(m1, tr, ms);
let m2 = new CompoundSet(ms, tr, [new MetaTuple(r3, 'i'), new MetaTuple(r4, 'n')]);
let rm2 = writeValue(m2, tr, ms);
let tuples = [];
for (let i = 0; i < values.length; i += 2) {
let l = new SetLeaf(ms, tr, [values[i], values[i + 1]]);
let r = writeValue(l, tr, ms);
tuples.push(new MetaTuple(r, values[i + 1]));
}
let c = new CompoundSet(ms, tr, [new MetaTuple(rm1, 'f'), new MetaTuple(rm2, 'n')]);
return [c, m1, m2];
let last: ?NomsSet = null;
while (tuples.length > 1) {
let next = [];
for (let i = 0; i < tuples.length; i += 2) {
last = new CompoundSet(ms, tr, [tuples[i], tuples[i + 1]]);
let r = writeValue(last, tr, ms);
next.push(new MetaTuple(r, tuples[i + 1].value));
}
tuples = next;
}
return notNull(last);
}
test('first/has', async () => {
let [c, m1, m2] = build();
assert.strictEqual('a', await m1.first());
assert.strictEqual('h', await m2.first());
let c = build(['a', 'b', 'e', 'f', 'h', 'i', 'm', 'n']);
assert.strictEqual('a', await c.first());
assert.isTrue(await c.has('a'));
assert.isTrue(await c.has('b'));
assert.isFalse(await c.has('c'));
@@ -82,8 +86,7 @@ suite('CompoundSet', () => {
});
test('forEach', async () => {
let [c] = build();
let c = build(['a', 'b', 'e', 'f', 'h', 'i', 'm', 'n']);
let values = [];
await c.forEach((k) => { values.push(k); });
assert.deepEqual(['a', 'b', 'e', 'f', 'h', 'i', 'm', 'n'], values);
@@ -101,7 +104,7 @@ suite('CompoundSet', () => {
}
test('advanceTo', async () => {
let [c] = build();
let c = build(['a', 'b', 'e', 'f', 'h', 'i', 'm', 'n']);
let cursor = await c.newCursorAt(null);
assert.ok(cursor);
@@ -136,5 +139,26 @@ suite('CompoundSet', () => {
await notNull(cursor).advanceTo('x');
});
});
async function testIntersect(expect: Array<string>, seqs: Array<Array<string>>) {
let first = build(seqs[0]);
let sets:Array<NomsSet> = [];
for (let i = 1; i < seqs.length; i++) {
sets.push(build(seqs[i]));
}
let result = await first.intersect(...sets);
let actual = [];
await result.forEach(v => { actual.push(v); });
assert.deepEqual(expect, actual);
}
test('intersect', async () => {
await testIntersect(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], [['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']]);
await testIntersect(['a', 'h'], [['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], ['a', 'h', 'i', 'j', 'k', 'l', 'm', 'n']]);
await testIntersect(['d', 'e', 'f', 'g', 'h'], [['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], ['d', 'e', 'f', 'g', 'h', 'i', 'j', 'k']]);
await testIntersect(['h'], [['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], ['d', 'e', 'f', 'g', 'h', 'i', 'j', 'k'], ['h', 'i', 'j', 'k', 'l', 'm', 'n', 'o']]);
await testIntersect([], [['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], ['d', 'e', 'f', 'g', 'h', 'i', 'j', 'k'], ['i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']]);
});
});
+10 -7
View File
@@ -29,17 +29,20 @@ export type valueOrPrimitive = Value | primitive;
export function less(v1: any, v2: any): boolean {
invariant(v1 !== null && v1 !== undefined && v2 !== null && v2 !== undefined);
if (v1 instanceof Ref) {
if (typeof v1 === 'object') {
invariant(typeof v2 === 'object');
if (v1 instanceof Value) {
v1 = v1.ref;
}
if (v2 instanceof Value) {
v2 = v2.ref;
}
invariant(v1 instanceof Ref);
invariant(v2 instanceof Ref);
return v1.compare(v2) < 0;
}
if (typeof v1 === 'object') {
invariant(v1.ref instanceof Ref);
invariant(v2.ref instanceof Ref);
return v1.ref.compare(v2.ref) < 0;
}
if (typeof v1 === 'string') {
invariant(typeof v2 === 'string');
return v1 < v2;