Skip to content

Commit d60ab70

Browse files
committed
Add iterator utility to return unique values according to a predicate function
1 parent 5155110 commit d60ab70

8 files changed

Lines changed: 1510 additions & 0 deletions

File tree

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<!--
2+
3+
@license Apache-2.0
4+
5+
Copyright (c) 2019 The Stdlib Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
19+
-->
20+
21+
# iterUniqueBy
22+
23+
> Create an [iterator][mdn-iterator-protocol] which returns unique values according to a predicate function.
24+
25+
<!-- Section to include introductory text. Make sure to keep an empty line after the intro `section` element and another before the `/section` close. -->
26+
27+
<section class="intro">
28+
29+
</section>
30+
31+
<!-- /.intro -->
32+
33+
<!-- Package usage documentation. -->
34+
35+
<section class="usage">
36+
37+
## Usage
38+
39+
```javascript
40+
var iterUniqueBy = require( '@stdlib/iter/unique-by' );
41+
```
42+
43+
#### iterUniqueBy( iterator, predicate\[, thisArg] )
44+
45+
Returns an [iterator][mdn-iterator-protocol] which returns unique values according to a `predicate` function.
46+
47+
```javascript
48+
var array2iterator = require( '@stdlib/array/to-iterator' );
49+
50+
function predicate( a, b ) {
51+
return ( a !== b );
52+
}
53+
54+
var src = array2iterator( [ 2, 1, 1, 2, 4, 3, 4, 3 ] );
55+
56+
var it = iterUniqueBy( src, predicate );
57+
// returns <Object>
58+
59+
var v = it.next().value;
60+
// returns 2
61+
62+
v = it.next().value;
63+
// returns 1
64+
65+
v = it.next().value;
66+
// returns 4
67+
68+
v = it.next().value;
69+
// returns 3
70+
71+
var bool = it.next().done;
72+
// returns true
73+
```
74+
75+
The returned [iterator][mdn-iterator-protocol] protocol-compliant object has the following properties:
76+
77+
- **next**: function which returns an [iterator][mdn-iterator-protocol] protocol-compliant object containing the next iterated value (if one exists) assigned to a `value` property and a `done` property having a `boolean` value indicating whether the [iterator][mdn-iterator-protocol] is finished.
78+
- **return**: function which closes an [iterator][mdn-iterator-protocol] and returns a single (optional) argument in an [iterator][mdn-iterator-protocol] protocol-compliant object.
79+
80+
A `predicate` function is provided two arguments:
81+
82+
- **a**: a previously identified unique value
83+
- **b**: the value whose uniqueness is being determined
84+
85+
To set the execution context of the `predicate` function, provide a `thisArg`.
86+
87+
<!-- eslint-disable object-curly-newline -->
88+
89+
```javascript
90+
var array2iterator = require( '@stdlib/array/to-iterator' );
91+
92+
function predicate( a, b ) {
93+
this.count += 1;
94+
return ( a.v !== b.v );
95+
}
96+
97+
var values = [
98+
{ 'v': 2 },
99+
{ 'v': 1 },
100+
{ 'v': 1 },
101+
{ 'v': 2 },
102+
{ 'v': 4 },
103+
{ 'v': 3 },
104+
{ 'v': 4 },
105+
{ 'v': 3 }
106+
];
107+
108+
var src = array2iterator( values );
109+
110+
var ctx = {
111+
'count': 0
112+
};
113+
114+
var it = iterUniqueBy( src, predicate, ctx );
115+
// returns <Object>
116+
117+
var v = it.next().value;
118+
// returns { 'v': 2 }
119+
120+
v = it.next().value;
121+
// returns { 'v': 1 }
122+
123+
v = it.next().value;
124+
// returns { 'v': 4 }
125+
126+
v = it.next().value;
127+
// returns { 'v': 3 }
128+
129+
var bool = it.next().done;
130+
// returns true
131+
132+
bool = ( ctx.count > 0 );
133+
// returns true
134+
```
135+
136+
</section>
137+
138+
<!-- /.usage -->
139+
140+
<!-- Package usage notes. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
141+
142+
<section class="notes">
143+
144+
## Notes
145+
146+
- A returned [iterator][mdn-iterator-protocol] internally buffers unique values and, thus, has `O(N)` memory requirements.
147+
- A `predicate` function is invoked for each iterated value against each value in an internal buffer consisting of previously identified unique values. Thus, as the number of unique values grows, so, too, does the number of `predicate` function invocations per iterated value.
148+
- An iterated value is considered "unique" if the `predicate` function returns truthy values for **all** comparisons of the iterated value with each value in the internal buffer.
149+
- If an environment supports `Symbol.iterator` **and** a provided [iterator][mdn-iterator-protocol] is iterable, the returned [iterator][mdn-iterator-protocol] is iterable.
150+
151+
</section>
152+
153+
<!-- /.notes -->
154+
155+
<!-- Package usage examples. -->
156+
157+
<section class="examples">
158+
159+
## Examples
160+
161+
<!-- eslint no-undef: "error" -->
162+
163+
```javascript
164+
var discreteUniform = require( '@stdlib/random/iter/discrete-uniform' );
165+
var iterUniqueBy = require( '@stdlib/iter/unique-by' );
166+
167+
function predicate( a, b ) {
168+
return ( a !== b );
169+
}
170+
171+
// Create a seeded iterator which can generate 1000 pseudorandom numbers:
172+
var rand = discreteUniform( 1, 10, {
173+
'seed': 1234,
174+
'iter': 1000
175+
});
176+
177+
// Create an iterator which returns unique values:
178+
var it = iterUniqueBy( rand, predicate );
179+
180+
// Perform manual iteration...
181+
var v;
182+
while ( true ) {
183+
v = it.next();
184+
if ( v.done ) {
185+
break;
186+
}
187+
console.log( v.value );
188+
}
189+
```
190+
191+
</section>
192+
193+
<!-- /.examples -->
194+
195+
<!-- Section to include cited references. If references are included, add a horizontal rule *before* the section. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
196+
197+
<section class="references">
198+
199+
</section>
200+
201+
<!-- /.references -->
202+
203+
<!-- Section for all links. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
204+
205+
<section class="links">
206+
207+
[mdn-iterator-protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterator_protocol
208+
209+
</section>
210+
211+
<!-- /.links -->
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2019 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MODULES //
22+
23+
var bench = require( '@stdlib/bench' );
24+
var discreteUniform = require( '@stdlib/random/iter/discrete-uniform' );
25+
var isnan = require( '@stdlib/math/base/assert/is-nan' );
26+
var isIteratorLike = require( '@stdlib/assert/is-iterator-like' );
27+
var pkg = require( './../package.json' ).name;
28+
var iterUniqueBy = require( './../lib' );
29+
30+
31+
// MAIN //
32+
33+
bench( pkg, function benchmark( b ) {
34+
var rand;
35+
var iter;
36+
var i;
37+
38+
rand = discreteUniform( 0, 100 );
39+
40+
b.tic();
41+
for ( i = 0; i < b.iterations; i++ ) {
42+
iter = iterUniqueBy( rand, predicate );
43+
if ( typeof iter !== 'object' ) {
44+
b.fail( 'should return an object' );
45+
}
46+
}
47+
b.toc();
48+
if ( !isIteratorLike( iter ) ) {
49+
b.fail( 'should return an iterator protocol-compliant object' );
50+
}
51+
b.pass( 'benchmark finished' );
52+
b.end();
53+
54+
function predicate( a, b ) {
55+
return ( a !== b );
56+
}
57+
});
58+
59+
bench( pkg+'::iteration', function benchmark( b ) {
60+
var rand;
61+
var iter;
62+
var z;
63+
var i;
64+
65+
rand = discreteUniform( 1, 1000, {
66+
'iter': b.iterations
67+
});
68+
iter = iterUniqueBy( rand, predicate );
69+
70+
b.tic();
71+
for ( i = 0; i < b.iterations; i++ ) {
72+
z = iter.next().value;
73+
if ( isnan( z ) ) {
74+
b.fail( 'should not return NaN' );
75+
}
76+
}
77+
b.toc();
78+
if ( isnan( z ) ) {
79+
b.fail( 'should not return NaN' );
80+
}
81+
b.pass( 'benchmark finished' );
82+
b.end();
83+
84+
function predicate( a, b ) {
85+
return ( a !== b );
86+
}
87+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
{{alias}}( iterator, predicate[, thisArg] )
3+
Returns an iterator which returns unique values according to a predicate
4+
function.
5+
6+
A returned iterator internally buffers unique values and, thus, has O(N)
7+
memory requirements.
8+
9+
A predicate function is invoked for each iterated value against each value
10+
in an internal buffer consisting of previously identified unique values.
11+
Thus, as the number of unique values grows, so, too, does the number of
12+
predicate function invocations per iterated value.
13+
14+
An iterated value is considered "unique" if the predicate function returns
15+
truthy values for all comparisons of the iterated value with each value in
16+
the internal buffer.
17+
18+
If an environment supports Symbol.iterator and a provided iterator is
19+
iterable, the returned iterator is iterable.
20+
21+
Parameters
22+
----------
23+
iterator: Object
24+
Input iterator.
25+
26+
predicate: Function
27+
A binary function with parameters `a` and `b` corresponding to iterated
28+
values. If the values are the same, the function should return `false`
29+
(i.e., non-unique); otherwise, if the values are distinct, the function
30+
should return `true` (i.e., unique).
31+
32+
thisArg: any (optional)
33+
Execution context.
34+
35+
Returns
36+
-------
37+
iterator: Object
38+
Iterator.
39+
40+
iterator.next(): Function
41+
Returns an iterator protocol-compliant object containing the next
42+
iterated value (if one exists) and a boolean flag indicating whether the
43+
iterator is finished.
44+
45+
iterator.return( [value] ): Function
46+
Finishes an iterator and returns a provided value.
47+
48+
Examples
49+
--------
50+
> var it1 = {{alias:@stdlib/array/to-iterator}}( [ 1, 2, 1, 2, 4 ] );
51+
> function f( a, b ) { return ( a !== b ); };
52+
> var it2 = {{alias}}( it1, f );
53+
> var v = it2.next().value
54+
1
55+
> v = it2.next().value
56+
2
57+
> v = it2.next().value
58+
4
59+
> var bool = it2.next().done
60+
true
61+
62+
See Also
63+
--------
64+

0 commit comments

Comments
 (0)