Skip to content

Commit fb9d091

Browse files
authored
Merge pull request #2 from N8Brooks/alternate_parameters
Alternate parameters
2 parents 558333c + 22d5118 commit fb9d091

18 files changed

+550
-456
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ jobs:
3636
run: deno lint
3737

3838
- name: Test
39-
run: deno test --doc --unstable
39+
# run: deno test --doc
40+
run: deno test
4041

4142
- name: Benchmark
4243
run: deno run _benchmark.ts

README.md

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ inspired by the combinatorial iterators provided by the
2020

2121
## Usage
2222

23-
### function combinations(r: number, iterable: Iterable<T>): Generator<T[]>
23+
### combinations
2424

2525
Yields `r` length `Arrays` from the input `iterable`. Order of selection does
2626
not matter and elements are chosen without replacement.
@@ -29,7 +29,7 @@ not matter and elements are chosen without replacement.
2929
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
3030
import { combinations } from "https://deno.land/x/combinatorics/mod.ts";
3131

32-
const sequences = [...combinations(2, [1, 2, 3, 4])];
32+
const sequences = [...combinations([1, 2, 3, 4], 2)];
3333

3434
assertEquals(sequences, [
3535
[1, 2],
@@ -41,7 +41,7 @@ assertEquals(sequences, [
4141
]);
4242
```
4343

44-
### function permutations(r: number | undefined, iterable: Iterable<T>): Generator<T[]>
44+
### permutations
4545

4646
Yields `r` length `Arrays` from the input `iterable`. Order of selection is
4747
important and elements are chosen without replacement. If `r` is undefined, then
@@ -52,7 +52,7 @@ the length of the `iterable` is used.
5252
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
5353
import { permutations } from "https://deno.land/x/combinatorics/mod.ts";
5454

55-
const sequences = [...permutations(2, [1, 2, 3, 4])];
55+
const sequences = [...permutations([1, 2, 3, 4], 2)];
5656

5757
assertEquals(sequences, [
5858
[1, 2], [1, 3], [1, 4],
@@ -62,7 +62,7 @@ assertEquals(sequences, [
6262
]);
6363
```
6464

65-
### function combinationsWithReplacement(r: number, iterable: Iterable<T>): Generator<T[]>
65+
### combinationsWithReplacement
6666

6767
Yields `r` length `Arrays` from the input `iterable`. Order of selection is not
6868
important and elements are chosen with replacement.
@@ -71,7 +71,7 @@ important and elements are chosen with replacement.
7171
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
7272
import { combinationsWithReplacement } from "https://deno.land/x/combinatorics/mod.ts";
7373

74-
const sequences = [...combinationsWithReplacement(2, [1, 2, 3, 4])];
74+
const sequences = [...combinationsWithReplacement([1, 2, 3, 4], 2)];
7575

7676
assertEquals(sequences, [
7777
[1, 1],
@@ -87,21 +87,17 @@ assertEquals(sequences, [
8787
]);
8888
```
8989

90-
### function product(r: number, ...iterables: Iterable<T>[]): Generator<T[]>
90+
### permutationsWithReplacement
9191

92-
Yields `r * iterables.length` length `Arrays` from the input `iterables`
93-
repeated `r` times. Order of selection is important and elements are chosen with
94-
replacement.
95-
96-
When `iterables.length === 1` the output is equivalent to the permutations with
97-
replacement of `iterables[0]` with the given `r`.
92+
Yields `r` length `Arrays` from the input `iterable`. Order of selection is
93+
important and elements are chosen with replacement.
9894

9995
<!-- deno-fmt-ignore -->
10096
```ts
10197
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
102-
import { product } from "https://deno.land/x/combinatorics/mod.ts";
98+
import { permutationsWithReplacement } from "https://deno.land/x/combinatorics/mod.ts";
10399

104-
const sequences = [...product(2, [1, 2, 3, 4])];
100+
const sequences = [...permutationsWithReplacement(2, [1, 2, 3, 4])];
105101

106102
assertEquals(sequences, [
107103
[1, 1], [1, 2], [1, 3], [1, 4],
@@ -111,17 +107,17 @@ assertEquals(sequences, [
111107
]);
112108
```
113109

114-
When `iterables.length > 1` the output is equivalent to the cartesian product of
115-
the `iterables` repeated `r` times. This can also be explained as running nested
116-
`for...of` loops using one of the inputs to provide the element at each index
117-
for the yielded `Array`.
110+
### cartesianProduct
111+
112+
Roughly equivalent to running nested `for...of` loops using one of the inputs to
113+
provide the element at each index for the yielded `Array`.
118114

119115
<!-- deno-fmt-ignore -->
120116
```ts
121117
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
122-
import { product } from "https://deno.land/x/combinatorics/mod.ts";
118+
import { cartesianProduct } from "https://deno.land/x/combinatorics/mod.ts";
123119

124-
const sequences = [...product(1, [1, 2, 3], [4, 5, 6], [7, 8, 9])];
120+
const sequences = [...cartesianProduct([1, 2, 3], [4, 5, 6], [7, 8, 9])];
125121

126122
assertEquals(sequences, [
127123
[1, 4, 7], [1, 4, 8], [1, 4, 9],
@@ -136,7 +132,7 @@ assertEquals(sequences, [
136132
]);
137133
```
138134

139-
### function powerSet(iterable: Iterable<T>): Generator<T[]>
135+
### powerSet
140136

141137
The set of all subsets of the given `iterable`. Equivalent to running
142138
`combinations` with `0 <= r <= iterable.length` and flattening the results. The

_benchmark.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ import {
66
import { combinations } from "./combinations.ts";
77
import { combinationsWithReplacement } from "./combinations_with_replacement.ts";
88
import { permutations } from "./permutations.ts";
9-
import { product } from "./product.ts";
9+
import { permutationsWithReplacement } from "./permutations_with_replacement.ts";
10+
import { cartesianProduct } from "./cartesian_product.ts";
1011
import { powerSet } from "./power_set.ts";
1112

1213
bench({
1314
name: "combinations",
1415
runs: 3,
1516
func(benchmarkTimer: BenchmarkTimer): void {
1617
benchmarkTimer.start();
17-
for (const _ of combinations(12, Array(29)));
18+
for (const _ of combinations(Array(29), 12));
1819
benchmarkTimer.stop();
1920
},
2021
});
@@ -24,7 +25,7 @@ bench({
2425
runs: 3,
2526
func(benchmarkTimer: BenchmarkTimer): void {
2627
benchmarkTimer.start();
27-
for (const _ of combinationsWithReplacement(12, Array(18)));
28+
for (const _ of combinationsWithReplacement(Array(18), 12));
2829
benchmarkTimer.stop();
2930
},
3031
});
@@ -34,17 +35,27 @@ bench({
3435
runs: 3,
3536
func(benchmarkTimer: BenchmarkTimer): void {
3637
benchmarkTimer.start();
37-
for (const _ of permutations(11, Array(11)));
38+
for (const _ of permutations(Array(11), 11));
3839
benchmarkTimer.stop();
3940
},
4041
});
4142

4243
bench({
43-
name: "product",
44+
name: "permutationsWithReplacement",
4445
runs: 3,
4546
func(benchmarkTimer: BenchmarkTimer): void {
4647
benchmarkTimer.start();
47-
for (const _ of product(7, Array(13)));
48+
for (const _ of permutationsWithReplacement(Array(13), 7));
49+
benchmarkTimer.stop();
50+
},
51+
});
52+
53+
bench({
54+
name: "cartesianProduct",
55+
runs: 3,
56+
func(benchmarkTimer: BenchmarkTimer): void {
57+
benchmarkTimer.start();
58+
for (const _ of cartesianProduct(...Array(7).fill(Array(13))));
4859
benchmarkTimer.stop();
4960
},
5061
});

cartesian_product.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// This module is browser compatible.
2+
3+
/**
4+
* Roughly equivalent to running nested `for...of` loops using one of the inputs to
5+
* provide the element at each index for the yielded `Array`.
6+
*
7+
* ```ts
8+
* import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
9+
* import { cartesianProduct } from "https://deno.land/x/combinatorics/mod.ts";
10+
*
11+
* const sequences = [...cartesianProduct([1, 2, 3], [4, 5, 6], [7, 8, 9])];
12+
*
13+
* assertEquals(sequences, [
14+
* [1, 4, 7], [1, 4, 8], [1, 4, 9],
15+
* [1, 5, 7], [1, 5, 8], [1, 5, 9],
16+
* [1, 6, 7], [1, 6, 8], [1, 6, 9],
17+
* [2, 4, 7], [2, 4, 8], [2, 4, 9],
18+
* [2, 5, 7], [2, 5, 8], [2, 5, 9],
19+
* [2, 6, 7], [2, 6, 8], [2, 6, 9],
20+
* [3, 4, 7], [3, 4, 8], [3, 4, 9],
21+
* [3, 5, 7], [3, 5, 8], [3, 5, 9],
22+
* [3, 6, 7], [3, 6, 8], [3, 6, 9],
23+
* ]);
24+
* ```
25+
*/
26+
export function* cartesianProduct<T>(
27+
...iterables: Iterable<T>[]
28+
): Generator<T[]> {
29+
const pools = iterables.map((iterable) => [...iterable]);
30+
const n = pools.length;
31+
if (n === 0) {
32+
yield [];
33+
return;
34+
}
35+
if (pools.some((pool) => pool.length === 0)) {
36+
return;
37+
}
38+
const indices = new Uint32Array(n);
39+
yield pools.map((pool) => pool[0]);
40+
while (true) {
41+
let i: number;
42+
loop: {
43+
for (i = n - 1; i >= 0; i--) {
44+
if (indices[i] === pools[i].length - 1) {
45+
continue;
46+
}
47+
const result: T[] = Array(n);
48+
for (let j = 0; j < i; j++) {
49+
result[j] = pools[j][indices[j]];
50+
}
51+
const index = indices[i] += 1;
52+
result[i] = pools[i][index];
53+
for (let j = i + 1; j < n; j++) {
54+
indices[j] = 0;
55+
result[j] = pools[j][0];
56+
}
57+
yield result;
58+
break loop;
59+
}
60+
return;
61+
}
62+
}
63+
}

cartesian_product_test.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import {
2+
assertEquals,
3+
assertStrictEquals,
4+
} from "https://deno.land/std@0.113.0/testing/asserts.ts";
5+
import { cartesianProduct } from "./cartesian_product.ts";
6+
import { range } from "./_util.ts";
7+
8+
Deno.test("no iterables", () => {
9+
const actual = [...cartesianProduct()];
10+
assertEquals(actual, [[]]);
11+
});
12+
13+
Deno.test("one iterable", () => {
14+
const actual = [...cartesianProduct("abc")];
15+
const expected = [["a"], ["b"], ["c"]];
16+
assertEquals(actual, expected);
17+
});
18+
19+
Deno.test("iterables of varying length", () => {
20+
const actual = [...cartesianProduct([1], [2, 3, 4], [5, 6])];
21+
const expected = [
22+
[1, 2, 5],
23+
[1, 2, 6],
24+
[1, 3, 5],
25+
[1, 3, 6],
26+
[1, 4, 5],
27+
[1, 4, 6],
28+
];
29+
assertEquals(actual, expected);
30+
});
31+
32+
Deno.test("iterables with empty", () => {
33+
const actual = [...cartesianProduct([1, 2, 3], [], [4, 5, 6])];
34+
assertEquals(actual, []);
35+
});
36+
37+
Deno.test("r = 65_537", () => {
38+
const actual = [...cartesianProduct(range(65_537))];
39+
const expected = Array(65_537).fill(undefined).map((_, i) => [i]);
40+
assertEquals(actual, expected);
41+
});
42+
43+
test(cartesianProductLength);
44+
45+
test(cartesianProductContent);
46+
47+
/** Calls a function with varying `r`s and `iterables`. */
48+
function test(
49+
func: typeof cartesianProductLength | typeof cartesianProductContent,
50+
): void {
51+
for (let n0 = 0; n0 < 8; n0++) {
52+
func(n0);
53+
}
54+
55+
for (let n0 = 0; n0 < 8; n0++) {
56+
for (let n1 = 0; n1 < 8; n1++) {
57+
func(n0, n1);
58+
}
59+
}
60+
61+
for (let n0 = 0; n0 < 4; n0++) {
62+
for (let n1 = 0; n1 < 4; n1++) {
63+
for (let n2 = 0; n2 < 4; n2++) {
64+
func(n0, n1, n2);
65+
}
66+
}
67+
}
68+
69+
for (let n0 = 0; n0 < 4; n0++) {
70+
for (let n1 = 0; n1 < 4; n1++) {
71+
for (let n2 = 0; n2 < 4; n2++) {
72+
for (let n3 = 0; n3 < 4; n3++) {
73+
func(n0, n1, n2, n3);
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
/** Tests `product` for length against `prod`. */
81+
function cartesianProductLength(...ns: number[]) {
82+
const iterables = getIterables(...ns);
83+
Deno.test(`prod(${ns.join(", ")})`, () => {
84+
const actual = [...cartesianProduct(...iterables)];
85+
const expectedLength = prod(...ns);
86+
assertStrictEquals(actual.length, expectedLength);
87+
});
88+
}
89+
90+
/** Tests `product` for content against `cartesianProduct1`. */
91+
function cartesianProductContent(...ns: number[]) {
92+
const iterables = getIterables(...ns);
93+
const restArgs = JSON.stringify(iterables).slice(1, -1);
94+
Deno.test(`cartesianProduct1(${restArgs})`, () => {
95+
const actual = [...cartesianProduct(...iterables)];
96+
const expected1 = [...cartesianProduct1(...iterables)];
97+
assertEquals(actual, expected1);
98+
});
99+
}
100+
101+
/** Creates arrays of the given lengths containing unique numbers. */
102+
function getIterables(...ns: number[]): number[][] {
103+
let i = 0;
104+
return ns.map((n) => Array.from({ length: n }, () => i++));
105+
}
106+
107+
/** Calculate the product of all the `ns` repeated `r` times. */
108+
function prod(...ns: number[]): number {
109+
let product = 1;
110+
for (const n of ns) {
111+
product *= n;
112+
}
113+
return product;
114+
}
115+
116+
/** Equivalent to `product` for testing. */
117+
function* cartesianProduct1<T>(
118+
...iterables: Iterable<T>[]
119+
): Generator<T[]> {
120+
const pools = iterables.map((iterable) => [...iterable]);
121+
let result: T[][] = [[]];
122+
for (const pool of pools) {
123+
result = result.map((x) => pool.map((y) => [...x, y])).flat();
124+
}
125+
for (const prod of result) {
126+
yield prod;
127+
}
128+
}

combinations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
99
* import { combinations } from "https://deno.land/x/combinatorics/mod.ts";
1010
*
11-
* const sequences = [...combinations(2, [1, 2, 3, 4])];
11+
* const sequences = [...combinations([1, 2, 3, 4], 2)];
1212
*
1313
* assertEquals(sequences, [
1414
* [1, 2],
@@ -21,8 +21,8 @@
2121
* ```
2222
*/
2323
export function* combinations<T>(
24-
r: number,
2524
iterable: Iterable<T>,
25+
r: number,
2626
): Generator<T[]> {
2727
if (r < 0 || !Number.isInteger(r)) {
2828
throw RangeError("r must be a non-negative integer");

0 commit comments

Comments
 (0)