Skip to content

Commit 858646b

Browse files
authored
Add exhaustive sampling and rename the default export to consecutiveUniqueRandom (#25)
1 parent c1a6a94 commit 858646b

File tree

5 files changed

+176
-34
lines changed

5 files changed

+176
-34
lines changed

index.d.ts

+41-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
/**
22
Generate random numbers that are consecutively unique.
33
4-
@returns Returns a function, that when called, will return a random number that is never the same as the previous.
4+
@returns A function, that when called, will return a random number that is never the same as the previous.
55
66
@example
77
```
8-
import uniqueRandom from 'unique-random';
8+
import {consecutiveUniqueRandom} from 'unique-random';
99
10-
const random = uniqueRandom(1, 10);
10+
const random = consecutiveUniqueRandom(1, 10);
1111
1212
console.log(random(), random(), random());
1313
//=> 5 2 6
@@ -17,9 +17,9 @@ The returned function is also an iterable which consumes from the same source as
1717
1818
@example
1919
```
20-
import uniqueRandom from 'unique-random';
20+
import {consecutiveUniqueRandom} from 'unique-random';
2121
22-
const random = uniqueRandom(1, 10);
22+
const random = consecutiveUniqueRandom(1, 10);
2323
2424
for (const number of random) {
2525
console.log(number);
@@ -31,4 +31,39 @@ for (const number of random) {
3131
}
3232
```
3333
*/
34-
export default function uniqueRandom(minimum: number, maximum: number): (() => number) & {[Symbol.iterator](): Iterator<number>};
34+
export function consecutiveUniqueRandom(minimum: number, maximum: number): (() => number) & {[Symbol.iterator](): Iterator<number>};
35+
36+
/**
37+
Generate random numbers that do not repeat until the entire range has appeared.
38+
39+
@return A function, that when called, will return a random number that is never the same as any previously returned until the entire range of possible numbers has been returned.
40+
41+
@example
42+
```
43+
import {exhaustiveUniqueRandom} from 'unique-random';
44+
45+
const random = exhaustiveUniqueRandom(1, 10);
46+
47+
console.log(random(), random(), random());
48+
//=> 5 2 6
49+
```
50+
51+
The returned function is also an iterable which consumes from the same source as the function:
52+
53+
@example
54+
```
55+
import {exhaustiveUniqueRandom} from 'unique-random';
56+
57+
const random = exhaustiveUniqueRandom(1, 10);
58+
59+
for (const number of random) {
60+
console.log(number);
61+
62+
// The unique numbers will be iterated over infinitely
63+
if (stopCondition) {
64+
break;
65+
}
66+
}
67+
```
68+
*/
69+
export function exhaustiveUniqueRandom(minimum: number, maximum: number): (() => number) & {[Symbol.iterator](): Iterator<number>};

index.js

+68-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1-
export default function uniqueRandom(minimum, maximum) {
2-
let previousValue;
1+
function * range(minimum, maximum) {
2+
for (let number = minimum; number <= maximum; number++) {
3+
yield number;
4+
}
5+
}
36

4-
function random() {
5-
const number = Math.floor(
6-
(Math.random() * (maximum - minimum + 1)) + minimum,
7-
);
7+
function randomInteger(minimum, maximum) {
8+
return Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
9+
}
10+
11+
function randomIntegerWithout(minimum, maximum, excludedValue) {
12+
const number = randomInteger(minimum, maximum - 1);
13+
14+
return number >= excludedValue ? number + 1 : number;
15+
}
816

9-
previousValue = (number === previousValue && minimum !== maximum) ? random() : number;
17+
function makeCallable(generator) {
18+
const iterator = generator();
1019

11-
return previousValue;
20+
function random() {
21+
return iterator.next().value;
1222
}
1323

1424
random[Symbol.iterator] = function * () {
@@ -19,3 +29,53 @@ export default function uniqueRandom(minimum, maximum) {
1929

2030
return random;
2131
}
32+
33+
export function consecutiveUniqueRandom(minimum, maximum) {
34+
return makeCallable(function * () {
35+
if (minimum === maximum) {
36+
while (true) {
37+
yield minimum;
38+
}
39+
}
40+
41+
let previousValue = randomInteger(minimum, maximum);
42+
yield previousValue;
43+
44+
while (true) {
45+
previousValue = randomIntegerWithout(minimum, maximum, previousValue);
46+
yield previousValue;
47+
}
48+
});
49+
}
50+
51+
export function exhaustiveUniqueRandom(minimum, maximum) {
52+
return makeCallable(function * () {
53+
if (minimum === maximum) {
54+
while (true) {
55+
yield minimum;
56+
}
57+
}
58+
59+
let unconsumedValues = [...range(minimum, maximum)];
60+
61+
while (true) {
62+
while (unconsumedValues.length > 1) {
63+
yield unconsumedValues.splice(
64+
randomInteger(0, unconsumedValues.length - 1),
65+
1,
66+
)[0];
67+
}
68+
69+
const [previousValue] = unconsumedValues;
70+
71+
yield previousValue;
72+
73+
unconsumedValues = [...range(minimum, maximum)];
74+
75+
yield unconsumedValues.splice(
76+
randomIntegerWithout(0, unconsumedValues.length - 1, previousValue - minimum),
77+
1,
78+
)[0];
79+
}
80+
});
81+
}

index.test-d.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import {expectAssignable, expectType} from 'tsd';
2-
import uniqueRandom from './index.js';
2+
import {consecutiveUniqueRandom, exhaustiveUniqueRandom} from './index.js';
33

4-
const random = uniqueRandom(1, 10);
4+
const random1 = consecutiveUniqueRandom(1, 10);
55

6-
expectAssignable<() => number>(random);
7-
expectType<number>(random());
6+
expectAssignable<() => number>(random1);
7+
expectType<number>(random1());
88

9-
for (const number of random) {
9+
for (const number of random1) {
10+
expectType<number>(number);
11+
}
12+
13+
const random2 = exhaustiveUniqueRandom(1, 10);
14+
15+
expectAssignable<() => number>(random2);
16+
expectType<number>(random2());
17+
18+
for (const number of random2) {
1019
expectType<number>(number);
1120
}

readme.md

+12-6
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,32 @@ npm install unique-random
1313
## Usage
1414

1515
```js
16-
import uniqueRandom from 'unique-random';
16+
import {consecutiveUniqueRandom} from 'unique-random';
1717

18-
const random = uniqueRandom(1, 10);
18+
const random = consecutiveUniqueRandom(1, 10);
1919

2020
console.log(random(), random(), random());
2121
//=> 5 2 6
2222
```
2323

2424
## API
2525

26-
### uniqueRandom(minimum, maximum)
26+
### consecutiveUniqueRandom(minimum, maximum)
2727

28-
Returns a function, that when called, will return a random number that is never the same as the previous.
28+
Generate random numbers that are consecutively unique.
29+
30+
### exhaustiveUniqueRandom(minimum, maximum)
31+
32+
Generate random numbers that do not repeat until the entire range has appeared.
33+
34+
Both `consecutiveUniqueRandom` and `exhaustiveUniqueRandom` return a function, that when called, will return the generated number.
2935

3036
The returned function is also an iterable which consumes from the same source as the function:
3137

3238
```js
33-
import uniqueRandom from 'unique-random';
39+
import {exhaustiveUniqueRandom} from 'unique-random';
3440

35-
const random = uniqueRandom(1, 10);
41+
const random = exhaustiveUniqueRandom(1, 10);
3642

3743
for (const number of random) {
3844
console.log(number);

test.js

+41-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import test from 'ava';
22
import assertInRange from './assert-in-range.js';
3-
import uniqueRandom from './index.js';
3+
import {consecutiveUniqueRandom, exhaustiveUniqueRandom} from './index.js';
44

5-
test('main', t => {
5+
function testConsecutiveUniqueness(t, uniqueRandom) {
66
const random = uniqueRandom(1, 10);
7-
let count = 1000;
8-
let currentValue;
97
let previousValue;
108

11-
while (--count > 0) {
12-
currentValue = random();
9+
for (let count = 0; count < 1000; count++) {
10+
const currentValue = random();
1311

1412
assertInRange(t, currentValue, {start: 1, end: 10});
1513
if (previousValue !== undefined) {
@@ -18,14 +16,48 @@ test('main', t => {
1816

1917
previousValue = currentValue;
2018
}
19+
}
2120

22-
t.pass();
21+
test('consecutiveUniqueRandom - main', t => {
22+
testConsecutiveUniqueness(t, consecutiveUniqueRandom);
2323
});
2424

25-
test('iterator', t => {
25+
test('consecutiveUniqueRandom - iterator', t => {
2626
t.plan(3); // In case the for-of loop doesn't run
2727

28-
const random = uniqueRandom(1, 10);
28+
const random = consecutiveUniqueRandom(1, 10);
29+
30+
for (const number of random) { // eslint-disable-line no-unreachable-loop
31+
assertInRange(t, number, {start: 1, end: 10});
32+
break;
33+
}
34+
35+
const {value, done} = random[Symbol.iterator]().next();
36+
37+
assertInRange(t, value, {start: 1, end: 10});
38+
t.false(done);
39+
});
40+
41+
test('exhaustiveUniqueRandom - main', t => {
42+
const random = exhaustiveUniqueRandom(1, 5);
43+
const seenValuesCount = new Map(Array.from({length: 5}, (_, index) => [index + 1, 0]));
44+
45+
for (let count = 1; count <= 10; count++) {
46+
const value = random();
47+
assertInRange(t, value, {start: 1, end: 5});
48+
t.true(seenValuesCount.get(value) < 2, 'Value should only appear twice');
49+
seenValuesCount.set(value, seenValuesCount.get(value) + 1);
50+
}
51+
});
52+
53+
test('exhaustiveUniqueRandom - consecutive uniqueness', t => {
54+
testConsecutiveUniqueness(t, exhaustiveUniqueRandom);
55+
});
56+
57+
test('exhaustiveUniqueRandom - iterator', t => {
58+
t.plan(3); // In case the for-of loop doesn't run
59+
60+
const random = exhaustiveUniqueRandom(1, 10);
2961

3062
for (const number of random) { // eslint-disable-line no-unreachable-loop
3163
assertInRange(t, number, {start: 1, end: 10});

0 commit comments

Comments
 (0)