Skip to content

Commit

Permalink
🚧 progress: Import existing sources from @aureooms/js-itertools.
Browse files Browse the repository at this point in the history
And make tests pass.
  • Loading branch information
make-github-pseudonymous-again committed Apr 26, 2021
1 parent 3e4e69d commit 46fca74
Show file tree
Hide file tree
Showing 9 changed files with 10,396 additions and 8 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,12 @@
"release": "np --message ':hatching_chick: release: Bumping to v%s.'",
"test": "ava"
},
"dependencies": {},
"dependencies": {
"@aureooms/js-collections-deque": "^7.0.0",
"@aureooms/js-itertools": "^5.1.1"
},
"devDependencies": {
"@aureooms/js-compare": "^2.0.1",
"@babel/core": "7.13.15",
"@babel/preset-env": "7.13.15",
"@babel/register": "7.13.14",
Expand Down
35 changes: 35 additions & 0 deletions src/_product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Algorithm used by {@link product} to compute the product of one or more
* iterables from pools of symbols.
*
* @example
* // Ax Ay Bx By Cx Cy Dx Dy
* _product(['xy', 'ABCD'], 0 , 2) ;
*
* @example
* // 000 001 010 011 100 101 110 111
* _product([[0,1],[0,1],[0,1]], 0 , 3) ;
*
* @param {any[][]} pools - The pools of symbols in reverse order.
* @param {number} i - Index of the pool to draw the next symbol from
* @param {number} n - Number of pools in total.
* @returns {IterableIterator}
*/
export default function* _product(pools, i, n) {
if (i === n) {
yield [];
return;
}

const iterable = pools[i];

for (const buffer of _product(pools, i + 1, n)) {
for (const item of iterable) {
buffer.push(item);

yield buffer;

buffer.pop(item);
}
}
}
77 changes: 77 additions & 0 deletions src/diagonal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {deque} from '@aureooms/js-collections-deque';

import {iter, _next, count} from '@aureooms/js-itertools';

/**
* Computes the product of two iterables in a way that allows for one or both
* of them to be infinite (in constrast with {@link product}).
* Although the output iterator may be infinite, it is guaranteed that each
* value of the product is located at some finite position in the output
* iterator.
*
* If one of the two inputs has finite size N, it is guaranteed that memory
* usage never exceeds O(N) values. If both inputs have infinite size, then
* memory usage grows proportionately to the square root of the number of
* output pairs.
*
* @example
* // returns [ [ 0 , 0 ] , [ 0 , 1 ] , [ 1 , 0 ] , [ 1 , 1 ] ]
* list( diagonal( range( 2 ) , range( 2 ) ) ) ;
*
* @param {Iterable} A - The first iterable.
* @param {Iterable} B - The second iterable.
* @returns {IterableIterator}
*
*/
export default function* diagonal(A, B) {
const itA = iter(A);
const itB = iter(B);
const _A = deque();
const _B = deque();

for (const n of count()) {
let _a = _next(itA);
let _b = _next(itB);
if (!_a.done) {
// eslint-disable-next-line no-negated-condition
if (!_b.done) {
_A.append(_a.value);
_B.append(_b.value);
for (let i = 0; i <= n; ++i) yield [_A.get(i), _B.get(n - i)];
} else {
if (_B.length === 0) return;
do {
_A.append(_a.value);
_A.popleft();
for (let i = 0; i < n; ++i) yield [_A.get(i), _B.get(n - i - 1)];
_a = _next(itA);
} while (!_a.done);

break;
}
// eslint-disable-next-line no-negated-condition
} else if (!_b.done) {
if (_A.length === 0) return;
do {
_B.append(_b.value);
_B.popleft();
for (let i = 0; i < n; ++i) yield [_A.get(i), _B.get(n - i - 1)];
_b = _next(itB);
} while (!_b.done);

break;
} else {
break;
}
}

// Yield lower triangle

const n = _A.length;

for (let i = 1; i < n; ++i) {
for (let j = i; j < n; ++j) {
yield [_A.get(j), _B.get(n - j + i - 1)];
}
}
}
5 changes: 3 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
const answer = 42;
export default answer;
export {default as _product} from './_product.js';
export {default as diagonal} from './diagonal.js';
export {default as product} from './product.js';
29 changes: 29 additions & 0 deletions src/product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {list, ncycle, map, reversed} from '@aureooms/js-itertools';

import _product from './_product.js';

/**
* Computes the product of the iterables given as first parameter. The second
* parameter is an integer that tells how many times the list of iterables
* given as input should be concatenated to itself before computing the
* product. This second parameter is optional and its default value is
* <code>1</code>.
*
* @example
* // Ax Ay Bx By Cx Cy Dx Dy
* product(['ABCD', 'xy']) ;
*
* @example
* // 000 001 010 011 100 101 110 111
* product([range(2)], 3) ;
*
* @param {Iterable} iterables - The input iterables.
* @param {Number} repeat - The number of times to cycle through the input iterables.
* @return {Iterator}
*/

export default function product(iterables, repeat = 1) {
const pools = list(ncycle(reversed(map(list, iterables)), repeat));

return map(list, _product(pools, 0, pools.length));
}
5 changes: 0 additions & 5 deletions test/src/api.js

This file was deleted.

219 changes: 219 additions & 0 deletions test/src/diagonal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import test from 'ava';

import {lexicographical, increasing} from '@aureooms/js-compare';

import {
list,
diagonal,
range,
count,
take,
sorted,
} from '@aureooms/js-itertools';

import {product} from '../../src/index.js';

test('diagonal docstring example', (t) => {
const result = list(diagonal(range(2), range(2)));
const expected = [
[0, 0],
[0, 1],
[1, 0],
[1, 1],
];

t.deepEqual(expected, result);
});

test('diagonal finite square', (t) => {
const result = list(diagonal(range(3), range(3)));
const expected = [
[0, 0],
[0, 1],
[1, 0],
[0, 2],
[1, 1],
[2, 0],
[1, 2],
[2, 1],
[2, 2],
];

t.deepEqual(expected, result);
});

test('diagonal finite rectangle (vertical)', (t) => {
const result = list(diagonal(range(4), range(3)));
const expected = [
[0, 0],
[0, 1],
[1, 0],
[0, 2],
[1, 1],
[2, 0],
[1, 2],
[2, 1],
[3, 0],
[2, 2],
[3, 1],
[3, 2],
];

t.deepEqual(expected, result);
});

test('diagonal finite rectangle (horizontal)', (t) => {
const result = list(diagonal(range(3), range(4)));
const expected = [
[0, 0],
[0, 1],
[1, 0],
[0, 2],
[1, 1],
[2, 0],
[0, 3],
[1, 2],
[2, 1],
[1, 3],
[2, 2],
[2, 3],
];

t.deepEqual(expected, result);
});

test('diagonal large but finite', (t) => {
const N = 10;
const M = 10000;
const result = list(take(diagonal(range(M), range(M)), N));
const expected = [
[0, 0],
[0, 1],
[1, 0],
[0, 2],
[1, 1],
[2, 0],
[0, 3],
[1, 2],
[2, 1],
[3, 0],
];

t.deepEqual(expected, result);
});

test('diagonal infinite', (t) => {
const N = 10;
const result = list(take(diagonal(count(), count()), N));
const expected = [
[0, 0],
[0, 1],
[1, 0],
[0, 2],
[1, 1],
[2, 0],
[0, 3],
[1, 2],
[2, 1],
[3, 0],
];

t.deepEqual(expected, result);
});

test('diagonal empty x empty', (t) => {
const result = list(diagonal(range(0), range(0)));
const expected = [];
t.deepEqual(expected, result);
});

test('diagonal empty x finite', (t) => {
const result = list(diagonal(range(0), range(100)));
const expected = [];
t.deepEqual(expected, result);
});

test('diagonal finite x empty', (t) => {
const result = list(diagonal(range(100), range(0)));
const expected = [];
t.deepEqual(expected, result);
});

test('diagonal empty x infinite', (t) => {
const result = list(diagonal(range(0), count()));
const expected = [];
t.deepEqual(expected, result);
});

test('diagonal infinite x empty', (t) => {
const result = list(diagonal(count(), range(0)));
const expected = [];
t.deepEqual(expected, result);
});

test('diagonal finite x infinite', (t) => {
const N = 13;
const result = list(take(diagonal(range(3), count()), N));
const expected = [
[0, 0],
[0, 1],
[1, 0],
[0, 2],
[1, 1],
[2, 0],
[0, 3],
[1, 2],
[2, 1],
[0, 4],
[1, 3],
[2, 2],
[0, 5],
];

t.deepEqual(expected, result);
});

test('diagonal infinite x finite', (t) => {
const N = 13;
const result = list(take(diagonal(count(), range(3)), N));
const expected = [
[0, 0],
[0, 1],
[1, 0],
[0, 2],
[1, 1],
[2, 0],
[1, 2],
[2, 1],
[3, 0],
[2, 2],
[3, 1],
[4, 0],
[3, 2],
];

t.deepEqual(expected, result);
});

const compare = lexicographical(increasing);
const set = (x) => sorted(compare, x);

test('diagonal compared to product (vertical)', (t) => {
const M = 100;
const N = 25;

const expected = list(product([range(M), range(N)]));
const result = set(diagonal(range(M), range(N)));

t.deepEqual(expected, result);
});

test('diagonal compared to product (horizontal)', (t) => {
const M = 25;
const N = 100;

const expected = list(product([range(M), range(N)]));
const result = set(diagonal(range(M), range(N)));

t.deepEqual(expected, result);
});
Loading

0 comments on commit 46fca74

Please sign in to comment.