Skip to content

Commit

Permalink
adopt interning for set operations (#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Sep 19, 2021
1 parent 61cc940 commit d05672a
Show file tree
Hide file tree
Showing 15 changed files with 83 additions and 40 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -765,23 +765,23 @@ This methods implement basic set operations for any iterable.

<a name="difference" href="#difference">#</a> d3.<b>difference</b>(<i>iterable</i>, ...<i>others</i>) · [Source](https://github.com/d3/d3-array/blob/master/src/difference.js)

Returns a new Set containing every value in *iterable* that is not in any of the *others* iterables.
Returns a new InternSet containing every value in *iterable* that is not in any of the *others* iterables.

```js
d3.difference([0, 1, 2, 0], [1]) // Set {0, 2}
```

<a name="union" href="#union">#</a> d3.<b>union</b>(...<i>iterables</i>) · [Source](https://github.com/d3/d3-array/blob/master/src/union.js)

Returns a new Set containing every (distinct) value that appears in any of the given *iterables*. The order of values in the returned Set is based on their first occurrence in the given *iterables*.
Returns a new InternSet containing every (distinct) value that appears in any of the given *iterables*. The order of values in the returned set is based on their first occurrence in the given *iterables*.

```js
d3.union([0, 2, 1, 0], [1, 3]) // Set {0, 2, 1, 3}
```

<a name="intersection" href="#intersection">#</a> d3.<b>intersection</b>(...<i>iterables</i>) · [Source](https://github.com/d3/d3-array/blob/master/src/intersection.js)

Returns a new Set containing every (distinct) value that appears in all of the given *iterables*. The order of values in the returned Set is based on their first occurrence in the given *iterables*.
Returns a new InternSet containing every (distinct) value that appears in all of the given *iterables*. The order of values in the returned set is based on their first occurrence in the given *iterables*.

```js
d3.intersection([0, 2, 1, 0], [1, 3]) // Set {1}
Expand Down
4 changes: 3 additions & 1 deletion src/difference.js
@@ -1,5 +1,7 @@
import {InternSet} from "internmap";

export default function difference(values, ...others) {
values = new Set(values);
values = new InternSet(values);
for (const other of others) {
for (const value of other) {
values.delete(value);
Expand Down
4 changes: 3 additions & 1 deletion src/disjoint.js
@@ -1,5 +1,7 @@
import {InternSet} from "internmap";

export default function disjoint(values, other) {
const iterator = other[Symbol.iterator](), set = new Set();
const iterator = other[Symbol.iterator](), set = new InternSet();
for (const v of values) {
if (set.has(v)) return false;
let value, done;
Expand Down
8 changes: 6 additions & 2 deletions src/intersection.js
@@ -1,7 +1,7 @@
import set from "./set.js";
import {InternSet} from "internmap";

export default function intersection(values, ...others) {
values = new Set(values);
values = new InternSet(values);
others = others.map(set);
out: for (const value of values) {
for (const other of others) {
Expand All @@ -13,3 +13,7 @@ export default function intersection(values, ...others) {
}
return values;
}

function set(values) {
return values instanceof InternSet ? values : new InternSet(values);
}
3 changes: 0 additions & 3 deletions src/set.js

This file was deleted.

12 changes: 9 additions & 3 deletions src/superset.js
@@ -1,13 +1,19 @@
export default function superset(values, other) {
const iterator = values[Symbol.iterator](), set = new Set();
for (const o of other) {
if (set.has(o)) continue;
const io = intern(o);
if (set.has(io)) continue;
let value, done;
while (({value, done} = iterator.next())) {
if (done) return false;
set.add(value);
if (Object.is(o, value)) break;
const ivalue = intern(value);
set.add(ivalue);
if (Object.is(io, ivalue)) break;
}
}
return true;
}

function intern(value) {
return value !== null && typeof value === "object" ? value.valueOf() : value;
}
4 changes: 3 additions & 1 deletion src/union.js
@@ -1,5 +1,7 @@
import {InternSet} from "internmap";

export default function union(...others) {
const set = new Set();
const set = new InternSet();
for (const other of others) {
for (const o of other) {
set.add(o);
Expand Down
15 changes: 6 additions & 9 deletions test/asserts.js
@@ -1,12 +1,9 @@
import assert from "assert";
import {InternSet} from "internmap";

export function assertSetEqual(A, B) {
assert(setEqual(A, B));
}

function setEqual(A, B) {
if (!(A instanceof Set)) throw new Error("not a set");
for (const a of A) if (!B.has(a)) return false;
for (const b of B) if (!A.has(b)) return false;
return true;
export function assertSetEqual(actual, expected) {
assert(actual instanceof Set);
expected = new InternSet(expected);
for (const a of actual) assert(expected.has(a), `unexpected ${a}`);
for (const e of expected) assert(actual.has(e), `expected ${e}`);
}
14 changes: 10 additions & 4 deletions test/difference-test.js
Expand Up @@ -2,11 +2,17 @@ import {difference} from "../src/index.js";
import {assertSetEqual} from "./asserts.js";

it("difference(values, other) returns a set of values", () => {
assertSetEqual(difference([1, 2, 3], [2, 1]), new Set([3]));
assertSetEqual(difference([1, 2], [2, 3, 1]), new Set([]));
assertSetEqual(difference([2, 1, 3], [4, 3, 1]), new Set([2]));
assertSetEqual(difference([1, 2, 3], [2, 1]), [3]);
assertSetEqual(difference([1, 2], [2, 3, 1]), []);
assertSetEqual(difference([2, 1, 3], [4, 3, 1]), [2]);
});

it("difference(...values) accepts iterables", () => {
assertSetEqual(difference(new Set([1, 2, 3]), new Set([1])), new Set([2, 3]));
assertSetEqual(difference(new Set([1, 2, 3]), new Set([1])), [2, 3]);
});

it("difference(values, other) performs interning", () => {
assertSetEqual(difference([new Date("2021-01-01"), new Date("2021-01-02"), new Date("2021-01-03")], [new Date("2021-01-02"), new Date("2021-01-01")]), [new Date("2021-01-03")]);
assertSetEqual(difference([new Date("2021-01-01"), new Date("2021-01-02")], [new Date("2021-01-02"), new Date("2021-01-03"), new Date("2021-01-01")]), []);
assertSetEqual(difference([new Date("2021-01-02"), new Date("2021-01-01"), new Date("2021-01-03")], [new Date("2021-01-04"), new Date("2021-01-03"), new Date("2021-01-01")]), [new Date("2021-01-02")]);
});
6 changes: 6 additions & 0 deletions test/disjoint-test.js
Expand Up @@ -15,6 +15,12 @@ it("disjoint(values, other) allows other to be infinite", () => {
assert.strictEqual(disjoint([2], repeat(1, 3, 2)), false);
});

it("disjoint(values, other) performs interning", () => {
assert.strictEqual(disjoint([new Date("2021-01-01")], [new Date("2021-01-02")]), true);
assert.strictEqual(disjoint([new Date("2021-01-02"), new Date("2021-01-03")], [new Date("2021-01-03"), new Date("2021-01-04")]), false);
assert.strictEqual(disjoint([new Date("2021-01-01")], []), true);
});

function* odds() {
for (let i = 1; true; i += 2) {
yield i;
Expand Down
14 changes: 9 additions & 5 deletions test/intersection-test.js
Expand Up @@ -2,18 +2,22 @@ import {intersection} from "../src/index.js";
import {assertSetEqual} from "./asserts.js";

it("intersection(values) returns a set of values", () => {
assertSetEqual(intersection([1, 2, 3, 2, 1]), new Set([1, 2, 3]));
assertSetEqual(intersection([1, 2, 3, 2, 1]), [1, 2, 3]);
});

it("intersection(values, other) returns a set of values", () => {
assertSetEqual(intersection([1, 2], [2, 3, 1]), new Set([1, 2]));
assertSetEqual(intersection([2, 1, 3], [4, 3, 1]), new Set([1, 3]));
assertSetEqual(intersection([1, 2], [2, 3, 1]), [1, 2]);
assertSetEqual(intersection([2, 1, 3], [4, 3, 1]), [1, 3]);
});

it("intersection(...values) returns a set of values", () => {
assertSetEqual(intersection([1, 2], [2, 1], [2, 3]), new Set([2]));
assertSetEqual(intersection([1, 2], [2, 1], [2, 3]), [2]);
});

it("intersection(...values) accepts iterables", () => {
assertSetEqual(intersection(new Set([1, 2, 3])), new Set([1, 2, 3]));
assertSetEqual(intersection(new Set([1, 2, 3])), [1, 2, 3]);
});

it("intersection(...values) performs interning", () => {
assertSetEqual(intersection([new Date("2021-01-01"), new Date("2021-01-03")], [new Date("2021-01-01"), new Date("2021-01-02")]), [new Date("2021-01-01")]);
});
6 changes: 6 additions & 0 deletions test/subset-test.js
Expand Up @@ -6,3 +6,9 @@ it("subset(values, other) returns true if values is a subset of others", () => {
assert.strictEqual(subset([3, 4], [2, 3]), false);
assert.strictEqual(subset([], [1]), true);
});

it("subset(values, other) performs interning", () => {
assert.strictEqual(subset([new Date("2021-01-02")], [new Date("2021-01-01"), new Date("2021-01-02")]), true);
assert.strictEqual(subset([new Date("2021-01-03"), new Date("2021-01-04")], [new Date("2021-01-02"), new Date("2021-01-03")]), false);
assert.strictEqual(subset([], [new Date("2021-01-01")]), true);
});
6 changes: 6 additions & 0 deletions test/superset-test.js
Expand Up @@ -15,6 +15,12 @@ it("superset(values, other) allows other to be infinite", () => {
assert.strictEqual(superset([1, 3, 5], repeat(1, 3, 2)), false);
});

it("superset(values, other) performs interning", () => {
assert.strictEqual(superset([new Date("2021-01-01"), new Date("2021-01-02")], [new Date("2021-01-02")]), true);
assert.strictEqual(superset([new Date("2021-01-02"), new Date("2021-01-03")], [new Date("2021-01-03"), new Date("2021-01-04")]), false);
assert.strictEqual(superset([new Date("2021-01-01")], []), true);
});

function* odds() {
for (let i = 1; true; i += 2) {
yield i;
Expand Down
15 changes: 10 additions & 5 deletions test/union-test.js
Expand Up @@ -2,18 +2,23 @@ import {union} from "../src/index.js";
import {assertSetEqual} from "./asserts.js";

it("union(values) returns a set of values", () => {
assertSetEqual(union([1, 2, 3, 2, 1]), new Set([1, 2, 3]));
assertSetEqual(union([1, 2, 3, 2, 1]), [1, 2, 3]);
});

it("union(values, other) returns a set of values", () => {
assertSetEqual(union([1, 2], [2, 3, 1]), new Set([1, 2, 3]));
assertSetEqual(union([1, 2], [2, 3, 1]), [1, 2, 3]);
});

it("union(...values) returns a set of values", () => {
assertSetEqual(union([1], [2], [2, 3], [1]), new Set([1, 2, 3]));
assertSetEqual(union([1], [2], [2, 3], [1]), [1, 2, 3]);
});

it("union(...values) accepts iterables", () => {
assertSetEqual(union(new Set([1, 2, 3])), new Set([1, 2, 3]));
assertSetEqual(union(Uint8Array.of(1, 2, 3)), new Set([1, 2, 3]));
assertSetEqual(union(new Set([1, 2, 3])), [1, 2, 3]);
assertSetEqual(union(Uint8Array.of(1, 2, 3)), [1, 2, 3]);
});

it("union(...values) performs interning", () => {
assertSetEqual(union([new Date("2021-01-01"), new Date("2021-01-01"), new Date("2021-01-02")]), [new Date("2021-01-01"), new Date("2021-01-02")]);
assertSetEqual(union([new Date("2021-01-01"), new Date("2021-01-03")], [new Date("2021-01-01"), new Date("2021-01-02")]), [new Date("2021-01-01"), new Date("2021-01-02"), new Date("2021-01-03")]);
});
6 changes: 3 additions & 3 deletions yarn.lock
Expand Up @@ -824,9 +824,9 @@ inherits@2:
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==

"internmap@1 - 2":
version "2.0.1"
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.1.tgz#33d0fa016185397549fb1a14ea3dbe5a2949d1cd"
integrity sha512-Ujwccrj9FkGqjbY3iVoxD1VV+KdZZeENx0rphrtzmRXbFvkFO88L80BL/zeSIguX/7T+y8k04xqtgWgS5vxwxw==
version "2.0.2"
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.2.tgz#3efa1165209cc56133df1400df9c34a73e0dad93"
integrity sha512-6O4dJQZN4+83kg9agi21fbasiAn7V2JRvLv29/YT1Kz8f+ngakB1hMG+AP0mYquLOtjWhNO8CvKhhXT/7Tla/g==

is-binary-path@~2.1.0:
version "2.1.0"
Expand Down

0 comments on commit d05672a

Please sign in to comment.