diff --git a/lib/period-length.js b/lib/period-length.js new file mode 100644 index 0000000..f369571 --- /dev/null +++ b/lib/period-length.js @@ -0,0 +1,46 @@ +import assert from 'assert' + +export default function getPeriodLengthStats(cycleLengths) { + throwIfArgsAreNotInRequiredFormat(cycleLengths) + const periodLengthStats = {} + const sortedCycleLengths = cycleLengths.sort((a, b) => { + return a - b + }) + periodLengthStats.minimum = sortedCycleLengths[0] + periodLengthStats.maximum = sortedCycleLengths[cycleLengths.length - 1] + periodLengthStats.mean = Math.round( + cycleLengths.reduce(getSum) / cycleLengths.length * 100) / 100 + // median + if (cycleLengths.length % 2 == 1) { + periodLengthStats.median = sortedCycleLengths[(cycleLengths.length + 1) / + 2 - 1] + } + else { + const middle = cycleLengths.length / 2 + periodLengthStats.median = (sortedCycleLengths[middle - 1] + + sortedCycleLengths[middle]) / 2 + } + // corrected standard deviation (based on unbiased sample variance) + periodLengthStats.stdDeviation = null // for case t + if (cycleLengths.length > 1) { + const sumOfSquares = cycleLengths.map(cycleLength => { + return Math.pow(cycleLength - periodLengthStats.mean, 2) + }).reduce(getSum) + periodLengthStats.stdDeviation = Math.round( + Math.sqrt(sumOfSquares / (cycleLengths.length - 1 )) * 100) / 100 + } + return periodLengthStats +} + +function getSum(total, num) { + return total + num +} + +function throwIfArgsAreNotInRequiredFormat(cycleLengths) { + assert.ok(Array.isArray(cycleLengths), 'Function requires input to be an array.') + assert.ok(cycleLengths.length > 0, 'Input array should not be empty.') + cycleLengths.forEach(cycleLength => { + assert.equal(typeof cycleLength, 'number', 'Elements in the array should be of type number.') + assert.ok(!isNaN(cycleLength), 'Elements of Array should not be NaN.') + }) +} \ No newline at end of file diff --git a/lib/periode-length.js b/lib/periode-length.js deleted file mode 100644 index 2445685..0000000 --- a/lib/periode-length.js +++ /dev/null @@ -1,35 +0,0 @@ -export default function getPeriodLengthStats(cycleLengthArray) { - if (Array.isArray(cycleLengthArray) && (cycleLengthArray.length > 0) && cycleLengthArray.every(cycleLength => { - return (typeof cycleLength === 'number') && !(isNaN(cycleLength)) - })) { - const PeriodLengthStats = {} - const sortedCycleLegthArray = cycleLengthArray.sort((a, b) => { - return a - b - }) - PeriodLengthStats.minimum = sortedCycleLegthArray[0] - PeriodLengthStats.maximum = sortedCycleLegthArray[cycleLengthArray.length - 1] - PeriodLengthStats.mean = cycleLengthArray.reduce(getSum) / cycleLengthArray.length - // median - if (cycleLengthArray.length % 2 == 1) { - PeriodLengthStats.median = sortedCycleLegthArray[(cycleLengthArray.length + 1) / 2 - 1] - } - else { - const middle = cycleLengthArray.length / 2 - PeriodLengthStats.median = (sortedCycleLegthArray[middle - 1] + sortedCycleLegthArray[middle]) / 2 - } - - PeriodLengthStats.stdDeviation = 0 // default - if (cycleLengthArray.length > 1) { - const sumOfSquares = cycleLengthArray.map(cycleLength => { - return Math.pow(cycleLength - PeriodLengthStats.mean, 2) - }).reduce(getSum) - PeriodLengthStats.stdDeviation = Math.sqrt(sumOfSquares / (cycleLengthArray.length - 1 )) - } - return PeriodLengthStats - } - console.error('getPeriodLengthStats requiers an array of numbers with length > 0.') -} - -function getSum(total, num) { - return total + num -} \ No newline at end of file diff --git a/test/periode-length.spec.js b/test/periode-length.spec.js index 3051244..ad10bd6 100644 --- a/test/periode-length.spec.js +++ b/test/periode-length.spec.js @@ -1,16 +1,52 @@ import chai from 'chai' +import { AssertionError } from 'assert' + import periodInfo from '../lib/period-length' const expect = chai.expect -describe('it calculates the median correctly', () => { - it('works for an odd-numbered array', () => { - const periodLengths = [1, 2, 5, 99, 100] - const result = periodInfo(periodLengths).median - expect(result).to.eql(5) +describe('getPeriodLengthStats', () => { + it('works for a simple odd-numbered array', () => { + const periodLengths = [99, 5, 1, 2, 100] + const result = periodInfo(periodLengths) + const expectedResult = { + minimum: 1, + maximum: 100, + mean: 41.4, + median: 5, + stdDeviation: 53.06 + } + expect(result).to.eql(expectedResult) }) - /* it('works for an even-numbered array', () => { - - }) */ + it('works for a simple even-numbered array', () => { + const periodLengths = [4, 1, 15, 2, 20, 5] + const result = periodInfo(periodLengths) + const expectedResult = { + minimum: 1, + maximum: 20, + mean: 7.83, + median: 4.5, + stdDeviation: 7.78 + } + expect(result).to.eql(expectedResult) + }) + describe('when args are wrong', () => { + it('throws when arg object is an empty array', () => { + const periodLengths = [] + expect(() => periodInfo(periodLengths).to.throw(AssertionError)) + }) + it('throws when arg object is not in right format', () => { + const wrongObject = { hello: 'world' } + expect(() => periodInfo(wrongObject).to.throw(AssertionError)) + }) + it('throws when arg array contains a string', () => { + const wrongElement = [4, 1, 15, '2', 20, 5] + expect(() => periodInfo(wrongElement).to.throw(AssertionError)) + }) + it('throws when arg array contains a NaN', () => { + const wrongElement = [4, 1, 15, NaN, 20, 5] + expect(() => periodInfo(wrongElement).to.throw(AssertionError)) + }) + }) }) \ No newline at end of file