Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: compute intersection over indices #438

Merged
merged 8 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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