Adds required files
This commit is contained in:
parent
7bc62d3934
commit
8aafb2bb6e
204
src/utility/lib/continuousDistribution.js
Normal file
204
src/utility/lib/continuousDistribution.js
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
const _ = require('lodash');
|
||||||
|
const { interpolate, range, min, max } = require('./functions');
|
||||||
|
|
||||||
|
class ContinuousDistribution {
|
||||||
|
/**
|
||||||
|
* @param {number[]} xs
|
||||||
|
* @param {number[]} ys
|
||||||
|
*/
|
||||||
|
constructor(xs, ys) {
|
||||||
|
if (!_.isArray(xs)) {
|
||||||
|
throw new Error('XS should be an array.');
|
||||||
|
}
|
||||||
|
if (!_.isArray(ys)) {
|
||||||
|
throw new Error('YS should be an array.');
|
||||||
|
}
|
||||||
|
if (!this.validateHasLength(xs)) {
|
||||||
|
throw new Error('You need at least one element.');
|
||||||
|
}
|
||||||
|
if (!this.validateSize(xs, ys)) {
|
||||||
|
throw new Error('Arrays of "xs" and "ys" have different sizes.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = this.order(xs, ys);
|
||||||
|
this.xs = sorted.xs;
|
||||||
|
this.ys = sorted.ys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order them to make sure that xs are increasing
|
||||||
|
* @param {number[]} xs
|
||||||
|
* @param {number[]} ys
|
||||||
|
* @return {{ys: number[], xs: number[]}}
|
||||||
|
*/
|
||||||
|
order(xs, ys) {
|
||||||
|
const xsYs = xs.map((v, i) => ({ ys: ys[i], xs: v }));
|
||||||
|
const sorted = xsYs.sort((a, b) => {
|
||||||
|
if (a.xs > b.xs) return 1;
|
||||||
|
if (a.xs < b.xs) return -1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const XS = sorted.map(v => v.xs);
|
||||||
|
const YS = sorted.map(v => v.ys);
|
||||||
|
|
||||||
|
return { xs: XS, ys: YS };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} xs
|
||||||
|
* @param {number[]} ys
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
validateSize(xs, ys) {
|
||||||
|
return xs.length === ys.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param xs
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
validateHasLength(xs) {
|
||||||
|
return xs.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
minX() {
|
||||||
|
return this.xs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
maxX() {
|
||||||
|
return this.xs[this.xs.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If xs=[1,2,3], and ys=[5,6,7],
|
||||||
|
* then findY(1) = 5, findY(3) = 7, findY(1.5) = 5.5
|
||||||
|
* @param {number} x
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
findY(x) {
|
||||||
|
let firstHigherIndex = this.xs.findIndex(X => X >= x);
|
||||||
|
if (firstHigherIndex < 0) return this.ys[this.ys.length - 1];
|
||||||
|
if (firstHigherIndex === 0) return this.ys[0];
|
||||||
|
let lowerOrEqualIndex = firstHigherIndex - 1;
|
||||||
|
if (lowerOrEqualIndex < 0) lowerOrEqualIndex = 0;
|
||||||
|
let needsInterpolation = this.xs[lowerOrEqualIndex] !== x;
|
||||||
|
if (needsInterpolation) {
|
||||||
|
return interpolate(
|
||||||
|
this.xs[lowerOrEqualIndex],
|
||||||
|
this.xs[firstHigherIndex],
|
||||||
|
this.ys[lowerOrEqualIndex],
|
||||||
|
this.ys[firstHigherIndex],
|
||||||
|
x
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return this.ys[lowerOrEqualIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If xs=[1,2,3], and ys=[5,6,7],
|
||||||
|
* then findX(5) = 1, findX(7) = 3, findY(5.5) = 1.5
|
||||||
|
* This should do the same thing as `findY`, but for Y.
|
||||||
|
* @param {number} y
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
findX(y) {
|
||||||
|
let firstHigherIndex = this.ys.findIndex(Y => Y >= y);
|
||||||
|
if (firstHigherIndex < 0) return this.xs[this.xs.length - 1];
|
||||||
|
if (firstHigherIndex === 0) return this.xs[0];
|
||||||
|
let lowerOrEqualIndex = firstHigherIndex - 1;
|
||||||
|
if (lowerOrEqualIndex < 0) lowerOrEqualIndex = 0;
|
||||||
|
let needsInterpolation = this.ys[lowerOrEqualIndex] !== y;
|
||||||
|
if (needsInterpolation) {
|
||||||
|
return interpolate(
|
||||||
|
this.ys[lowerOrEqualIndex],
|
||||||
|
this.ys[firstHigherIndex],
|
||||||
|
this.xs[lowerOrEqualIndex],
|
||||||
|
this.xs[firstHigherIndex],
|
||||||
|
y
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return this.xs[lowerOrEqualIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} xs
|
||||||
|
* @return {ContinuousDistribution}
|
||||||
|
*/
|
||||||
|
convertWithAlternativeXs(xs) {
|
||||||
|
const ys = xs.map(x => this.findY(x));
|
||||||
|
return new ContinuousDistribution(xs, ys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} newLength
|
||||||
|
* @return {ContinuousDistribution}
|
||||||
|
*/
|
||||||
|
convertToNewLength(newLength) {
|
||||||
|
const _range = range(min(this.xs), max(this.xs), newLength);
|
||||||
|
return this.convertWithAlternativeXs(_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
sampleSingle() {
|
||||||
|
const y = Math.random();
|
||||||
|
return this.findX(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poduce n samples, using ``sampleSingle`` for each.
|
||||||
|
* @param size
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
sample(size) {
|
||||||
|
return Array.from(Array(size), () => this.sampleSingle());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the integral. Takes the average Y value between points,
|
||||||
|
* treating them like a triangle.
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
integral(params = { filterOutNaNs: false }) {
|
||||||
|
let integral = 0;
|
||||||
|
if (!params.filterOutNaNs && _.includes(this.ys, NaN)) {
|
||||||
|
return NaN;
|
||||||
|
} else if (_.includes(this.ys, Infinity) && _.includes(this.ys, -Infinity)) {
|
||||||
|
return NaN;
|
||||||
|
} else if (_.includes(this.ys, Infinity)) {
|
||||||
|
return Infinity;
|
||||||
|
} else if (_.includes(this.ys, -Infinity)) {
|
||||||
|
return -Infinity;
|
||||||
|
}
|
||||||
|
for (let i = 1; i < this.ys.length; i++) {
|
||||||
|
let thisY = this.ys[i];
|
||||||
|
let lastY = this.ys[i - 1];
|
||||||
|
let thisX = this.xs[i];
|
||||||
|
let lastX = this.xs[i - 1];
|
||||||
|
|
||||||
|
if (
|
||||||
|
_.isFinite(thisY) && _.isFinite(lastY) &&
|
||||||
|
_.isFinite(thisX) && _.isFinite(lastX)
|
||||||
|
) {
|
||||||
|
let sectionInterval = ((thisY + lastY) / 2) * (thisX - lastX);
|
||||||
|
integral = integral + sectionInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return integral;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ContinuousDistribution,
|
||||||
|
};
|
111
src/utility/lib/continuousDistribution.spec.js
Normal file
111
src/utility/lib/continuousDistribution.spec.js
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
const { ContinuousDistribution } = require('./continuousDistribution');
|
||||||
|
const { up, down } = require('./functions');
|
||||||
|
|
||||||
|
describe('ContinuousDistribution Class', () => {
|
||||||
|
it('constructor()', () => {
|
||||||
|
const xs = up(1, 9);
|
||||||
|
const ys = up(1, 8);
|
||||||
|
expect(() => {
|
||||||
|
new ContinuousDistribution(xs, ys);
|
||||||
|
}).toThrow(/^Arrays of "xs" and "ys" have different sizes.$/);
|
||||||
|
});
|
||||||
|
it('order()', () => {
|
||||||
|
const xs = down(9, 1);
|
||||||
|
const ys = down(9, 1);
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
expect(cdf.xs).toEqual(up(1, 9));
|
||||||
|
expect(cdf.ys).toEqual(up(1, 9));
|
||||||
|
});
|
||||||
|
it('findY()', () => {
|
||||||
|
const xs = [1, 2, 3];
|
||||||
|
const ys = [5, 6, 7];
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
expect(cdf.findY(1)).toEqual(5);
|
||||||
|
expect(cdf.findY(1.5)).toEqual(5.5);
|
||||||
|
expect(cdf.findY(3)).toEqual(7);
|
||||||
|
expect(cdf.findY(4)).toEqual(7);
|
||||||
|
expect(cdf.findY(15)).toEqual(7);
|
||||||
|
expect(cdf.findY(-1)).toEqual(5);
|
||||||
|
});
|
||||||
|
it('findX()', () => {
|
||||||
|
const xs = [1, 2, 3];
|
||||||
|
const ys = [5, 6, 7];
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
expect(cdf.findX(5)).toEqual(1);
|
||||||
|
expect(cdf.findX(7)).toEqual(3);
|
||||||
|
expect(cdf.findX(5.5)).toEqual(1.5);
|
||||||
|
expect(cdf.findX(8)).toEqual(3);
|
||||||
|
expect(cdf.findX(4)).toEqual(1);
|
||||||
|
});
|
||||||
|
it('convertWithAlternativeXs() when "XS" within "xs"', () => {
|
||||||
|
const xs = up(1, 9);
|
||||||
|
const ys = up(20, 28);
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
const XS = up(3, 7);
|
||||||
|
const CDF = cdf.convertWithAlternativeXs(XS);
|
||||||
|
expect(CDF.xs).toEqual([3, 4, 5, 6, 7]);
|
||||||
|
expect(CDF.ys).toEqual([22, 23, 24, 25, 26]);
|
||||||
|
});
|
||||||
|
it('convertToNewLength()', () => {
|
||||||
|
const xs = up(1, 9);
|
||||||
|
const ys = up(50, 58);
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
const CDF = cdf.convertToNewLength(3);
|
||||||
|
expect(CDF.xs).toEqual([1, 5, 9]);
|
||||||
|
expect(CDF.ys).toEqual([50, 54, 58]);
|
||||||
|
});
|
||||||
|
it('sample()', () => {
|
||||||
|
const xs = up(1, 9);
|
||||||
|
const ys = up(70, 78);
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
const XS = cdf.sample(3);
|
||||||
|
expect(Number.isInteger(XS[0])).toBe(true);
|
||||||
|
expect(Number.isInteger(XS[1])).toBe(true);
|
||||||
|
expect(Number.isInteger(XS[2])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('integral()', () => {
|
||||||
|
it('with regular inputs', () => {
|
||||||
|
const xs = [0,1,2,4];
|
||||||
|
const ys = [0.0, 1.0, 2.0, 2.0];
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
const integral = cdf.integral();
|
||||||
|
expect(integral).toEqual(6);
|
||||||
|
});
|
||||||
|
it('with an infinity', () => {
|
||||||
|
const xs = [0,1,2,4];
|
||||||
|
const ys = [0.0, 1.0, Infinity, 2.0];
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
const integral = cdf.integral();
|
||||||
|
expect(integral).toEqual(Infinity);
|
||||||
|
});
|
||||||
|
it('with negative infinity', () => {
|
||||||
|
const xs = [0,1,2,4];
|
||||||
|
const ys = [0.0, 1.0, -Infinity, 2.0];
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
const integral = cdf.integral();
|
||||||
|
expect(integral).toEqual(-Infinity);
|
||||||
|
});
|
||||||
|
it('with both positive and negative infinities', () => {
|
||||||
|
const xs = [0,1,2,4];
|
||||||
|
const ys = [0.0, 1.0, -Infinity, Infinity];
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
const integral = cdf.integral();
|
||||||
|
expect(integral).toEqual(NaN);
|
||||||
|
});
|
||||||
|
it('with a NaN and filterOutNaNs set to false', () => {
|
||||||
|
const xs = [0,1,2,4];
|
||||||
|
const ys = [0.0, 1.0, 2.0, NaN];
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
const integral = cdf.integral({filterOutNaNs: false});
|
||||||
|
expect(integral).toEqual(NaN);
|
||||||
|
});
|
||||||
|
it('with a NaN and filterOutNaNs set to true', () => {
|
||||||
|
const xs = [0,1,2,4];
|
||||||
|
const ys = [0.0, 1.0, 2.0, NaN];
|
||||||
|
const cdf = new ContinuousDistribution(xs, ys);
|
||||||
|
const integral = cdf.integral({filterOutNaNs: true});
|
||||||
|
expect(integral).toEqual(2);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
136
src/utility/lib/functions.js
Normal file
136
src/utility/lib/functions.js
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
/**
|
||||||
|
* @param {number} xMin
|
||||||
|
* @param {number} xMax
|
||||||
|
* @param {number} yMin
|
||||||
|
* @param {number} yMax
|
||||||
|
* @param {number} xIntended
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
function interpolate(xMin, xMax, yMin, yMax, xIntended) {
|
||||||
|
const minProportion = (xMax - xIntended) / (xMax - xMin);
|
||||||
|
const maxProportion = (xIntended - xMin) / (xMax - xMin);
|
||||||
|
return (yMin * minProportion) + (yMax * maxProportion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should return an array of n evenly-spaced items
|
||||||
|
* between min and max, including min and max.
|
||||||
|
* range(1,5,3) = [1, 3, 5];
|
||||||
|
* range(1,5,5) = [1, 2, 3, 4, 5];
|
||||||
|
* @param {number} min
|
||||||
|
* @param {number} max
|
||||||
|
* @param {number} n
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
function range(min, max, n) {
|
||||||
|
if (n <= 0) throw new RangeError('n is less then zero');
|
||||||
|
if (n === Infinity) throw new RangeError('n is Infinity');
|
||||||
|
if (n === 0) return [];
|
||||||
|
if (n === 1) return [min];
|
||||||
|
if (n === 2) return [min, max];
|
||||||
|
if (min === max) return Array(n).fill(min);
|
||||||
|
n -= 1;
|
||||||
|
const diff = min - max;
|
||||||
|
const interval = Math.abs(diff / n);
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
let item = min;
|
||||||
|
do {
|
||||||
|
result.push(item);
|
||||||
|
item += interval;
|
||||||
|
} while (item <= max);
|
||||||
|
|
||||||
|
// corrects results because of math errors
|
||||||
|
if ((n + 1) - result.length === 1) {
|
||||||
|
result.push(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} arr
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
function sum(arr) {
|
||||||
|
return arr.reduce((acc, val) => acc + val, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} arr
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
function mean(arr) {
|
||||||
|
return sum(arr) / arr.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} arr
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
function min(arr) {
|
||||||
|
let val = arr[0];
|
||||||
|
for (let i = 1; i < arr.length; i++) {
|
||||||
|
if (arr[i] < val) {
|
||||||
|
val = arr[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} arr
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
function max(arr) {
|
||||||
|
let val = arr[0];
|
||||||
|
for (let i = 1; i < arr.length; i++) {
|
||||||
|
if (arr[i] > val) {
|
||||||
|
val = arr[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} min
|
||||||
|
* @param {number} max
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
function random(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} from
|
||||||
|
* @param {number} to
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
function up(from, to) {
|
||||||
|
const arr = [];
|
||||||
|
for (let i = from; i <= to; i++) arr.push(i);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} from
|
||||||
|
* @param {number} to
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
function down(from, to) {
|
||||||
|
const arr = [];
|
||||||
|
for (let i = from; i >= to; i--) arr.push(i);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
interpolate,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
range,
|
||||||
|
mean,
|
||||||
|
random,
|
||||||
|
up,
|
||||||
|
down,
|
||||||
|
};
|
55
src/utility/lib/functions.spec.js
Normal file
55
src/utility/lib/functions.spec.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
const { interpolate } = require('./functions');
|
||||||
|
const { range } = require('./functions');
|
||||||
|
const { mean } = require('./functions');
|
||||||
|
const { min } = require('./functions');
|
||||||
|
const { max } = require('./functions');
|
||||||
|
const { random } = require('./functions');
|
||||||
|
const { up, down } = require('./functions');
|
||||||
|
|
||||||
|
describe('Functions', () => {
|
||||||
|
it('interpolate()', () => {
|
||||||
|
expect(interpolate(
|
||||||
|
10, 20,
|
||||||
|
1, 2,
|
||||||
|
15
|
||||||
|
)).toBe(1.5);
|
||||||
|
});
|
||||||
|
it('range()', () => {
|
||||||
|
expect(range(1, 5, 3)).toEqual([1, 3, 5]);
|
||||||
|
expect(range(1, 5, 5)).toEqual([1, 2, 3, 4, 5]);
|
||||||
|
expect(range(-10, 15, 2)).toEqual([-10, 15]);
|
||||||
|
expect(range(-10, 15, 3)).toEqual([-10, 2.5, 15]);
|
||||||
|
expect(range(-10.3, 17, 3)).toEqual([-10.3, 3.3499999999999996, 17]);
|
||||||
|
expect(range(-10.3, 17, 5)).toEqual([-10.3, -3.4750000000000005, 3.3499999999999996, 10.175, 17]);
|
||||||
|
expect(range(-10.3, 17.31, 3)).toEqual([-10.3, 3.504999999999999, 17.31]);
|
||||||
|
expect(range(1, 1, 3)).toEqual([1, 1, 1]);
|
||||||
|
});
|
||||||
|
it('mean()', () => {
|
||||||
|
expect(mean([1, 2, 3])).toBe(2);
|
||||||
|
expect(mean([1, 2, 3, -2])).toBe(1);
|
||||||
|
expect(mean([1, 2, 3, -2, -10])).toBe(-1.2);
|
||||||
|
});
|
||||||
|
it('min()', () => {
|
||||||
|
expect(min([1, 2, 3])).toBe(1);
|
||||||
|
expect(min([-1, -2, 0, 20])).toBe(-2);
|
||||||
|
expect(min([-1, -2, 0, 20, -2.2])).toBe(-2.2);
|
||||||
|
});
|
||||||
|
it('max()', () => {
|
||||||
|
expect(max([1, 2, 3])).toBe(3);
|
||||||
|
expect(max([-1, -2, 0, 20])).toBe(20);
|
||||||
|
expect(max([-1, -2, 0, -2.2])).toBe(0);
|
||||||
|
});
|
||||||
|
it('random()', () => {
|
||||||
|
const num = random(1, 5);
|
||||||
|
expect(num).toBeLessThanOrEqual(5);
|
||||||
|
expect(num).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
it('up()', () => {
|
||||||
|
expect(up(1, 5)).toEqual([1, 2, 3, 4, 5]);
|
||||||
|
expect(up(-1, 5)).toEqual([-1, 0, 1, 2, 3, 4, 5]);
|
||||||
|
});
|
||||||
|
it('down()', () => {
|
||||||
|
expect(down(5, 1)).toEqual([5, 4, 3, 2, 1]);
|
||||||
|
expect(down(5, -1)).toEqual([5, 4, 3, 2, 1, 0, -1]);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user