add sample

main
Yanick Champoux 2023-07-25 11:25:50 -04:00
parent 2e2c6a8b3b
commit 85ace80d93
3 changed files with 148 additions and 1 deletions

View File

@ -16,7 +16,8 @@ import { matches } from '@yanick/remeda';
## Additional functions
### `matches`
### `matches(target, matcher)`
### `matches(matcher)(target)`
Compares the input with the matcher and returns `true` if they match.
The matcher can be a function (which will be fed the input and is expected
@ -37,3 +38,23 @@ pipe({a:1,b:2}, R.matches({ b: (val) => val < 5 }) ) // => true
pipe({a:1,b:2}, R.matches({ c: 3 }) ) // => false
pipe( { 'a': 4, 'b': 5, 'c': 6 }, R.matches({ 'a': 4, 'c': 6 })) // => true
```
### `sample(target, size | { size: number, repeating: boolean })`
### `sample(size | { size: number, repeating: boolean })(target)`
Returns random elements of the array. If `size` is bigger than the array
length and `repeating` is false, returns a number of samples equal to
the size of the whole array.
```
sample([1,2,3,4],2); // [3,1]
sample([1,2,3,4],{size:5, repeating: true}); // [3,1,2,2,4]
sample([1,2,3,4],{size:5, repeating: false}); // [3,1,2,4]
R.pipe(
[{a: 5}, {a: 1}, {a: 3}],
R.sumBy(x => x.a)
) // 9
R.sample(2)([1,2,3,4]); // [3,1]
R.sample({size:5, repeating: true},[1,2,3,4]); // [3,1,2,2,4]
R.sample({size:5, repeating: false},[1,2,3,4]); // [3,1,2,4]
```

43
src/sample.test.ts Normal file
View File

@ -0,0 +1,43 @@
import { describe, test, expect, vi, afterEach, beforeEach } from 'vitest';
import { sample } from './sample';
const list = [1, 2, 3, 4, 5];
describe('data first', () => {
beforeEach(() => {
vi.spyOn(global.Math, 'random').mockReturnValue(0.41);
});
afterEach(() => {
vi.spyOn(global.Math, 'random').mockRestore();
});
test('sample of 1', () => {
expect(sample(list, 1)).toEqual([3]);
});
test('sample of 2', () => {
expect(sample(list, 2)).toEqual([3, 2]);
});
test('sample of 20 (> than the array size)', () => {
expect(sample(list, 20)).toEqual([3, 2, 4, 1, 5]);
});
describe('repeating', () => {
test('sample of 2', () => {
expect(sample(list, { size: 2, repeating: true })).toEqual([3, 3]);
});
test('sample of 20', () => {
expect(sample(list, { size: 20, repeating: true })).toHaveLength(20);
});
});
});
describe('data last', () => {
beforeEach(() => {
vi.spyOn(global.Math, 'random').mockReturnValue(0.41);
});
afterEach(() => {
vi.spyOn(global.Math, 'random').mockRestore();
});
test('sample of 2', () => {
expect(sample(2)(list)).toEqual([3, 2]);
});
});

83
src/sample.ts Normal file
View File

@ -0,0 +1,83 @@
import { purry } from 'remeda';
export interface SampleType {
size: number;
repeating: boolean;
}
function _sample<T>(items: Array<T>, options: number | SampleType) {
let nbrSamples = typeof options === 'number' ? options : options.size;
const repeating = typeof options === 'object' && options?.repeating;
let size = items.length;
// we only need a copy if we're in non-repeating mode
if (!repeating) items = [...items];
if (nbrSamples > size && !repeating) nbrSamples = size;
const sample = [];
for (let i = 0; i < nbrSamples; i++) {
const index = Math.floor(Math.random() * size);
sample.push(items[index]);
if (!repeating) {
items[index] = items[size - 1];
size--;
}
}
return sample;
}
/**
* Returns random elements of the array.
* @param items the array
* @param options The number of samples to take. If the same item can be
* picked multiple times, you can pass the object `{ size: number, repeating: boolean }`.
* If the number of samples to return exceeds the array size, and `repeating`
* is false, returns a number of sample equals to the size of the array.
* @signature
* R.sample(array,size)
* R.sample(array, {size, repeating})
* @example
* R.sample([1,2,3,4],2); // [3,1]
* R.sample([1,2,3,4],{size:5, repeating: true}); // [3,1,2,2,4]
* R.sample([1,2,3,4],{size:5, repeating: false}); // [3,1,2,4]
* R.pipe(
* [{a: 5}, {a: 1}, {a: 3}],
* R.sumBy(x => x.a)
* ) // 9
* @data_first
* @category Array
*/
export function sample<T>(items: ReadonlyArray<T>, size: number): Array<T>;
export function sample<T>(
items: ReadonlyArray<T>,
options: SampleType
): Array<T>;
/**
* Returns random elements of the array.
* @param options The number of samples to take. If the same item can be
* picked multiple times, you can pass the object `{ size: number, repeating: boolean }`.
* If the number of samples to return exceeds the array size, and `repeating`
* is false, returns a number of sample equals to the size of the array.
* @signature
* R.sample(size)(array)
* R.sample({size, repeating})(array)
* @example
* R.sample(2)([1,2,3,4]); // [3,1]
* R.sample({size:5, repeating: true},[1,2,3,4]); // [3,1,2,2,4]
* R.sample({size:5, repeating: false},[1,2,3,4]); // [3,1,2,4]
* @data_last
* @category Array
*/
export function sample(size: number): <T>(items: Array<T>) => Array<T>;
export function sample(options: SampleType): <T>(items: Array<T>) => Array<T>;
export function sample() {
return purry(_sample, arguments);
}