From a60126b692e76ffdc28e6e8ad38bffcfd1866a83 Mon Sep 17 00:00:00 2001 From: Natalia Date: Tue, 30 Sep 2025 14:56:28 +0300 Subject: [PATCH 1/3] add task solution --- src/reduce.test.js | 68 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/src/reduce.test.js b/src/reduce.test.js index 47a892f..d3ad626 100644 --- a/src/reduce.test.js +++ b/src/reduce.test.js @@ -4,16 +4,78 @@ const { reduce } = require('./reduce'); describe('reduce', () => { beforeAll(() => { - Array.prototype.reduce2 = reduce; // eslint-disable-line + Array.prototype.reduce2 = reduce; }); afterAll(() => { delete Array.prototype.reduce2; }); - it('should ', () => { + let callback; + beforeEach(() => { + callback = jest.fn().mockImplementation((a, b) => a + b); }); - // Add tests here + it('should be declared', () => { + expect(reduce).toBeInstanceOf(Function); + }); + + it('should not mutate array', () => { + const array = [1, 2, 3, 4]; + + array.reduce2(callback, 0); + + expect(array).toEqual(array); + }); + + it('should run callback array`s length times if initialValue is', () => { + const array = [1, 2, 3, 4]; + + array.reduce2(callback, 0); + + expect(callback).toHaveBeenCalledTimes(array.length); ; + }); + + it('should run callback array`s length - 1 times if no initialValue', () => { + const array = [1, 2, 3, 4]; + + array.reduce2(callback); + + expect(callback).toHaveBeenCalledTimes(array.length - 1); ; + }); + + it('should not run callback if array is empty', () => { + const array = []; + + array.reduce2(callback); + + expect(callback).toHaveBeenCalledTimes(0); ; + }); + + it('should run callback with correct arguments', () => { + const array = [1, 2, 3, 4]; + + array.reduce2(callback, 0); + + expect(callback).toHaveBeenNthCalledWith(1, 0, 1, 0, array); + expect(callback).toHaveBeenNthCalledWith(2, 1, 2, 1, array); + expect(callback).toHaveBeenNthCalledWith(3, 3, 3, 2, array); + expect(callback).toHaveBeenNthCalledWith(4, 6, 4, 3, array); + }); + + it('should return initial value if array is empty', () => { + const array = []; + const initialValue = 0; + const result = array.reduce2(callback, initialValue); + + expect(result).toBe(initialValue); ; + }); + + it('should return undefined if array is empty and no initial value', () => { + const array = []; + const result = array.reduce2(callback); + + expect(result).toBe(undefined); ; + }); }); From 5812badb394a806bc26cec3d93a631a43fe123e7 Mon Sep 17 00:00:00 2001 From: Natalia Date: Tue, 30 Sep 2025 15:08:08 +0300 Subject: [PATCH 2/3] add task solution --- src/reduce.js | 32 +++++++++++--- src/reduce.test.js | 105 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/src/reduce.js b/src/reduce.js index 38be21c..fcfb02e 100644 --- a/src/reduce.js +++ b/src/reduce.js @@ -7,19 +7,37 @@ * @returns {*} */ function reduce(callback, startValue) { - let prev = startValue; + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + + const arr = this; + let hasInitialValue = arguments.length > 1; + let accumulator; let startIndex = 0; - if (arguments.length < 2) { - startIndex = 1; - prev = this[0]; + if (arr.length === 0 && !hasInitialValue) { + throw new TypeError('Reduce of empty array with no initial value'); + } + + if (hasInitialValue) { + accumulator = startValue; + } else { + + while (startIndex < arr.length && !(startIndex in arr)) { + startIndex++; + } + accumulator = arr[startIndex]; + startIndex++; } - for (let i = startIndex; i < this.length; i++) { - prev = callback(prev, this[i], i, this); + for (let i = startIndex; i < arr.length; i++) { + if (i in arr) { + accumulator = callback(accumulator, arr[i], i, arr); + } } - return prev; + return accumulator; } module.exports = { reduce }; diff --git a/src/reduce.test.js b/src/reduce.test.js index d3ad626..50bc55c 100644 --- a/src/reduce.test.js +++ b/src/reduce.test.js @@ -23,59 +23,120 @@ describe('reduce', () => { it('should not mutate array', () => { const array = [1, 2, 3, 4]; + const original = array.slice(); array.reduce2(callback, 0); - expect(array).toEqual(array); + expect(array).toEqual(original); }); - it('should run callback array`s length times if initialValue is', () => { + it('should run callback array`s length times if initialValue is provided', () => { const array = [1, 2, 3, 4]; - array.reduce2(callback, 0); - - expect(callback).toHaveBeenCalledTimes(array.length); ; + expect(callback).toHaveBeenCalledTimes(array.length); }); it('should run callback array`s length - 1 times if no initialValue', () => { const array = [1, 2, 3, 4]; - array.reduce2(callback); - - expect(callback).toHaveBeenCalledTimes(array.length - 1); ; + expect(callback).toHaveBeenCalledTimes(array.length - 1); }); - it('should not run callback if array is empty', () => { - const array = []; - - array.reduce2(callback); - - expect(callback).toHaveBeenCalledTimes(0); ; + it('should throw TypeError if array is empty and no initialValue', () => { + expect(() => [].reduce2(callback)).toThrow(TypeError); }); - it('should run callback with correct arguments', () => { + it('should run callback with correct arguments when initialValue is provided', () => { const array = [1, 2, 3, 4]; - array.reduce2(callback, 0); - expect(callback).toHaveBeenNthCalledWith(1, 0, 1, 0, array); expect(callback).toHaveBeenNthCalledWith(2, 1, 2, 1, array); expect(callback).toHaveBeenNthCalledWith(3, 3, 3, 2, array); expect(callback).toHaveBeenNthCalledWith(4, 6, 4, 3, array); }); - it('should return initial value if array is empty', () => { + it('should pass correct arguments when no initialValue', () => { + const array = [10, 20, 30]; + array.reduce2(callback); + expect(callback).toHaveBeenNthCalledWith(1, 10, 20, 1, array); + expect(callback).toHaveBeenNthCalledWith(2, 30, 30, 2, array); + }); + + it('should return initial value if array is empty and initialValue is provided', () => { const array = []; const initialValue = 0; const result = array.reduce2(callback, initialValue); + expect(result).toBe(initialValue); + expect(callback).not.toHaveBeenCalled(); + }); - expect(result).toBe(initialValue); ; + // ----- Extra edge cases ----- + it('should skip empty slots in sparse arrays', () => { + const array = [1, , 3]; + const result = array.reduce2((acc, cur) => acc + cur, 0); + expect(result).toBe(4); }); - it('should return undefined if array is empty and no initial value', () => { - const array = []; + it('should include undefined and null elements', () => { + const array = [1, undefined, null, 2]; + const result = array.reduce2((acc, cur) => acc.concat([cur]), []); + expect(result).toEqual([1, undefined, null, 2]); + }); + + it('should return the only element if single-element array without initialValue', () => { + const array = [42]; const result = array.reduce2(callback); + expect(result).toBe(42); + expect(callback).not.toHaveBeenCalled(); + }); + + it('should run callback once for single-element array with initialValue', () => { + const array = [42]; + array.reduce2(callback, 10); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(10, 42, 0, array); + }); + + it('should handle element addition during iteration', () => { + const array = [1, 2, 3]; + const result = array.reduce2((acc, cur, i, arr) => { + if (i === 0) arr.push(4); + return acc + cur; + }, 0); + expect(result).toBe(10); + }); + + it('should skip deleted unvisited indices', () => { + const array = [1, 2, 3]; + const result = array.reduce2((acc, cur, i, arr) => { + if (i === 0) arr.pop(); + return acc; + }, 0); + expect(result).toBe(3); + }); + + it('should process elements strictly left-to-right', () => { + const array = [1, 2, 3]; + const seen = []; + array.reduce2((acc, cur, i) => { + seen.push([i, cur]); + return acc; + }, 0); + expect(seen).toEqual([[0, 1], [1, 2], [2, 3]]); + }); + + it('should work with string concatenation', () => { + const array = ['a', 'b', 'c']; + const result = array.reduce2((acc, cur) => acc + cur, ''); + expect(result).toBe('abc'); + }); - expect(result).toBe(undefined); ; + it('should work with objects as accumulator', () => { + const array = ['x', 'y']; + const result = array.reduce2((acc, cur, i) => { + acc[i] = cur; + return acc; + }, {}); + expect(result).toEqual({ 0: 'x', 1: 'y' }); }); }); From 29ad120a9b1b884ad901f82b0e69ef3f6ba4a81b Mon Sep 17 00:00:00 2001 From: Natalia Date: Tue, 30 Sep 2025 19:14:18 +0300 Subject: [PATCH 3/3] add task solution --- src/reduce.js | 44 +++++++++++---------- src/reduce.test.js | 98 +++++++++++++++++++++++----------------------- 2 files changed, 74 insertions(+), 68 deletions(-) diff --git a/src/reduce.js b/src/reduce.js index fcfb02e..2afd99d 100644 --- a/src/reduce.js +++ b/src/reduce.js @@ -2,38 +2,42 @@ /** * @param {function} callback - * @param {*} startValue - * + * @param {*} initialValue * @returns {*} */ -function reduce(callback, startValue) { +function reduce(callback, initialValue) { + if (this == null) { + throw new TypeError('Array.prototype.reduce called on null or undefined'); + } + if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } - const arr = this; - let hasInitialValue = arguments.length > 1; - let accumulator; - let startIndex = 0; + const arr = Object(this); + const len = arr.length >>> 0; - if (arr.length === 0 && !hasInitialValue) { - throw new TypeError('Reduce of empty array with no initial value'); - } + let k = 0; + let accumulator; - if (hasInitialValue) { - accumulator = startValue; + if (arguments.length > 1) { + accumulator = initialValue; } else { - - while (startIndex < arr.length && !(startIndex in arr)) { - startIndex++; + // шукаємо перший існуючий елемент + while (k < len && !(k in arr)) { + k++; + } + + if (k >= len) { + throw new TypeError('Reduce of empty array with no initial value'); } - accumulator = arr[startIndex]; - startIndex++; + + accumulator = arr[k++]; } - for (let i = startIndex; i < arr.length; i++) { - if (i in arr) { - accumulator = callback(accumulator, arr[i], i, arr); + for (; k < len; k++) { + if (k in arr) { + accumulator = callback(accumulator, arr[k], k, arr); } } diff --git a/src/reduce.test.js b/src/reduce.test.js index 50bc55c..b296d71 100644 --- a/src/reduce.test.js +++ b/src/reduce.test.js @@ -1,9 +1,11 @@ 'use strict'; +// eslint-disable-next-line no-extend-native const { reduce } = require('./reduce'); describe('reduce', () => { beforeAll(() => { + // eslint-disable-next-line no-extend-native Array.prototype.reduce2 = reduce; }); @@ -23,11 +25,9 @@ describe('reduce', () => { it('should not mutate array', () => { const array = [1, 2, 3, 4]; - const original = array.slice(); - + const copy = [...array]; array.reduce2(callback, 0); - - expect(array).toEqual(original); + expect(array).toEqual(copy); }); it('should run callback array`s length times if initialValue is provided', () => { @@ -42,87 +42,89 @@ describe('reduce', () => { expect(callback).toHaveBeenCalledTimes(array.length - 1); }); - it('should throw TypeError if array is empty and no initialValue', () => { - expect(() => [].reduce2(callback)).toThrow(TypeError); + it('should not run callback if array is empty and initialValue is provided', () => { + const array = []; + array.reduce2(callback, 0); + expect(callback).toHaveBeenCalledTimes(0); }); it('should run callback with correct arguments when initialValue is provided', () => { const array = [1, 2, 3, 4]; array.reduce2(callback, 0); + expect(callback).toHaveBeenNthCalledWith(1, 0, 1, 0, array); expect(callback).toHaveBeenNthCalledWith(2, 1, 2, 1, array); expect(callback).toHaveBeenNthCalledWith(3, 3, 3, 2, array); expect(callback).toHaveBeenNthCalledWith(4, 6, 4, 3, array); }); - it('should pass correct arguments when no initialValue', () => { - const array = [10, 20, 30]; - array.reduce2(callback); - expect(callback).toHaveBeenNthCalledWith(1, 10, 20, 1, array); - expect(callback).toHaveBeenNthCalledWith(2, 30, 30, 2, array); - }); - - it('should return initial value if array is empty and initialValue is provided', () => { + it('should return initial value if array is empty', () => { const array = []; const initialValue = 0; const result = array.reduce2(callback, initialValue); + expect(result).toBe(initialValue); - expect(callback).not.toHaveBeenCalled(); }); - // ----- Extra edge cases ----- + it('should throw if array is empty and no initial value', () => { + const array = []; + expect(() => array.reduce2(callback)).toThrow(TypeError); + }); + it('should skip empty slots in sparse arrays', () => { + // eslint-disable-next-line no-sparse-arrays const array = [1, , 3]; const result = array.reduce2((acc, cur) => acc + cur, 0); expect(result).toBe(4); }); - it('should include undefined and null elements', () => { - const array = [1, undefined, null, 2]; - const result = array.reduce2((acc, cur) => acc.concat([cur]), []); - expect(result).toEqual([1, undefined, null, 2]); + it('should handle leading holes without initialValue', () => { + // eslint-disable-next-line no-sparse-arrays + const array = [, , 3, 4]; + const result = array.reduce2((acc, cur) => acc + cur); + expect(result).toBe(7); // accumulator = 3, then adds 4 }); - it('should return the only element if single-element array without initialValue', () => { - const array = [42]; - const result = array.reduce2(callback); - expect(result).toBe(42); - expect(callback).not.toHaveBeenCalled(); + it('should throw on all-holes array without initialValue', () => { + const array = new Array(3); // [ , , , ] + expect(() => array.reduce2(callback)).toThrow(TypeError); }); - it('should run callback once for single-element array with initialValue', () => { - const array = [42]; - array.reduce2(callback, 10); - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith(10, 42, 0, array); + it('should throw if callback is not a function', () => { + const array = [1, 2]; + expect(() => array.reduce2(null)).toThrow(TypeError); }); - it('should handle element addition during iteration', () => { - const array = [1, 2, 3]; - const result = array.reduce2((acc, cur, i, arr) => { - if (i === 0) arr.push(4); + it('should have undefined as callback this in strict mode', () => { + const array = [1]; + let recordedThis; + array.reduce2(function (acc, cur) { + recordedThis = this; return acc + cur; }, 0); - expect(result).toBe(10); + expect(recordedThis).toBeUndefined(); }); - it('should skip deleted unvisited indices', () => { - const array = [1, 2, 3]; - const result = array.reduce2((acc, cur, i, arr) => { - if (i === 0) arr.pop(); - return acc; - }, 0); + it('should support array-like objects via call', () => { + const obj = { 0: 1, 1: 2, length: 2 }; + const result = reduce.call(obj, (acc, cur) => acc + cur, 0); expect(result).toBe(3); }); - it('should process elements strictly left-to-right', () => { + it('should throw if called on null/undefined', () => { + expect(() => reduce.call(null, callback, 0)).toThrow(TypeError); + expect(() => reduce.call(undefined, callback, 0)).toThrow(TypeError); + }); + + it('should ignore non-numeric properties', () => { const array = [1, 2, 3]; - const seen = []; - array.reduce2((acc, cur, i) => { - seen.push([i, cur]); - return acc; - }, 0); - expect(seen).toEqual([[0, 1], [1, 2], [2, 3]]); + // @ts-ignore + array.foo = 10; + // element beyond initial length + array[100] = 100; + + const result = array.reduce2((acc, cur) => acc + cur, 0); + expect(result).toBe(6); // only 1+2+3 }); it('should work with string concatenation', () => {