Skip to content

Commit

Permalink
perf: compute intersection over indices (#438)
Browse files Browse the repository at this point in the history
* perf: compute intersection over indices

* chore: dont use ||= syntax

* chore: fix lint errors

* fix: dont set _graphs = false

* chore: explicitly add truthiness test for graphs

* chore: add docs

* chore: fix lint errors

* perf: remove unecessary lookups
  • Loading branch information
jeswr authored Sep 8, 2024
1 parent f71ef64 commit 9eea4b6
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 1 deletion.
39 changes: 39 additions & 0 deletions src/N3Store.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,34 @@ function merge(target, source, depth = 4) {
return target;
}

/**
* Determines the intersection of the `_graphs` index s1 and s2.
* s1 and s2 *must* belong to Stores that share an `_entityIndex`.
*
* False is returned when there is no intersection; this should
* *not* be set as the value for an index.
*/
function intersect(s1, s2, depth = 4) {
let target = false;

for (const key in s1) {
if (key in s2) {
const intersection = depth === 0 ? null : intersect(s1[key], s2[key], depth - 1);
if (intersection !== false) {
target = target || Object.create(null);
target[key] = intersection;
}
// Depth 3 is the 'subjects', 'predicates' and 'objects' keys.
// If the 'subjects' index is empty, so will the 'predicates' and 'objects' index.
else if (depth === 3) {
return false;
}
}
}

return target;
}

// ## Constructor
export class N3EntityIndex {
constructor(options = {}) {
Expand Down Expand Up @@ -901,7 +929,18 @@ export default class N3Store {
const store = new N3Store({ entityIndex: this._entityIndex });
store._graphs = merge(Object.create(null), this._graphs);
store._size = this._size;
return store;
}
else if ((other instanceof N3Store) && this._entityIndex === other._entityIndex) {
const store = new N3Store({ entityIndex: this._entityIndex });
const graphs = intersect(other._graphs, this._graphs);
if (graphs) {
store._graphs = graphs;
store._size = null;
}
return store;
}

return this.filter(quad => other.has(quad));
}

Expand Down
35 changes: 34 additions & 1 deletion test/N3Store-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2030,7 +2030,7 @@ describe('Store', () => {
const matrix = [true, false, 'instantiated'].flatMap(match => [true, false].map(share => [match, share]));

describe.each(matrix)('RDF/JS Dataset Methods [DatasetCoreAndReadableStream: %s] [sharedIndex: %s]', (match, shareIndex) => {
let q, store, store1, store2, store3, storeg, storeb, empty, options;
let q, store, store1, store2, store3, store4, storeg, storeb, empty, options;

beforeEach(() => {
options = shareIndex ? { entityIndex: new EntityIndex() } : {};
Expand All @@ -2046,6 +2046,7 @@ describe('Store', () => {
store1 = new Store([q[0], q[1]], options);
store2 = new Store([q[0], q[2]], options);
store3 = new Store([q[0], q[3]], options);
store4 = new Store([new Quad(new NamedNode('a'), new NamedNode('b'), new NamedNode('c'))], options);

if (match) {
empty = store2.match(new NamedNode('sn'));
Expand Down Expand Up @@ -2137,8 +2138,40 @@ describe('Store', () => {
expect(store2.size).toEqual(2);
expect(store.size).toEqual(1);

const stores = [store, store1, store2, store3, store4, storeb, storeg, empty];
for (const s1 of stores) {
for (const s2 of stores) {
expect(s1.intersection(s2).size).toBeLessThanOrEqual(s1.size);
expect(s1.intersection(s2).size).toBeLessThanOrEqual(s2.size);
expect(s1.intersection(s2)._graphs).toBeTruthy();
expect(s1.intersection(s2).equals(s2.intersection(s1)));
expect(s1.union(s2).intersection(s1).equals(s1));
expect(s1.intersection(s2).union(s1).equals(s1));
expect(new Store([...s1.union(s2).intersection(s1)]).equals(new Store([...s1])));
expect(new Store([...s1.intersection(s2).union(s1)]).equals(new Store([...s2])));

const newStore = s1.intersection(s2);
const size = newStore.size;
newStore.add(new Quad(new NamedNode('mys1'), new NamedNode('myp1'), new NamedNode('myo1')));
expect(newStore.size).toBe(size + 1);
}
}

expect(store.intersection(store).size).toEqual(1);
expect(store2.intersection(store2).size).toEqual(2);
expect(storeg.intersection(store).size).toBe(0);
expect(store.intersection(storeg).size).toBe(0);
expect(storeg.intersection(storeb).size).toBe(1);
expect(store.intersection(storeb).size).toBe(1);
expect(store.intersection(store1).size).toBe(1);
expect(store.intersection(store3).size).toBe(1);
expect(store.intersection(store2).size).toBe(1);
expect(empty.intersection(store1).size).toBe(0);
expect(empty.intersection(store2).size).toBe(0);
expect(store2.intersection(store1).size).toBe(1);
expect(store1.intersection(store2).size).toBe(1);
expect(store1.intersection(storeb).size).toBe(1);
expect(storeb.intersection(store1).size).toBe(1);
});
});

Expand Down

0 comments on commit 9eea4b6

Please sign in to comment.