Skip to content

Commit 807dcad

Browse files
🚧 progress: Import existing sources, test, and code samples.
1 parent ea6b403 commit 807dcad

File tree

10 files changed

+10450
-11
lines changed

10 files changed

+10450
-11
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
Iterable items grouping for JavaScript.
55
See [docs](https://iterable-iterator.github.io/group/index.html).
66

7-
> :building_construction: Caveat emptor! This is work in progress. Code may be
8-
> working. Documentation may be present. Coherence may be. Maybe.
9-
107
> :warning: Depending on your environment, the code may require
118
> `regeneratorRuntime` to be defined, for instance by importing
129
> [regenerator-runtime/runtime](https://www.npmjs.com/package/regenerator-runtime).
1310
11+
```js
12+
import {group} from '@iterable-iterator/group';
13+
import {identity} from '@functional-abstraction/operator';
14+
group( identity , "AAAABBBCCAABB" ) ; // [ A AAAA ] [ B BBB ] [ C CC ] [ A AA ] [ B BB ]
15+
```
16+
1417
[![License](https://img.shields.io/github/license/iterable-iterator/group.svg)](https://raw.githubusercontent.com/iterable-iterator/group/main/LICENSE)
1518
[![Version](https://img.shields.io/npm/v/@iterable-iterator/group.svg)](https://www.npmjs.org/package/@iterable-iterator/group)
1619
[![Tests](https://img.shields.io/github/workflow/status/iterable-iterator/group/ci:test?event=push&label=tests)](https://github.com/iterable-iterator/group/actions/workflows/ci:test.yml?query=branch:main)

package.json

+13-1
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,24 @@
6060
"release": "np --message ':hatching_chick: release: Bumping to v%s.'",
6161
"test": "ava"
6262
},
63-
"dependencies": {},
63+
"dependencies": {
64+
"@iterable-iterator/iter": "^0.0.2",
65+
"@iterable-iterator/range": "^1.0.0"
66+
},
6467
"devDependencies": {
6568
"@babel/core": "7.14.0",
6669
"@babel/preset-env": "7.14.0",
6770
"@babel/register": "7.13.16",
6871
"@commitlint/cli": "12.1.1",
72+
"@functional-abstraction/operator": "^1.0.4",
73+
"@iterable-iterator/count": "^0.0.1",
74+
"@iterable-iterator/cycle": "^0.0.1",
75+
"@iterable-iterator/list": "^0.0.2",
76+
"@iterable-iterator/map": "^0.0.1",
77+
"@iterable-iterator/next": "^1.0.0",
78+
"@iterable-iterator/repeat": "^0.0.1",
79+
"@iterable-iterator/slice": "^0.0.1",
80+
"@iterable-iterator/zip": "^0.0.1",
6981
"@js-library/commitlint-config": "0.0.4",
7082
"ava": "3.15.0",
7183
"babel-plugin-transform-remove-console": "6.9.4",

src/by.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {iter} from '@iterable-iterator/iter';
2+
import {range} from '@iterable-iterator/range';
3+
4+
/**
5+
* Yields elements of the input iterable by grouping them into tuples of a
6+
* given size.
7+
*
8+
* @param {Iterable} iterable - The input iterable.
9+
* @param {Number} n - The size of the yielded tuples.
10+
* @returns {Iterator}
11+
*/
12+
export default function* by(iterable, n) {
13+
const iterator = iter(iterable);
14+
15+
while (true) {
16+
const tuple = [];
17+
18+
for (const i of range(n)) {
19+
const current = iterator.next();
20+
21+
if (current.done) {
22+
if (i === 0) {
23+
return;
24+
}
25+
26+
// eslint-disable-next-line no-unused-vars
27+
for (const j of range(n - i)) {
28+
tuple.push(undefined);
29+
}
30+
31+
yield tuple;
32+
33+
return;
34+
}
35+
36+
tuple.push(current.value);
37+
}
38+
39+
yield tuple;
40+
}
41+
}

src/group.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {iter} from '@iterable-iterator/iter';
2+
3+
/**
4+
* Yields elements of the input iterable by grouping them into tuples
5+
* consecutive elements from the same equivalence class.
6+
*
7+
* @example
8+
* // A B C D A B
9+
* list( map( ( [ k , g ] ) => k , group( x => x , 'AAAABBBCCDAABBB' ) ) )
10+
*
11+
* @example
12+
* // AAAA BBB CC D
13+
* list( map( ( [ k , g ] ) => list( g ) , group( x => x , 'AAAABBBCCD' ) ) )
14+
*
15+
* @param {Function} key - The function used to determine the equivalence class
16+
* of an element.
17+
* @param {Iterable} iterable - The input iterable.
18+
* @returns {Iterator}
19+
*/
20+
export default function* group(key, iterable) {
21+
const iterator = iter(iterable);
22+
23+
const first = iterator.next();
24+
25+
if (first.done) {
26+
return;
27+
}
28+
29+
let currval = first.value;
30+
let currkey = key(currval);
31+
32+
const grouper = function* (tgtkey) {
33+
while (true) {
34+
yield currval;
35+
36+
const event = iterator.next();
37+
if (event.done) {
38+
return;
39+
}
40+
41+
currval = event.value;
42+
currkey = key(currval);
43+
44+
if (currkey !== tgtkey) {
45+
return;
46+
}
47+
}
48+
};
49+
50+
while (true) {
51+
const tgtkey = currkey;
52+
53+
const g = grouper(tgtkey);
54+
55+
yield [tgtkey, g];
56+
57+
while (currkey === tgtkey) {
58+
const event = iterator.next();
59+
if (event.done) {
60+
return;
61+
}
62+
63+
currval = event.value;
64+
currkey = key(currval);
65+
}
66+
}
67+
}

src/groupby.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import group from './group.js';
2+
3+
/**
4+
* Same as {@link group}.
5+
* @function groupby
6+
*/
7+
const groupby = group;
8+
export default groupby;

src/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
const answer = 42;
2-
export default answer;
1+
export {default as by} from './by.js';
2+
export {default as group} from './group.js';
3+
export {default as groupby} from './groupby.js';

test/src/api.js

-5
This file was deleted.

test/src/by.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import test from 'ava';
2+
3+
import {range} from '@iterable-iterator/range';
4+
import {zip} from '@iterable-iterator/zip';
5+
import {list} from '@iterable-iterator/list';
6+
import {count} from '@iterable-iterator/count';
7+
import {head} from '@iterable-iterator/slice';
8+
9+
import {by} from '../../src/index.js';
10+
11+
test('by', (t) => {
12+
const A = by(range(100), 2);
13+
const B = zip(range(0, 100, 2), range(1, 100, 2));
14+
15+
t.deepEqual(list(A), list(B), 'compare to zip output');
16+
17+
const C = by(range(4), 3);
18+
19+
t.deepEqual(
20+
list(C),
21+
[
22+
[0, 1, 2],
23+
[3, undefined, undefined],
24+
],
25+
'n !| N',
26+
);
27+
28+
const D = by('', 3);
29+
30+
t.deepEqual(list(D), [], 'empty');
31+
});
32+
33+
test('by (infinite sequences)', (t) => {
34+
const A = by(count(), 2);
35+
const B = zip(count(0, 2), count(1, 2));
36+
37+
t.deepEqual(
38+
list(head(A, 1000)),
39+
list(head(B, 1000)),
40+
'compare to zip output',
41+
);
42+
});

test/src/group.js

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import test from 'ava';
2+
3+
import {identity} from '@functional-abstraction/operator';
4+
5+
import {range} from '@iterable-iterator/range';
6+
import {map} from '@iterable-iterator/map';
7+
import {repeat} from '@iterable-iterator/repeat';
8+
import {ncycle} from '@iterable-iterator/cycle';
9+
import {next} from '@iterable-iterator/next';
10+
import {list} from '@iterable-iterator/list';
11+
12+
import {group, groupby} from '../../src/index.js';
13+
14+
test('groupby is group', (t) => {
15+
t.is(groupby, group);
16+
});
17+
18+
test('group', (t) => {
19+
const x = (key, iterable, expected) => {
20+
t.deepEqual(
21+
list(map(([k, g]) => [k, list(g)], group(key, iterable))),
22+
expected,
23+
);
24+
};
25+
26+
x(identity, '', []);
27+
28+
x(identity, 'AAAAAABBBBCCCCAABBCC', [
29+
['A', ['A', 'A', 'A', 'A', 'A', 'A']],
30+
['B', ['B', 'B', 'B', 'B']],
31+
['C', ['C', 'C', 'C', 'C']],
32+
['A', ['A', 'A']],
33+
['B', ['B', 'B']],
34+
['C', ['C', 'C']],
35+
]);
36+
37+
x(
38+
(item) => {
39+
return item.charCodeAt(0) - 65;
40+
},
41+
'AAAAAABBBBCCCCAABBCC',
42+
[
43+
[0, ['A', 'A', 'A', 'A', 'A', 'A']],
44+
[1, ['B', 'B', 'B', 'B']],
45+
[2, ['C', 'C', 'C', 'C']],
46+
[0, ['A', 'A']],
47+
[1, ['B', 'B']],
48+
[2, ['C', 'C']],
49+
],
50+
);
51+
52+
x(
53+
(item) => {
54+
return Math.floor((item.charCodeAt(0) - 65) / 2);
55+
},
56+
'AAAAAABBBBCCCCAABBCC',
57+
[
58+
[0, ['A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B']],
59+
[1, ['C', 'C', 'C', 'C']],
60+
[0, ['A', 'A', 'B', 'B']],
61+
[1, ['C', 'C']],
62+
],
63+
);
64+
});
65+
66+
test('group keys', (t) => {
67+
const x = (key, iterable, expected) => {
68+
t.deepEqual(list(map(([k, _]) => k, group(key, iterable))), expected);
69+
};
70+
71+
x(identity, '', []);
72+
73+
x(identity, 'AAAAAABBBBCCCCAABBCC', list('ABCABC'));
74+
75+
x(
76+
(item) => {
77+
return item.charCodeAt(0) - 65;
78+
},
79+
'AAAAAABBBBCCCCAABBCC',
80+
list(ncycle(range(3), 2)),
81+
);
82+
83+
x(
84+
(item) => {
85+
return Math.floor((item.charCodeAt(0) - 65) / 2);
86+
},
87+
'AAAAAABBBBCCCCAABBCC',
88+
list(ncycle(range(2), 2)),
89+
);
90+
});
91+
92+
test('group for infinite sequence of something', (t) => {
93+
const v = Math.random();
94+
95+
const [k, g] = next(group(identity, repeat(v)));
96+
97+
t.deepEqual(k, v);
98+
99+
// eslint-disable-next-line no-unused-vars
100+
for (const _ of range(1000)) {
101+
t.deepEqual(next(g), v);
102+
}
103+
});

0 commit comments

Comments
 (0)