diff --git a/projects/array-objects/index.js b/projects/array-objects/index.js new file mode 100644 index 000000000..84950c9c3 --- /dev/null +++ b/projects/array-objects/index.js @@ -0,0 +1,75 @@ +/* ДЗ 2 - работа с массивами и объектами */ + +/* + Задание 1: + + Напишите аналог встроенного метода forEach для работы с массивами. + Посмотрите как работает forEach и повторите это поведение для массива, который будет передан в параметре array + + Пример: + forEach([1, 2, 3], (el) => console.log(el)); // выведет каждый элемент массива + */ +function forEach(array, callback) { + for (let i = 0; i < array.length; i++) { + callback(array[i], i, array); + } +} + +/* + Задание 2: + + Напишите аналог встроенного метода map для работы с массивами. + Посмотрите как работает map и повторите это поведение для массива, который будет передан в параметре array + + Пример: + const newArray = map([1, 2, 3], (el) => el ** 2); + console.log(newArray); // выведет [1, 4, 9] + */ +function map(array, callback) { + const newArray = []; + for (let i = 0; i < array.length; i++) { + newArray.push(callback(array[i], i, array)); + } + + return newArray; +} + +/* + Задание 3: + + Напишите аналог встроенного метода reduce для работы с массивами. + Посмотрите как работает reduce и повторите это поведение для массива, который будет передан в параметре array + + Пример: + const sum = reduce([1, 2, 3], (all, current) => all + current); + console.log(sum); // выведет 6 + */ +function reduce(array, callback, initial = 0) { + let accumulator = initial === 0 ? array[0] : initial; + const startIndex = initial === 0 ? 1 : 0; + for (let i = startIndex; i < array.length; i++) { + accumulator = callback(accumulator, array[i], i, array); + console.log(i); + } + + return accumulator; +} + +/* + Задание 4: + + Функция должна перебрать все свойства объекта, преобразовать их имена в верхний регистр и вернуть в виде массива + + Пример: + const keys = upperProps({ name: 'Сергей', lastName: 'Петров' }); + console.log(keys) // выведет ['NAME', 'LASTNAME'] + */ +function upperProps(obj) { + const array = Object.keys(obj); + for (let i = 0; i < array.length; i++) { + array[i] = array[i].toUpperCase(); + } + return array; +} + +export { forEach, map, reduce, upperProps }; diff --git a/projects/array-objects/index.spec.js b/projects/array-objects/index.spec.js new file mode 100644 index 000000000..eddc0bfdc --- /dev/null +++ b/projects/array-objects/index.spec.js @@ -0,0 +1,78 @@ +import { forEach, map, reduce, upperProps } from './index'; + +describe('ДЗ 3 - объекты и массивы', () => { + describe('forEach', () => { + it('должна вызывать функцию для каждого элемента массива', () => { + const array = [1, 2, 3]; + const fn = jest.fn(); + + forEach(array, fn); + + for (let i = 0; i < array.length; i++) { + expect(fn).nthCalledWith(i + 1, array[i], i, array); + } + }); + }); + + describe('map', () => { + it('должна вызывать функцию для каждого элемента массива и не изменять оригинальный массив', () => { + const originalArray = [4, 5, 6]; + const array = [...originalArray]; + const modified = array.map((el) => el ** 2); + const fn = jest.fn((el) => el ** 2); + + expect(map(array, fn)).toEqual(modified); + expect(array).toEqual(originalArray); + + for (let i = 0; i < array.length; i++) { + expect(fn).nthCalledWith(i + 1, array[i], i, array); + } + }); + }); + + describe('reduce', () => { + it('должна вызывать функцию для каждого элемента и передавать предыдущий результат первым аргументом', () => { + const originalArray = [7, 8, 9]; + const array = [...originalArray]; + const modified = array.reduce((all, current) => all + current); + const fn = jest.fn((all, current) => all + current); + + expect(reduce(array, fn)).toEqual(modified); + expect(array).toEqual(originalArray); + + let sum = array[0]; + + for (let i = 1; i < array.length; i++) { + expect(fn).nthCalledWith(i, sum, array[i], i, array); + sum += array[i]; + } + }); + + it('должна учитывать initial', () => { + const originalArray = [1, 3, 5]; + const array = [...originalArray]; + const modified = array.reduce((all, current) => all + current, 10); + const fn = jest.fn((all, current) => all + current); + + expect(reduce(array, fn, 10)).toEqual(modified); + expect(array).toEqual(originalArray); + + let sum = 10; + + for (let i = 0; i < array.length; i++) { + expect(fn).nthCalledWith(i + 1, sum, array[i], i, array); + sum += array[i]; + } + }); + }); + + describe('upperProps', () => { + it('должна возвращать массив с именами свойств и преобразовывать эти имена в верхний регистр', () => { + const obj = { a: 1, b: 2 }; + const target = ['A', 'B']; + const result = upperProps(obj); + + expect(result).toEqual(target); + }); + }); +}); diff --git a/projects/exceptions/index.js b/projects/exceptions/index.js new file mode 100644 index 000000000..9915b3f4a --- /dev/null +++ b/projects/exceptions/index.js @@ -0,0 +1,168 @@ +/* ДЗ 3 - работа с исключениями и отладчиком */ + +/* + Задание 1: + + 1.1: Функция isAllTrue принимает массив в параметре array и другую функцию в параметре fn. + Нужно по-очереди запустить функцию fn для всех элементов массива. + isAllTrue должна вернуть true только если fn вернула true для всех элементов массива. + Если хотя бы для одного из элементов массива fn вернула false, то и isAllTrue должна вернуть false. + + 1.2: Необходимо выбрасывать исключение в случаях: + - array не массив или пустой массив (с текстом "empty array") + для проверки на массив вам может помочь функция Array.isArray() + - fn не является функцией (с текстом "fn is not a function") + для проверки на функцию вам может помочь оператор typeof + + Запрещено использовать встроенные методы для работы с массивами + + Пример: + isAllTrue([1, 2, 3, 4, 5], n => n < 10) // вернет true (потому что все элементы массива меньше 10) + isAllTrue([100, 2, 3, 4, 5], n => n < 10) // вернет false (потому что как минимум первый элемент больше 10) + */ +function isAllTrue(array, fn) { + if (!Array.isArray(array) | (array.length === 0)) throw new Error('empty array'); + else if (typeof fn !== 'function') throw new Error('fn is not a function'); + + const result = true; + for (let i = 0; i < array.length; i++) { + if (!fn(array[i])) return false; + } + + return result; +} + +/* + Задание 2: + + 2.1: Функция isSomeTrue принимает массив в параметре array и функцию в параметре fn. + Нужно по-очереди запустить функцию fn для всех элементов массива. + isSomeTrue должна вернуть true только если fn вернула true хотя бы для одного из элементов массива. + Если fn не вернула true ни для одного элементов массива, то и isSomeTrue должна вернуть false. + + 2.2: Необходимо выбрасывать исключение в случаях: + - array не массив или пустой массив (с текстом "empty array") + для проверки на массив вам может помочь функция Array.isArray() + - fn не является функцией (с текстом "fn is not a function") + для проверки на функцию вам может помочь оператор typeof + + Запрещено использовать встроенные методы для работы с массивами + + Пример: + isSomeTrue([1, 2, 30, 4, 5], n => n > 20) // вернет true (потому что в массиве есть хотя бы один элемент больше 20) + isSomeTrue([1, 2, 3, 4, 5], n => n > 20) // вернет false (потому что в массиве нет ни одного элемента больше 20) + */ +function isSomeTrue(array, fn) { + if (!Array.isArray(array) | (array.length === 0)) throw new Error('empty array'); + else if (typeof fn !== 'function') throw new Error('fn is not a function'); + + const result = false; + for (let i = 0; i < array.length; i++) { + if (fn(array[i])) return true; + } + + return result; +} + +/* + Задание 3: + + 3.1: Функция returnBadArguments принимает заранее неизвестное количество аргументов, первым из которых является функция fn + returnBadArguments должна поочередно запустить fn для каждого переданного аргумента (кроме самой fn) + + 3.2: returnBadArguments должна вернуть массив аргументов, для которых fn выбросила исключение + + 3.3: Необходимо выбрасывать исключение в случаях: + - fn не является функцией (с текстом "fn is not a function") + для проверки на функцию вам может помочь оператор typeof + */ +function returnBadArguments(...arg) { + const errorArgs = []; + if (typeof arg[0] !== 'function') throw new Error('fn is not a function'); + for (let i = 1; i < arg.length; i++) { + try { + arg[0](arg[i]); + } catch (e) { + errorArgs.push(arg[i]); + } + } + + return errorArgs; +} + +/* + Задание 4: + + 4.1: Функция calculator имеет параметр number (по умолчанию - 0) + + 4.2: Функция calculator должна вернуть объект, у которого должно быть несколько методов: + - sum - складывает number с переданными аргументами + - dif - вычитает из number переданные аргументы + - div - делит number на первый аргумент. Результат делится на следующий аргумент (если передан) и так далее + - mul - умножает number на первый аргумент. Результат умножается на следующий аргумент (если передан) и так далее + + Количество передаваемых в методы аргументов заранее неизвестно + + 4.3: Необходимо выбрасывать исключение в случаях: + - number не является числом (с текстом "number is not a number") + - какой-либо из аргументов div является нулем (с текстом "division by 0") + + Пример: + const myCalc = calculator(10); + + console.log(calc.sum(1, 2, 3)); // выведет 16 (10 + 1 + 2 + 3) + console.log(calc.dif(1, 2, 3)); // выведет 5 (10 - 1 - 2 - 3) + console.log(calc.mul(1, 2, 3)); // выведет 60 (10 * 1 * 2 * 3) + console.log(calc.div(2, 2)); // выведет 2.5 (10 / 2 / 2) + console.log(calc.div(2, 0)); // выбросит исключение, потому что один из аргументов равен 0 + */ +function calculator(number = 0) { + if (typeof number !== 'number') throw new Error('number is not a number'); + + return { + sum(...arg) { + let initial = number; + for (let i = 0; i < arg.length; i++) { + if (arg[i] === 0) throw new Error('division by 0'); + + initial += arg[i]; + } + + return initial; + }, + dif(...arg) { + let initial = number; + for (let i = 0; i < arg.length; i++) { + if (arg[i] === 0) throw new Error('division by 0'); + + initial -= arg[i]; + } + + return initial; + }, + div(...arg) { + let initial = number; + for (let i = 0; i < arg.length; i++) { + if (arg[i] === 0) throw new Error('division by 0'); + + initial /= arg[i]; + } + + return initial; + }, + mul(...arg) { + let initial = number; + for (let i = 0; i < arg.length; i++) { + if (arg[i] === 0) throw new Error('division by 0'); + + initial *= arg[i]; + } + + return initial; + }, + }; +} + +/* При решении задач, постарайтесь использовать отладчик */ + +export { isAllTrue, isSomeTrue, returnBadArguments, calculator }; diff --git a/projects/exceptions/index.spec.js b/projects/exceptions/index.spec.js new file mode 100644 index 000000000..bb9b4d0c1 --- /dev/null +++ b/projects/exceptions/index.spec.js @@ -0,0 +1,178 @@ +import { calculator, isAllTrue, isSomeTrue, returnBadArguments } from './index'; + +describe('ДЗ 2 - работа с исключениями и отладчиком', () => { + describe('isAllTrue', () => { + it('должна вызывать fn для всех элементов массива', () => { + const array = ['l', 's']; + const pass = []; + + isAllTrue(array, (e) => pass.push(e)); + + expect(pass).toEqual(array); + }); + + it('должна вернуть true, если fn вернула true для всех элементов массива', () => { + const array = [1, 2, 3]; + const result = isAllTrue(array, Number.isFinite); + + expect(result); + }); + + it('должна вернуть false, если fn вернула false хотя бы для одного элемента массива', () => { + const array = [1, 2, 3]; + + array.push('ls'); + const result = isAllTrue(array, Number.isFinite); + + expect(!result); + }); + + it('должна выбросить исключение, если передан пустой массив', () => { + expect(() => isAllTrue([], () => {})).toThrow('empty array'); + }); + + it('должна выбросить исключение, если передан не массив', () => { + expect(() => isAllTrue(':(', () => {})).toThrow('empty array'); + expect(() => isAllTrue({}, () => {})).toThrow('empty array'); + }); + + it('должна выбросить исключение, если fn не функция', () => { + const array = [1, 2, 3]; + + expect(() => isAllTrue(array, ':(')).toThrow('fn is not a function'); + }); + }); + + describe('isSomeTrue', () => { + it('должна вернуть true, если fn вернула true хотя бы для одного элемента массива', () => { + const array = ['l', 's', 3]; + const result = isSomeTrue(array, Number.isFinite); + + expect(result); + }); + + it('должна вернуть false, если fn не вернула true хотя бы для одного элемента массива', () => { + const array = ['l', 's']; + const result = isSomeTrue(array, Number.isFinite); + + expect(!result); + }); + + it('должна выбросить исключение, если передан пустой массив', () => { + expect(() => isSomeTrue([], () => {})).toThrow('empty array'); + }); + + it('должна выбросить исключение, если передан не массив', () => { + expect(() => isSomeTrue(':(', () => {})).toThrow('empty array'); + expect(() => isSomeTrue({}, () => {})).toThrow('empty array'); + }); + + it('должна выбросить исключение, если fn не функция', () => { + const array = [1, 2, 3]; + + expect(() => isSomeTrue(array, ':(')).toThrow('fn is not a function'); + }); + }); + + describe('returnBadArguments', () => { + it('должна вызывать fn для всех элементов массива', () => { + const array = [1, 2, 3]; + const pass = []; + + returnBadArguments((e) => pass.push(e), ...array); + + expect(pass).toEqual(array); + }); + + it('должна вернуть массив с аргументами, для которых fn выбрасила исключение', () => { + const evenNumbers = [2, 4, 6]; + const oddNumbers = [1, 3, 5]; + const fn = (a) => { + if (a % 2 !== 0) { + throw new Error('not even'); + } + }; + const result = returnBadArguments(fn, ...evenNumbers, ...oddNumbers); + + expect(result).toEqual(oddNumbers); + }); + + it('должна вернуть массив пустой массив, если не передано дополнительных аргументов', () => { + const fn = () => ':)'; + const result = returnBadArguments(fn); + + expect(result.length).toBe(0); + }); + + it('должна выбросить исключение, если fn не функция', () => { + expect(() => returnBadArguments(':(')).toThrow('fn is not a function'); + }); + }); + + describe('calculator', () => { + it('должна возвращать объект с методами', () => { + const calc = calculator(); + + expect(Object.keys(calc)).toEqual(['sum', 'dif', 'div', 'mul']); + }); + + it('метод sum должен складывать аргументы', () => { + const initialValue = 20; + const calc = calculator(initialValue); + const args = [1, 2, 3]; + + expect(calc.sum(...args)).toBe( + args.reduce((prev, current) => prev + current, initialValue) + ); + }); + + it('метод dif должен вычитать аргументы', () => { + const initialValue = 10; + const calc = calculator(initialValue); + const args = [1, 2, 3]; + + expect(calc.dif(...args)).toBe( + args.reduce((prev, current) => prev - current, initialValue) + ); + }); + + it('метод div должен делить аргументы', () => { + const initialValue = 20; + const calc = calculator(initialValue); + const args = [1, 2]; + + expect(calc.div(...args)).toBe( + args.reduce((prev, current) => prev / current, initialValue) + ); + }); + + it('метод div должен выбрасывать исключение, если хотя бы один из аргументов равен 0', () => { + const initialValue = 20; + const calc = calculator(initialValue); + const args = [1, 2, 0]; + + expect(() => calc.div(...args)).toThrow('division by 0'); + }); + + it('метод mul должен умножать аргументы', () => { + const initialValue = 10; + const calc = calculator(initialValue); + const args = [1, 2]; + + expect(calc.mul(...args)).toBe( + args.reduce((prev, current) => prev * current, initialValue) + ); + }); + + it('функция должна выбрасывать исключение, если number не является числом', () => { + expect(() => calculator(':(')).toThrow('number is not a number'); + }); + + it('значение по умолчанию для аргумента number должно быть равно 0', () => { + const calc = calculator(); + const args = [1, 2]; + + expect(calc.sum(...args)).toBe(args.reduce((prev, current) => prev + current)); + }); + }); +}); diff --git a/projects/loft-photo-1/friends.json b/projects/loft-photo-1/friends.json new file mode 100644 index 000000000..b861e0c00 --- /dev/null +++ b/projects/loft-photo-1/friends.json @@ -0,0 +1,32 @@ +[ + { + "id": 0, + "avatar": "https://via.placeholder.com/100?text=avatar", + "firstName": "Adrian", + "lastName": "Norman" + }, + { + "id": 1, + "avatar": "https://via.placeholder.com/100?text=avatar", + "firstName": "Gail", + "lastName": "Norton" + }, + { + "id": 2, + "avatar": "https://via.placeholder.com/100?text=avatar", + "firstName": "Molina", + "lastName": "Rodgers" + }, + { + "id": 3, + "avatar": "https://via.placeholder.com/100?text=avatar", + "firstName": "Adams", + "lastName": "Parrish" + }, + { + "id": 4, + "avatar": "https://via.placeholder.com/100?text=avatar", + "firstName": "Mercer", + "lastName": "Wiggins" + } +] \ No newline at end of file diff --git a/projects/loft-photo-1/index.js b/projects/loft-photo-1/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/projects/loft-photo-1/model.js b/projects/loft-photo-1/model.js new file mode 100644 index 000000000..1e31e33b3 --- /dev/null +++ b/projects/loft-photo-1/model.js @@ -0,0 +1,9 @@ +// eslint-disable-next-line no-unused-vars +import photosDB from './photos.json'; +// eslint-disable-next-line no-unused-vars +import friendsDB from './friends.json'; + +export default { + getRandomElement(array) {}, + getNextPhoto() {}, +}; diff --git a/projects/loft-photo-1/photos.json b/projects/loft-photo-1/photos.json new file mode 100644 index 000000000..a299e6057 --- /dev/null +++ b/projects/loft-photo-1/photos.json @@ -0,0 +1,72 @@ +{ + "0": [ + { + "id": 10, + "url": "https://via.placeholder.com/360x680?text=photo 1 for Adrian Norman" + }, + { + "id": 11, + "url": "https://via.placeholder.com/360x680?text=photo 2 for Adrian Norman" + }, + { + "id": 12, + "url": "https://via.placeholder.com/360x680?text=photo 3 for Adrian Norman" + } + ], + "1": [ + { + "id": 20, + "url": "https://via.placeholder.com/360x680?text=photo 1 for Gail Norton" + }, + { + "id": 21, + "url": "https://via.placeholder.com/360x680?text=photo 2 for Gail Norton" + }, + { + "id": 22, + "url": "https://via.placeholder.com/360x680?text=photo 3 for Gail Norton" + } + ], + "2": [ + { + "id": 30, + "url": "https://via.placeholder.com/360x680?text=photo 1 for Molina Rodgers" + }, + { + "id": 31, + "url": "https://via.placeholder.com/360x680?text=photo 2 for Molina Rodgers" + }, + { + "id": 32, + "url": "https://via.placeholder.com/360x680?text=photo 3 for Molina Rodgers" + } + ], + "3": [ + { + "id": 40, + "url": "https://via.placeholder.com/360x680?text=photo 1 for Adams Parrish" + }, + { + "id": 41, + "url": "https://via.placeholder.com/360x680?text=photo 2 for Adams Parrish" + }, + { + "id": 42, + "url": "https://via.placeholder.com/360x680?text=photo 3 for Adams Parrish" + } + ], + "4": [ + { + "id": 50, + "url": "https://via.placeholder.com/360x680?text=photo 1 for Mercer Wiggins" + }, + { + "id": 51, + "url": "https://via.placeholder.com/360x680?text=photo 2 for Mercer Wiggins" + }, + { + "id": 52, + "url": "https://via.placeholder.com/360x680?text=photo 3 for Mercer Wiggins" + } + ] +} \ No newline at end of file diff --git a/projects/loft-photo-1/readme.md b/projects/loft-photo-1/readme.md new file mode 100644 index 000000000..cd26a9f0e --- /dev/null +++ b/projects/loft-photo-1/readme.md @@ -0,0 +1,71 @@ +## Массивы и объекты + +Реализуйте объект с двумя методами: + +- `getRandomElement(array)` +- `getNextPhoto()` + +### `getRandomElement(array)` + +Метод принимает массив в параметре `array` и должен вернуть **случайный** элемент из этого масства при каждом вызове. + +Например: + +```js +const fruits = ['банан', 'яблоко', 'груша']; + +console.log(getRandomElement(fruits)); // груша +console.log(getRandomElement(fruits)); // банан +console.log(getRandomElement(fruits)); // банан +``` + +Для получения случайного числа может пригодиться метод [Math.random()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Math/random). Этот метод возвращает случайное число между 0 и 1 (например 0.548) + +Вот так можно получить число от 0 до 10 (например): + +```js +console.log(Math.random() * 10); // 5.754356 +console.log(Math.random() * 10); // 2.12864 +``` + +А при помощи [parseInt](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/parseInt) можно убрать дробную часть: + +```js +console.log(parseInt(Math.random() * 10)); // 8 +console.log(parseInt(Math.random() * 10)); // 3 +``` + +### `getNextPhoto()` + +При каждом вызове метод должен вернуть информацию со случайным другом и случайной его фотографией. + +Информация должна быть возвращена в виде объекта из двух полей: `friend` и `url`. + +Например: + +```js +let photo = getNextPhoto(); + +console.log(photo.friend); // { firstName: 'Иван' } +console.log(photo.url); // https://... + +photo = getNextPhoto(); + +console.log(photo.friend); // { firstName: 'Сергей' } +console.log(photo.url); // https://... +``` + +Пример списка друзей и фотографий можно найти в файлах [friends.json](friends.json) и [photos.json](photos.json). + +В файле [friends.json](friends.json) хранится массив со списком друзей. У каждого друга есть имя, фамилия и идентификатор. + +В файле [photos.json](photos.json) хранится объект, ключами которого являются идентификаторы друзей, а значениями - массив фотографий этого друга. +У каждой фотографии есть идентификатор и url. + +Вот так, например, можно получить все фотографии друга с идентификатором `1`: + +```js +const photosDB = require('./photos.json'); + +console.log(photosDB[1]) // [ ... ] +```