mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-26 19:46:39 -05:00
Merge pull request #746 from rafael-atticlabs/setIntersect
NomsSet.intersect
This commit is contained in:
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user