From e651e3bc73f092b5f434ca71d7633ec7dfd556d9 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Sun, 30 May 2021 21:54:27 +0300 Subject: [PATCH 01/40] Task1 --- package.json | 6 +++--- task/01-strings-tasks.js | 44 +++++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 712b264fa..1f387c39b 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "devDependencies": { "mocha": "^2.3.4" }, - "repository" : { - "type" : "git", - "url" : "https://github.com/rolling-scopes-school/js-assignments.git" + "repository": { + "type": "git", + "url": "https://github.com/rolling-scopes-school/js-assignments.git" } } diff --git a/task/01-strings-tasks.js b/task/01-strings-tasks.js index e28054657..286dd0cfe 100644 --- a/task/01-strings-tasks.js +++ b/task/01-strings-tasks.js @@ -22,7 +22,7 @@ * '', 'bb' => 'bb' */ function concatenateStrings(value1, value2) { - throw new Error('Not implemented'); + return value1 + value2 ; } @@ -38,7 +38,7 @@ function concatenateStrings(value1, value2) { * '' => 0 */ function getStringLength(value) { - throw new Error('Not implemented'); + return value.length; } /** @@ -55,7 +55,7 @@ function getStringLength(value) { * 'Chuck','Norris' => 'Hello, Chuck Norris!' */ function getStringFromTemplate(firstName, lastName) { - throw new Error('Not implemented'); + return "Hello, " +firstName+ ' '+lastName+'!'; } /** @@ -69,10 +69,9 @@ function getStringFromTemplate(firstName, lastName) { * 'Hello, Chuck Norris!' => 'Chuck Norris' */ function extractNameFromTemplate(value) { - throw new Error('Not implemented'); + return value.slice(7, -1); } - /** * Returns a first char of the given string. * @@ -84,7 +83,7 @@ function extractNameFromTemplate(value) { * 'cat' => 'c' */ function getFirstChar(value) { - throw new Error('Not implemented'); + return value[0]; } /** @@ -99,7 +98,7 @@ function getFirstChar(value) { * '\tHello, World! ' => 'Hello, World!' */ function removeLeadingAndTrailingWhitespaces(value) { - throw new Error('Not implemented'); + return value.trim(); } /** @@ -114,7 +113,7 @@ function removeLeadingAndTrailingWhitespaces(value) { * 'cat', 3 => 'catcatcat' */ function repeatString(value, count) { - throw new Error('Not implemented'); + return value.repeat(count); } /** @@ -130,7 +129,7 @@ function repeatString(value, count) { * 'ABABAB','BA' => 'ABAB' */ function removeFirstOccurrences(str, value) { - throw new Error('Not implemented'); + return str.replace(value, ''); } /** @@ -145,7 +144,7 @@ function removeFirstOccurrences(str, value) { * '' => 'a' */ function unbracketTag(str) { - throw new Error('Not implemented'); + return str.slice(1, str.length-1); } @@ -160,7 +159,7 @@ function unbracketTag(str) { * 'abcdefghijklmnopqrstuvwxyz' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' */ function convertToUpperCase(str) { - throw new Error('Not implemented'); + return str.toUpperCase(); } /** @@ -174,7 +173,7 @@ function convertToUpperCase(str) { * 'info@gmail.com' => ['info@gmail.com'] */ function extractEmails(str) { - throw new Error('Not implemented'); + return str.split(';'); } /** @@ -201,7 +200,9 @@ function extractEmails(str) { * */ function getRectangleString(width, height) { - throw new Error('Not implemented'); + return '┌' + '─'.repeat(width - 2) + '┐\n' + + ('│' + ' '.repeat(width - 2) + '│\n').repeat(height - 2) + + '└' + '─'.repeat(width - 2) + '┘\n'; } @@ -221,7 +222,11 @@ function getRectangleString(width, height) { * */ function encodeToRot13(str) { - throw new Error('Not implemented'); + var input = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + var output = 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'; + var index = x => input.indexOf(x); + var translate = x => index(x) > -1 ? output[index(x)] : x; + return str.split('').map(translate).join(''); } /** @@ -238,7 +243,7 @@ function encodeToRot13(str) { * isString(new String('test')) => true */ function isString(value) { - throw new Error('Not implemented'); + return typeof value === 'string' || value instanceof String; } @@ -267,7 +272,14 @@ function isString(value) { * 'K♠' => 51 */ function getCardId(value) { - throw new Error('Not implemented'); + const cards = [ + 'A♣','2♣','3♣','4♣','5♣','6♣','7♣','8♣','9♣','10♣','J♣','Q♣','K♣', + 'A♦','2♦','3♦','4♦','5♦','6♦','7♦','8♦','9♦','10♦','J♦','Q♦','K♦', + 'A♥','2♥','3♥','4♥','5♥','6♥','7♥','8♥','9♥','10♥','J♥','Q♥','K♥', + 'A♠','2♠','3♠','4♠','5♠','6♠','7♠','8♠','9♠','10♠','J♠','Q♠','K♠' + ]; + + return cards.indexOf(value); } From 0110568d0b6e7ed64f576f38cb83ae6a688d0700 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Sun, 30 May 2021 23:09:26 +0300 Subject: [PATCH 02/40] Task3 --- task/03-date-tasks.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/task/03-date-tasks.js b/task/03-date-tasks.js index 83c6266bc..eaf778a51 100644 --- a/task/03-date-tasks.js +++ b/task/03-date-tasks.js @@ -22,7 +22,7 @@ * 'Sun, 17 May 1998 03:00:00 GMT+01' => Date() */ function parseDataFromRfc2822(value) { - throw new Error('Not implemented'); + return new Date(value); } /** @@ -37,7 +37,7 @@ function parseDataFromRfc2822(value) { * '2016-01-19T08:07:37Z' => Date() */ function parseDataFromIso8601(value) { - throw new Error('Not implemented'); + return new Date(value); } @@ -56,7 +56,9 @@ function parseDataFromIso8601(value) { * Date(2015,1,1) => false */ function isLeapYear(date) { - throw new Error('Not implemented'); + if ((date.getFullYear() % 4 == 0 && (date.getFullYear() % 100 != 0)) || date.getFullYear() % 400 == 0) + return true; + return false; } @@ -76,14 +78,14 @@ function isLeapYear(date) { * Date(2000,1,1,10,0,0), Date(2000,1,1,15,20,10,453) => "05:20:10.453" */ function timeSpanToString(startDate, endDate) { - throw new Error('Not implemented'); + return (new Date(endDate - startDate)).toISOString().slice(11, -1); } /** * Returns the angle (in radians) between the hands of an analog clock for the specified Greenwich time. * If you have problem with solution please read: https://en.wikipedia.org/wiki/Clock_angle_problem - * + * * @param {date} date * @return {number} * @@ -94,7 +96,14 @@ function timeSpanToString(startDate, endDate) { * Date.UTC(2016,3,5,21, 0) => Math.PI/2 */ function angleBetweenClockHands(date) { - throw new Error('Not implemented'); + let h = new Date(date).getUTCHours(); + h = h % 12; + let min = new Date(date).getUTCMinutes(); + let angle = Math.abs(1/2 * (60 * h - 11 * min)); + if (angle > 180) { + angle = (360 - angle); + } + return angle / 180 * Math.PI; } From 3a19aa9bfcd84fd16c5b1af7ce00d7ac74f1e69d Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Sun, 30 May 2021 23:12:26 +0300 Subject: [PATCH 03/40] Task2 --- test/02-numbers-tests.js | 356 ++++++++++++++++++++++++++++----------- 1 file changed, 255 insertions(+), 101 deletions(-) diff --git a/test/02-numbers-tests.js b/test/02-numbers-tests.js index d86b3e940..dc91648f2 100644 --- a/test/02-numbers-tests.js +++ b/test/02-numbers-tests.js @@ -1,103 +1,257 @@ 'use strict'; -var assert = require('assert'); -var tasks = require('../task/02-numbers-tasks'); -it.optional = require('../extensions/it-optional'); - -describe('02-numbers-tasks', function() { - - it.optional('getRectangleArea should return a square of rectangle', function() { - assert.equal(50, tasks.getRectangleArea(5, 10)); - assert.equal(25, tasks.getRectangleArea(5, 5)); - }); - - it.optional('getCicleCircumference should return a circumference of cicle', function() { - assert.equal(31.41592653589793, tasks.getCicleCircumference(5)); - assert.equal(19.729201864543903, tasks.getCicleCircumference(3.14)); - assert.equal(0, tasks.getCicleCircumference(0)); - }); - - it.optional('getAverage should return an average of two numbers', function() { - assert.equal(5, tasks.getAverage(5, 5)); - assert.equal(5, tasks.getAverage(10, 0)); - assert.equal(0, tasks.getAverage(-3, 3)); - assert.equal(Number.MAX_VALUE-1, tasks.getAverage(Number.MAX_VALUE-2, Number.MAX_VALUE)); - assert.equal(Number.MAX_VALUE / 4, tasks.getAverage(Number.MAX_VALUE, -Number.MAX_VALUE / 2)); - }); - - it.optional('getDistanceBetweenPoints should return a distance between points', function() { - assert.equal(1, tasks.getDistanceBetweenPoints(0, 0, 0, 1)); - assert.equal(1, tasks.getDistanceBetweenPoints(0, 0, 1, 0)); - assert.equal(18.027756377319946, tasks.getDistanceBetweenPoints(-5, 0, 10, -10)); - }); - - it.optional('getLinearEquationRoot should return a root of linear equation', function() { - assert.equal(2, tasks.getLinearEquationRoot(5, -10)); - assert.equal(-8, tasks.getLinearEquationRoot(1, 8)); - assert.equal(0, tasks.getLinearEquationRoot(5, 0)); - }); - - it.optional('getAngleBetweenVectors should return a angle (in radians) between two linear vectors', function() { - assert.equal(Math.PI/2, tasks.getAngleBetweenVectors(1, 0, 0, 1)); - assert.equal(Math.PI, tasks.getAngleBetweenVectors(0, 1, 0, -1)); - assert.equal(Math.PI/2, tasks.getAngleBetweenVectors(0, -1, 1, 0)); - assert.equal(0, tasks.getAngleBetweenVectors(0, 1, 0, 1)); - }); - - it.optional('getLastDigit should return a last digit of the number', function() { - assert.equal(0, tasks.getLastDigit(100)); - assert.equal(7, tasks.getLastDigit(37)); - assert.equal(5, tasks.getLastDigit(5)); - assert.equal(0, tasks.getLastDigit(0)); - }); - - it.optional('parseNumberFromString should return a number from the given string representation', function() { - assert.equal(100, tasks.parseNumberFromString('100')); - assert.equal(37, tasks.parseNumberFromString('37')); - assert.equal(-525.5, tasks.parseNumberFromString('-525.5')); - }); - - it.optional('getParallelipidedDiagonal should return a diagonal length of the rectagular parallepiped', function() { - assert.equal(Math.sqrt(3), tasks.getParallelipidedDiagonal(1,1,1)); - assert.equal(Math.sqrt(27), tasks.getParallelipidedDiagonal(3,3,3)); - //assert.equal(Math.sqrt(14), tasks.getParallelipidedDiagonal(1,2,3)); - }); - - it.optional('roundToPowerOfTen should return an number rounded to specified power of 10', function() { - assert.equal(1234, tasks.roundToPowerOfTen(1234,0)); - assert.equal(1230, tasks.roundToPowerOfTen(1234,1)); - assert.equal(1200, tasks.roundToPowerOfTen(1234,2)); - assert.equal(1000, tasks.roundToPowerOfTen(1234,3)); - - assert.equal(9678, tasks.roundToPowerOfTen(9678,0)); - assert.equal(9680, tasks.roundToPowerOfTen(9678,1)); - assert.equal(9700, tasks.roundToPowerOfTen(9678,2)); - assert.equal(10000, tasks.roundToPowerOfTen(9678,3)); - }); - - it.optional('isPrime should return true if specified number is prime', function() { - assert.equal(true, tasks.isPrime(2), "2"); - assert.equal(true, tasks.isPrime(3), "3"); - assert.equal(false, tasks.isPrime(4), "4"); - assert.equal(true, tasks.isPrime(5), "5"); - assert.equal(false, tasks.isPrime(6), "6"); - assert.equal(true, tasks.isPrime(7), "7"); - assert.equal(false, tasks.isPrime(8), "8"); - assert.equal(false, tasks.isPrime(9), "9"); - assert.equal(false, tasks.isPrime(10), "10"); - assert.equal(true, tasks.isPrime(11), "11"); - assert.equal(false, tasks.isPrime(12), "12"); - assert.equal(true, tasks.isPrime(13), "13"); - assert.equal(true, tasks.isPrime(113), "113"); - assert.equal(false, tasks.isPrime(119), "119"); - }); - - it.optional('toNumber should convert any value to number or return the default', function() { - assert.equal(0, tasks.toNumber(null, 0)); - assert.equal(0, tasks.toNumber('test', 0)); - assert.equal(1, tasks.toNumber('1', 0)); - assert.equal(42, tasks.toNumber(42, 0)); - assert.equal(42, tasks.toNumber(new Number(42), 0)); - assert.equal(-1, tasks.toNumber(undefined, -1)); - }); -}); +/******************************************************************************************** + * * + * Plese read the following tutorial before implementing tasks: * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Numbers_and_dates * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math * + * * + ********************************************************************************************/ + + +/** + * Returns an area of a rectangle given by width and heigth. + * + * @param {numder} width + * @param {number} height + * @return {number} + * + * @example: + * 5, 10 => 50 + * 5, 5 => 25 + */ +function getRectangleArea(width, height) { + return width * height; +} + + +/** + * Returns a circumference of circle given by radius. + * + * @param {number} radius + * @return {number} + * + * @example: + * 5 => 31.41592653589793 + * 3.14 => 19.729201864543903 + * 0 => 0 + */ +function getCicleCircumference(radius) { + return 2 * Math.PI * radius; +} + +/** + * Returns an average of two given numbers. + * + * @param {numder} value1 + * @param {number} value2 + * @return {number} + * + * @example: + * 5, 5 => 5 + * 10, 0 => 5 + * -3, 3 => 0 + */ +function getAverage(value1, value2) { + return value1 / 2 + value2 / 2; +} + +/** + * Returns a distance beetween two points by cartesian coordinates. + * + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * + * @return {number} + * + * @example: + * (0,0) (0,1) => 1 + * (0,0) (1,0) => 1 + * (-5,0) (10,-10) => 18.027756377319946 + */ +function getDistanceBetweenPoints(x1, y1, x2, y2) { + return Math.hypot((x2 - x1), (y2 - y1)); +} + +/** + * Returns a root of linear equation a*x + b = 0 given by coefficients a and b. + * + * @param {number} a + * @param {number} b + * @return {number} + * + * @example: + * 5*x - 10 = 0 => 2 + * x + 8 = 0 => -8 + * 5*x = 0 => 0 + */ +function getLinearEquationRoot(a, b) { + return -b / a; +} + + +/** + * Returns an angle (in radians) between two vectors given by xi and yi, coordinates in Cartesian plane + * See details https://en.wikipedia.org/wiki/Euclidean_vector#Representations + * + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @return {number} + * + * @example: + * (1,0) (0,1) => π/2 + * (0,1) (0,-1) => π + * (0,-1) (1,0) => π/2 + * (0,1) (0,1) => 0 + * (0,1) (1,2) => 0 + */ +function getAngleBetweenVectors(x1, y1, x2, y2) { + return Math.acos( + (x1 * x2 + y1 * y2) / (Math.sqrt(x1 * x1 + y1 * y1) * Math.sqrt(x2 * x2 + y2 * y2)) + ); +} + +/** + * Returns a last digit of a integer number. + * + * @param {number} value + * @return {number} + * + * @example: + * 100 => 0 + * 37 => 7 + * 5 => 5 + * 0 => 0 + */ +function getLastDigit(value) { + return value % 10; +} + + +/** + * Returns a number by given string representation. + * + * @param {string} value + * @return {number} + * + * @example: + * '100' => 100 + * '37' => 37 + * '-525.5' => -525.5 + */ +function parseNumberFromString(value) { + return Number.parseFloat(value); +} + +/** + * Returns a diagonal length of the rectangular parallelepiped given by its sides a,b,c. + * + * @param {number} a + * @param {number} b + * @param {number} c + * @return {number} + * + * @example: + * 1,1,1 => 1.7320508075688772 + * 3,3,3 => 5.196152422706632 + * 1,2,3 => 3.741657386773941 + */ +function getParallelipidedDiagonal(a,b,c) { + return Math.sqrt(a * a + b * b + c * c); +} + +/** + * Returns the number rounded to specified power of 10. + * + * @param {number} num + * @param {number} pow + * @return {number} + * + * @example: + * 1234, 0 => 1234 + * 1234, 1 => 1230 + * 1234, 2 => 1200 + * 1234, 3 => 1000 + * 1678, 0 => 1678 + * 1678, 1 => 1680 + * 1678, 2 => 1700 + * 1678, 3 => 2000 + */ +function roundToPowerOfTen(num, pow) { + return Math.round(num / Math.pow(10, pow)) * Math.pow(10, pow); +} + +/** + * Returns true is the number is prime; otherwise false. + * See: https://en.wikipedia.org/wiki/Primality_test + * + * @param {number} n + * @return {bool} + * + * @example: + * 4 => false + * 5 => true + * 6 => false + * 7 => true + * 11 => true + * 12 => false + * 16 => false + * 17 => true + */ +function isPrime(n) { + if (n <= 1) { + return false; + } else if (n <= 3) { + return true; + } else if (!(n % 2) || !(n % 3)) { + return false; + } + + let i = 5; + while (i * i <= n) { + if (!(n % i) || !(n % (i + 2))) { + return false; + } + i += 6; + } + return true; +} + +/** + * Tries to convert value to number and returns it if conversion was successfull; + * otherwise returns default value passed as a second argument. + * + * @param {any} value + * @param {any} def + * @return {number} + * + * @example + * toNumber(null, 0) => 0 + * toNumber('test', 0) => 0 + * toNumber('1', 0) => 1 + * toNumber(42, 0) => 42 + * toNumber(new Number(42), 0) => 42 + */ +function toNumber(value, def) { + return +value ? +value : def; +} + +module.exports = { + getRectangleArea: getRectangleArea, + getCicleCircumference: getCicleCircumference, + getAverage: getAverage, + getDistanceBetweenPoints: getDistanceBetweenPoints, + getLinearEquationRoot: getLinearEquationRoot, + getAngleBetweenVectors: getAngleBetweenVectors, + getLastDigit: getLastDigit, + parseNumberFromString: parseNumberFromString, + getParallelipidedDiagonal: getParallelipidedDiagonal, + roundToPowerOfTen: roundToPowerOfTen, + isPrime: isPrime, + toNumber: toNumber +}; From e65bb9f3574871aada33a3048484a6b879dd8413 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Sun, 30 May 2021 23:16:00 +0300 Subject: [PATCH 04/40] Task4 --- test/04-arrays-tests.js | 1530 +++++++++++++++++---------------------- 1 file changed, 678 insertions(+), 852 deletions(-) diff --git a/test/04-arrays-tests.js b/test/04-arrays-tests.js index eef6f904f..42c9af0b3 100644 --- a/test/04-arrays-tests.js +++ b/test/04-arrays-tests.js @@ -1,866 +1,692 @@ 'use strict'; -var assert = require('assert'); -var tasks = require('../task/04-arrays-tasks'); -it.optional = require('../extensions/it-optional'); - -describe('04-arrays-tasks', function() { - - it.optional('findElement should return the index of specified value if exists', function () { - [ - { - arr: ['Ace', 10, true], - value: 10, - expected: 1 - },{ - arr: ['Array', 'Number', 'string'], - value: 'Date', - expected: -1 - },{ - arr: [0, 1, 2, 3, 4, 5], - value: 5, - expected: 5 - } - ].forEach(data => { - var actual = tasks.findElement(data.arr, data.value); - assert.equal( - data.expected, - actual, - `Index of '${data.value}' inside of [${data.arr}] = ${data.expected}, but actually ${actual}` - ); - }); +/********************************************************************************************* + * * + * Plese read the following tutorial before implementing tasks: * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array * + * * + * NOTE : Please do not use loops! All tasks can be implmeneted using standard Array methods * + * * + *********************************************************************************************/ + + +/** + * Returns an index of the specified element in array or -1 if element is not found + * + * @param {array} arr + * @param {any} value + * @return {number} + * + * @example + * ['Ace', 10, true], 10 => 1 + * ['Array', 'Number', 'string'], 'Date' => -1 + * [0, 1, 2, 3, 4, 5], 5 => 5 + */ +function findElement(arr, value) { + return arr.indexOf(value); +} + +/** + * Generates an array of odd numbers of the specified length + * + * @param {number} len + * @return {array} + * + * @example + * 1 => [ 1 ] + * 2 => [ 1, 3 ] + * 5 => [ 1, 3, 5, 7, 9 ] + */ +function generateOdds(len) { + let arr = new Array(len); + arr.fill(1); + return arr.map((elem, index) => + { + return elem = index*2 + 1; }); - - - it.optional('generateOdds should return the array of odd numbers of specified size', function () { - [ - { - len: 1, - expected: [ 1 ] - },{ - len: 2, - expected: [ 1, 3 ] - },{ - len: 5, - expected: [ 1, 3, 5, 7, 9 ] - },{ - len: 16, - expected: [ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 ] - } - ].forEach(data => { - assert.deepEqual( - data.expected, - tasks.generateOdds(data.len) - ); - }); +} + + +/** + * Returns the doubled array - elements of the specified array are repeated twice using original order + * + * @param {array} arr + * @return {array} + * + * @example + * ['Ace', 10, true] => ['Ace', 10, true, 'Ace', 10, true] + * [0, 1, 2, 3, 4, 5] => [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5] + * [] => [] + */ +function doubleArray(arr) { + arr.map(elem => + { + return arr.push(elem); }); - - - it.optional('doubleArray should return the specified array twice', function () { - [ - { - arr: ['Ace', 10, true], - expected: ['Ace', 10, true, 'Ace', 10, true] - },{ - arr: [0, 1, 2, 3, 4, 5], - expected: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5] - },{ - arr: [], - expected: [] - } - ].forEach(data => { - var actual = tasks.doubleArray(data.arr); - assert.deepEqual( - data.expected, - actual, - `The result of doubling [${data.arr}] is not correct` - ); - }); + return arr; +} + + +/** + * Returns an array of positive numbers from the specified array in original order + * + * @param {array} arr + * @return {array} + * + * @example + * [ 0, 1, 2, 3, 4, 5 ] => [ 1, 2, 3, 4, 5 ] + * [-1, 2, -5, -4, 0] => [ 2 ] + * [] => [] + */ +function getArrayOfPositives(arr) { + return arr.filter((item, i, arr) => item > 0); +} + +/** + * Returns the array with strings only in the specified array (in original order) + * + * @param {array} arr + * @return {array} + * + * @example + * [ 0, 1, 'cat', 3, true, 'dog' ] => [ 'cat', 'dog' ] + * [ 1, 2, 3, 4, 5 ] => [] + * [ 'cat, 'dog', 'raccon' ] => [ 'cat', 'dog', 'racoon' ] + */ +function getArrayOfStrings(arr) { + return arr.filter(elem => typeof elem === 'string' || elem instanceof String); +} + +/** + * Removes falsy values from the specified array + * Falsy values: false, null, 0, "", undefined, and NaN. + * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean#Description) + * + * @param {array} arr + * @return {array} + * + * @example + * [ 0, false, 'cat', NaN, true, '' ] => [ 'cat', true ] + * [ 1, 2, 3, 4, 5, 'false' ] => [ 1, 2, 3, 4, 5, 'false' ] + * [ false, 0, NaN, '', undefined ] => [ ] + */ +function removeFalsyValues(arr) { + return arr.filter((item, i, arr) => !!item); +} + +/** + * Returns the array of useprcase strings from the specified array + * + * @param {array} arr + * @return {array} + * + * @example + * [ 'permanent-internship', 'glutinous-shriek', 'multiplicative-elevation' ] => [ 'PERMANENT-INTERNSHIP', 'GLUTINOUS-SHRIEK', 'MULTIPLICATIVE-ELEVATION' ] + * [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ] => [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ] + */ +function getUpperCaseStrings(arr) { + return arr.map(elem => + { + return elem.toUpperCase(); }); - - - it.optional('getArrayOfPositives should return the array of positive values from specified array', function () { - [ - { - arr: [ 0, 1, 2, 3, 4, 5 ], - expected: [ 1, 2, 3, 4, 5 ] - },{ - arr: [-1, 2, -5, -4, 0], - expected: [ 2 ] - },{ - arr: [], - expected: [] - } - ].forEach(data => { - var actual = tasks.getArrayOfPositives(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); +} + + +/** + * Returns the array of string lengths from the specified string array. + * + * @param {array} arr + * @return {array} + * + * @example + * [ '', 'a', 'bc', 'def', 'ghij' ] => [ 0, 1, 2, 3, 4 ] + * [ 'angular', 'react', 'ember' ] => [ 7, 5, 5 ] + */ +function getStringsLength(arr) { + return arr.map((item, i, arr) => item.length); +} + +/** + * Inserts the item into specified array at specified index + * + * @param {array} arr + * @param {any} item + * @param {number} index + * + * @example + * [ 1, 3, 4, 5 ], 2, 1 => [ 1, 2, 3, 4, 5 ] + * [ 1, 'b', 'c'], 0, 'x' => [ 'x', 1, 'b', 'c' ] + */ +function insertItem(arr, item, index) { + return arr.splice(index, 0, item); +} + +/** + * Returns the n first items of the specified array + * + * @param {array} arr + * @param {number} n + * + * @example + * [ 1, 3, 4, 5 ], 2 => [ 1, 2 ] + * [ 'a', 'b', 'c', 'd'], 3 => [ 'a', 'b', 'c' ] + */ +function getHead(arr, n) { + return arr.slice(0, n); +} + + +/** + * Returns the n last items of the specified array + * + * @param {array} arr + * @param {number} n + * + * @example + * [ 1, 3, 4, 5 ], 2 => [ 4, 5 ] + * [ 'a', 'b', 'c', 'd'], 3 => [ 'b', 'c', 'd' ] + */ +function getTail(arr, n) { + return arr.splice( -n); +} + + +/** + * Returns CSV represebtation of two-dimentional numeric array. + * https://en.wikipedia.org/wiki/Comma-separated_values + * + * @param {array} arr + * @return {string} + * + * @example + * [ + * [ 0, 1, 2, 3, 4 ], + * [ 10,11,12,13,14 ], + * [ 20,21,22,23,24 ], + * [ 30,31,32,33,34 ] + * ] + * => + * '0,1,2,3,4\n' + * +'10,11,12,13,14\n' + * +'20,21,22,23,24\n' + * +'30,31,32,33,34' + */ +function toCsvText(arr) { + let arrStr = arr.map((elem, index) => + { + if (index != arr.length-1) + return elem.join(',') + '\n'; + else + return elem.join(','); + }).join(''); + return arrStr; +} + +/** + * Transforms the numeric array into the according array of squares: + * f(x) = x * x + * + * @param {array} arr + * @return {array} + * + * @example + * [ 0, 1, 2, 3, 4, 5 ] => [ 0, 1, 4, 9, 16, 25 ] + * [ 10, 100, -1 ] => [ 100, 10000, 1 ] + */ +function toArrayOfSquares(arr) { + return arr.map((item, i, arr) => item ** 2); +} + + +/** + * Transforms the numeric array to the according moving sum array: + * f[n] = x[0] + x[1] + x[2] +...+ x[n] + * or f[n] = f[n-1] + x[n] + * + * @param {array} arr + * @return {array} + * + * Example : + * [ 1, 1, 1, 1, 1 ] => [ 1, 2, 3, 4, 5 ] + * [ 10, -10, 10, -10, 10 ] => [ 10, 0, 10, 0, 10 ] + * [ 0, 0, 0, 0, 0] => [ 0, 0, 0, 0, 0] + * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55 ] + */ +function getMovingSum(arr) { + let sum = 0; + return arr.map((elem) => + { + elem += sum; + sum = elem; + return elem; }); - - - it.optional('getArrayOfStrings should return the array of string values from specified array', function () { - [ - { - arr: [ 0, 1, 'cat', 3, true, 'dog' ], - expected: [ 'cat', 'dog' ] - },{ - arr: [ 1, 2, 3, 4, 5 ], - expected: [ ] - },{ - arr: [ 'cat', 'dog', 'raccon' ], - expected: [ 'cat', 'dog', 'raccon' ] - } - ].forEach(data => { - var actual = tasks.getArrayOfStrings(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); +} + +/** + * Returns every second item from the specified array: + * + * @param {array} arr + * @return {array} + * + * Example : + * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 2, 4, 6, 8, 10 ] + * [ 'a', 'b', 'c' , null ] => [ "b", null ] + * [ "a" ] => [] + */ +function getSecondItems(arr) { + return arr.filter((elem, index) => index % 2); +} + + +/** + * Propagates every item in sequence its position times + * Returns an array that consists of: one first item, two second items, tree third items etc. + * + * @param {array} arr + * @return {array} + * + * @example : + * [] => [] + * [ 1 ] => [ 1 ] + * [ 'a', 'b' ] => [ 'a', 'b','b' ] + * [ 'a', 'b', 'c', null ] => [ 'a', 'b','b', 'c','c','c', null,null,null,null ] + * [ 1,2,3,4,5 ] => [ 1, 2,2, 3,3,3, 4,4,4,4, 5,5,5,5,5 ] + */ +function propagateItemsByPositionIndex(arr) { + return arr.reduce((acc, elem, index) => acc.concat( + Array.from({length: index + 1}, () => elem)), [] + ); +} + + +/** + * Returns the 3 largest numbers from the specified array + * + * @param {array} arr + * @return {array} + * + * @example + * [] => [] + * [ 1, 2 ] => [ 2, 1 ] + * [ 1, 2, 3 ] => [ 3, 2, 1 ] + * [ 1,2,3,4,5,6,7,8,9,10 ] => [ 10, 9, 8 ] + * [ 10, 10, 10, 10 ] => [ 10, 10, 10 ] + */ +function get3TopItems(arr) { + return arr.sort((a, b) => b - a).slice(0, 3); +} + + +/** + * Returns the number of positive numbers from specified array + * + * @param {array} arr + * @return {number} + * + * @example + * [ ] => 0 + * [ -1, 0, 1 ] => 1 + * [ 1, 2, 3] => 3 + * [ null, 1, 'elephant' ] => 1 + * [ 1, '2' ] => 1 + */ +function getPositivesCount(arr) { + throw new Error('Not implemented'); +} + +/** + * Sorts digit names + * + * @param {array} arr + * @return {array} + * + * @example + * [] => [] + * [ 'nine','one' ] => [ 'one', 'nine' ] + * [ 'one','two','three' ] => [ 'one','two', 'three' ] + * [ 'nine','eight','nine','eight'] => [ 'eight','eight','nine','nine'] + * [ 'one','one','one','zero' ] => [ 'zero','one','one','one' ] + */ +function sortDigitNamesByNumericOrder(arr) { + const mapped1 = { 0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six', 7: 'seven', 8: 'eight', 9: 'nine' }; + const mapped2 = { 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9 }; + return arr.map(x => mapped2[x]).sort().map(x => mapped1[x]); +} + +/** + * Returns the sum of all items in the specified array of numbers + * + * @param {array} arr + * @return {number} + * + * @example + * [] => 0 + * [ 1, 2, 3 ] => 6 + * [ -1, 1, -1, 1 ] => 0 + * [ 1, 10, 100, 1000 ] => 1111 + */ +function getItemsSum(arr) { + return arr.reduce((acc, elem) => acc + elem, 0); +} + +/** + * Returns the number of all falsy value in the specified array + * + * @param {array} arr + * @return {array} + * + * @example + * [] => 0 + * [ 1, '', 3 ] => 1 + * [ -1, 'false', null, 0 ] => 2 + * [ null, undefined, NaN, false, 0, '' ] => 6 + */ +function getFalsyValuesCount(arr) { + let sum = 0; + arr.map(elem => + { + if (Boolean(elem) == 0) + return sum++; }); - - - it.optional('removeFalsyValues should return the specified array without falsy values', function () { - [ - { - arr: [ 0, false, 'cat', NaN, true, '' ], - expected: [ 'cat', true ] - },{ - arr: [ 1, 2, 3, 4, 5, 'false' ], - expected: [ 1, 2, 3, 4, 5, 'false' ] - },{ - arr: [ false, 0, NaN, '', undefined ], - expected: [ ] + return sum; +} + +/** + * Returns a number of all occurences of the specified item in an array + * + * @param {array} arr + * @param {any} item + * @return {number} + * + * @example + * [ 0, 0, 1, 1, 1, 2 ], 1 => 3 + * [ 1, 2, 3, 4, 5 ], 0 => 0 + * [ 'a','b','c','c' ], 'c'=> 2 + * [ null, undefined, null ], null => 2 + * [ true, 0, 1, 'true' ], true => 1 + */ +function findAllOccurences(arr, item) { + let num = 0; + arr.map(elem => + { + if (elem === item) + return num++; + }) + return num; +} + +/** + * Concatenates all elements from specified array into single string with ',' delimeter + * + * @param {array} arr + * @return {string} + * + * @example + * [0, false, 'cat', NaN, true, ''] => '0,false,cat,NaN,true,' + * [1, 2, 3, 4, 5] => '1,2,3,4,5' + * ['rock', 'paper', 'scissors'] => 'rock,paper,scissors' + */ +function toStringList(arr) { + return arr.join(); +} + + +/** + * Sorts the specified array by country name first and city name (if countries are equal) in ascending order. + * + * @param {array} arr + * @return {array} + * + * @example + * [ + * { country: 'Russia', city: 'Moscow' }, + * { country: 'Belarus', city: 'Minsk' }, + * { country: 'Poland', city: 'Warsaw' }, + * { country: 'Russia', city: 'Saint Petersburg' }, + * { country: 'Poland', city: 'Krakow' }, + * { country: 'Belarus', city: 'Brest' } + * ] + * => + * [ + * { country: 'Belarus', city: 'Brest' }, + * { country: 'Belarus', city: 'Minsk' }, + * { country: 'Poland', city: 'Krakow' }, + * { country: 'Poland', city: 'Warsaw' }, + * { country: 'Russia', city: 'Moscow' }, + * { country: 'Russia', city: 'Saint Petersburg' } + */ +function sortCitiesArray(arr) { + function compareLocation(a, b) { + if (a.country < b.country) { + return -1; + } + if (a.country > b.country) { + return 1; + } + if (a.country === b.country) { + if (a.city < b.city) { + return -1; } - ].forEach(data => { - var actual = tasks.removeFalsyValues(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('findAllOccurences should return the number of all occurences of specified item in an array', function () { - [ - { - arr: [ 0, 0, 1, 1, 1, 2 ], - item: 1, - expected: 3 - },{ - arr: [ 1, 2, 3, 4, 5 ], - item: 0, - expected: 0 - },{ - arr: [ 'a','b','c','c' ], - item: 'c', - expected: 2 - },{ - arr: [ null, undefined, null ], - item: null, - expected: 2 - },{ - arr: [ true, 0, 1, 'true' ], - item: true, - expected: 1 + if (a.city > b.city) { + return 1; } - ].forEach(data => { - var actual = tasks.findAllOccurences(data.arr, data.item); - assert.equal( - data.expected, - actual, - `Number of occurences of ${JSON.stringify(data.item)} in ${JSON.stringify(data.arr)} is ${data.expected}, but actually ${actual})` - ); - }); - }); - - - it.optional('getUpperCaseStrings should convert strings from specified array to uppercase', function () { - [ - { - arr: [ 'permanent-internship', 'glutinous-shriek', 'multiplicative-elevation' ], - expected: [ 'PERMANENT-INTERNSHIP', 'GLUTINOUS-SHRIEK', 'MULTIPLICATIVE-ELEVATION' ] - },{ - arr: [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ], - expected: [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ] + if (a.city === b.city) { + return 0; } - ].forEach(data => { - var actual = tasks.getUpperCaseStrings(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); + } + } + return arr.sort(compareLocation); +} + +/** + * Creates an indentity matrix of the specified size + * + * @param {number} n + * @return {array} + * + * @example + * 1 => [[1]] + * + * 2 => [[1,0], + * [0,1]] + * + * [[1,0,0,0,0], + * [0,1,0,0,0], + * 5 => [0,0,1,0,0], + * [0,0,0,1,0], + * [0,0,0,0,1]] + */ +function getIdentityMatrix(n) { + return Array.from({length: n}, function (elem, index) { + let row = new Array(n).fill(0, 0, n); + row[index] = 1; + return row; }); - - - it.optional('getStringsLength should convert strings from specified array to uppercase', function () { - [ - { - arr: [ '', 'a', 'bc', 'def', 'ghij' ], - expected: [ 0, 1, 2, 3, 4 ] - },{ - arr: [ 'angular', 'react', 'ember' ], - expected: [ 7, 5, 5 ] - } - ].forEach(data => { - var actual = tasks.getStringsLength(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); +} + +/** + * Creates an array of integers from the specified start to end (inclusive) + * + * @param {number} start + * @param {number} end + * @return {array} + * + * @example + * 1, 5 => [ 1, 2, 3, 4, 5 ] + * -2, 2 => [ -2, -1, 0, 1, 2 ] + * 0, 100 => [ 0, 1, 2, ..., 100 ] + * 3, 3 => [ 3 ] + */ +function getIntervalArray(start, end) { + return Array.from({length: end - start + 1}, (elem, index) => index + start); +} + +/** + * Returns array containing only unique values from the specified array. + * + * @param {array} arr + * @return {array} + * + * @example + * [ 1, 2, 3, 3, 2, 1 ] => [ 1, 2, 3 ] + * [ 'a', 'a', 'a', 'a' ] => [ 'a' ] + * [ 1, 1, 2, 2, 3, 3, 4, 4] => [ 1, 2, 3, 4] + */ +function distinct(arr) { + let resArr = []; + arr.map(elem => + { + if (resArr.indexOf(elem) == -1) + resArr.push(elem); + return resArr; }); - - - it.optional('insertItem should insert an item at specified position', function () { - [ - { - arr: [ 1, 3, 4, 5 ], - item: 2, - index: 1, - expected: [ 1, 2, 3, 4, 5 ] - },{ - arr: [ 1, 'b', 'c' ], - item: 'x', - index: 0, - expected: [ 'x', 1, 'b', 'c' ] - } - ].forEach(data => { - tasks.insertItem(data.arr, data.item, data.index); - assert.deepEqual( - data.expected, - data.arr - ); - }); + return resArr; +} + +/** + * Groups elements of the specified array by key. + * Returns multimap of keys extracted from array elements via keySelector callback + * and values extracted via valueSelector callback. + * See: https://en.wikipedia.org/wiki/Multimap + * + * @param {array} array + * @param {Function} keySelector + * @param {Function} valueSelector + * @return {Map} + * + * @example + * group([ + * { country: 'Belarus', city: 'Brest' }, + * { country: 'Russia', city: 'Omsk' }, + * { country: 'Russia', city: 'Samara' }, + * { country: 'Belarus', city: 'Grodno' }, + * { country: 'Belarus', city: 'Minsk' }, + * { country: 'Poland', city: 'Lodz' } + * ], + * item => item.country, + * item => item.city + * ) + * => + * Map { + * "Belarus" => ["Brest", "Grodno", "Minsk"], + * "Russia" => ["Omsk", "Samara"], + * "Poland" => ["Lodz"] + * } + */ +function group(array, keySelector, valueSelector) { + let _map = new Map(); + array.map((x, ind) => + { + if( _map.has(keySelector(x)) ) + _map.get(keySelector(x)).push(valueSelector(x)); + else + _map.set(keySelector(x), [valueSelector(x)]); }); - - - it.optional('getHead should return the n first items from the specified array', function () { - [ - { - arr: [ 1, 2, 3, 4, 5 ], - n: 2, - expected: [ 1, 2 ] - },{ - arr: [ 'a', 'b', 'c', 'd' ], - n: 3, - expected: [ 'a', 'b', 'c' ] - } - ].forEach(data => { - assert.deepEqual( - data.expected, - tasks.getHead(data.arr, data.n) - ); - }); - }); - - - it.optional('getTail should return the n last items from the specified array', function () { - [ - { - arr: [ 1, 2, 3, 4, 5 ], - n: 2, - expected: [ 4, 5 ] - },{ - arr: [ 'a', 'b', 'c', 'd' ], - n: 3, - expected: [ 'b', 'c', 'd' ] - } - ].forEach(data => { - assert.deepEqual( - data.expected, - tasks.getTail(data.arr, data.n) - ); - }); - }); - - - it.optional('toCsvText should convert two-dimentional numeric array to CSV format', function () { - [ - { - arr: [ - [ 0, 1, 2, 3, 4 ], - [ 10,11,12,13,14 ], - [ 20,21,22,23,24 ], - [ 30,31,32,33,34 ] - ], - expected: - '0,1,2,3,4\n' - +'10,11,12,13,14\n' - +'20,21,22,23,24\n' - +'30,31,32,33,34' - }, { - arr: [[]], - expected: '' - } - ].forEach(data => { - var actual = tasks.toCsvText(data.arr); - assert.equal( - data.expected, - actual - ); - }); - }); - - - it.optional('toArrayOfSquares should convert numeric array to the array of squares', function () { - [ - { - arr: [ 0, 1, 2, 3, 4, 5 ], - expected: [ 0, 1, 4, 9, 16, 25 ] - }, { - arr: [ 10, 100, -1 ], - expected: [ 100, 10000, 1 ] - } - ].forEach(data => { - var actual = tasks.toArrayOfSquares(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('getMovingSum should convert numeric array to the according array of moving sum', function () { - [ - { - arr: [ 1, 1, 1, 1, 1 ], - expected: [ 1, 2, 3, 4, 5 ] - }, { - arr: [ 10, -10, 10, -10, 10 ], - expected: [ 10, 0, 10, 0, 10 ] - }, { - arr: [ 0, 0, 0, 0, 0], - expected: [ 0, 0, 0, 0, 0] - }, { - arr: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], - expected: [ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55 ] - } - ].forEach(data => { - var actual = tasks.getMovingSum(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('getSecondItems should return every second item from the specified array', function () { - [ - { - arr: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], - expected: [ 2, 4, 6, 8, 10 ] - }, { - arr: [ 'a', 'b', 'c' , null ], - expected: [ "b", null ] - }, { - arr: [ "a" ], - expected: [ ] - } - ].forEach(data => { - var actual = tasks.getSecondItems(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('propagateItemsByPositionIndex should propagate every item its position time', function () { - [ - { - arr: [], - expected: [] - }, { - arr: [ 1 ], - expected: [ 1 ] - }, { - arr: [ 'a', 'b' ], - expected: [ 'a', 'b','b' ] - }, { - arr: [ 'a', 'b', 'c', null ], - expected: [ 'a', 'b','b', 'c','c','c', null,null,null,null ] - }, { - arr: [ 1, 2, 3, 4, 5 ], - expected: [ 1, 2,2, 3,3,3, 4,4,4,4, 5,5,5,5,5 ] - } - ].forEach(data => { - var actual = tasks.propagateItemsByPositionIndex(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('get3TopItems should return the 3 largest items from integer array', function () { - [ - { - arr: [], - expected: [] - }, { - arr: [ 1,2 ], - expected: [ 2,1 ] - }, { - arr: [ 1, 2, 3 ], - expected: [ 3, 2, 1 ] - }, { - arr: [ 1,2,3,4,5,6,7,8,9,10 ], - expected: [ 10,9,8 ] - }, { - arr: [ 10, 10, 10, 10], - expected: [ 10, 10, 10 ] - } - ].forEach(data => { - var actual = tasks.get3TopItems(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('getPositivesCount should return the number of positive integers in the specified array', function () { - [ - { - arr: [], - expected: 0 - }, { - arr: [ -1, 0, 1 ], - expected: 1 - }, { - arr: [ 1, 2, 3 ], - expected: 3 - }, { - arr: [ null, 1, 'elephant' ], - expected: 1 - }, { - arr: [ 1, '2' ], - expected: 1 - } - ].forEach(data => { - var actual = tasks.getPositivesCount(data.arr); - assert.equal( - data.expected, - actual, - `Test failed for argument [${data.arr}]` - ); - }); - }); - - - it.optional('sortDigitNamesByNumericOrder should sort digit names by its numeric value', function () { - [ - { - arr: [], - expected: [] - }, { - arr: [ 'nine','one' ], - expected: [ 'one', 'nine' ] - }, { - arr: [ 'one','two','three' ], - expected: [ 'one','two', 'three' ] - }, { - arr: [ 'nine','eight','nine','eight' ], - expected: [ 'eight','eight','nine','nine' ] - }, { - arr: [ 'one','one','one','zero' ], - expected: [ 'zero','one','one','one' ] - }, { - arr: [ 'nine','eight','seven','six','five','four','three','two','one','zero' ], - expected: [ 'zero','one','two','three','four','five','six','seven','eight','nine' ] - } - ].forEach(data => { - var actual = tasks.sortDigitNamesByNumericOrder(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('getItemsSum should return the sum of all items of numbers array', function () { - [ - { - arr: [ ], - expected: 0 - },{ - arr: [ 1, 2, 3 ], - expected: 6 - },{ - arr: [ 1, 10, 100, 1000 ], - expected: 1111 - } - ].forEach(data => { - var actual = tasks.getItemsSum(data.arr); - assert.deepEqual( - data.expected, - actual, - `Test failed for [${data.arr}]` - ); - }); - }); - - - it.optional('getFalsyValuesCount should return the number of falsy value in the specified array', function () { - [ - { - arr: [ ], - expected: 0 - },{ - arr: [ 1, '', 3 ], - expected: 1 - },{ - arr: [ -1, 'false', null, 0 ], - expected: 2 - },{ - arr: [ null, undefined, NaN, false, 0, '' ], - expected: 6 - } - ].forEach(data => { - var actual = tasks.getFalsyValuesCount(data.arr); - assert.deepEqual( - data.expected, - actual, - `Test failed for [${data.arr}]` - ); - }); - }); - - - it.optional('toStringList should return the string list of passed arguments', function () { - [ - { - arr: [ 0, false, 'cat', NaN, true, '' ], - expected: '0,false,cat,NaN,true,' - },{ - arr: [ 1, 2, 3, 4, 5 ], - expected: '1,2,3,4,5' - },{ - arr: [ 'rock', 'paper', 'scissors' ], - expected: 'rock,paper,scissors' - } - ].forEach(data => { - var actual = tasks.toStringList(data.arr); - assert.equal( - data.expected, - actual - ); - }); - }); - - - it.optional('sortCitiesArray should sort the array of objects using two keys', function () { - [ - { - arr: [ - { country: 'Russia', city: 'Moscow' }, - { country: 'Belarus', city: 'Minsk' }, - { country: 'Poland', city: 'Warsaw' }, - { country: 'Russia', city: 'Saint Petersburg' }, - { country: 'Poland', city: 'Krakow' }, - { country: 'Belarus', city: 'Brest' } - ], - expected: [ - { country: 'Belarus', city: 'Brest' }, - { country: 'Belarus', city: 'Minsk' }, - { country: 'Poland', city: 'Krakow' }, - { country: 'Poland', city: 'Warsaw' }, - { country: 'Russia', city: 'Moscow' }, - { country: 'Russia', city: 'Saint Petersburg' } - ] - }, { - arr: [ - { country: 'D', city: '1' }, - { country: 'E', city: '1' }, - { country: 'A', city: '2' }, - { country: 'B', city: '1' }, - { country: 'B', city: '2' }, - { country: 'A', city: '1' } - ], - expected: [ - { country: 'A', city: '1' }, - { country: 'A', city: '2' }, - { country: 'B', city: '1' }, - { country: 'B', city: '2' }, - { country: 'D', city: '1' }, - { country: 'E', city: '1' } - ] - },{ - arr: [ - { country: '5', city: '1' }, - { country: '1', city: '1' }, - { country: '1', city: '2' }, - { country: '1', city: '3' }, - { country: '2', city: '2' }, - { country: '1', city: '1' }, - { country: '1', city: '1' }, - { country: '2', city: '1' }, - { country: '3', city: '1' }, - { country: '3', city: '3' }, - { country: '2', city: '5' }, - { country: '5', city: '2' } - ], - expected: [ - { country: '1', city: '1' }, - { country: '1', city: '1' }, - { country: '1', city: '1' }, - { country: '1', city: '2' }, - { country: '1', city: '3' }, - { country: '2', city: '1' }, - { country: '2', city: '2' }, - { country: '2', city: '5' }, - { country: '3', city: '1' }, - { country: '3', city: '3' }, - { country: '5', city: '1' }, - { country: '5', city: '2' } - ] - } - ].forEach(data => { - var actual = tasks.sortCitiesArray(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('getIdentityMatrix should return the identity matrix of the specified size', function () { - [ - { - n: 1, - expected: [[1]] - }, { - n: 2, - expected: [[1,0], - [0,1]] - }, { - n: 5, - expected: [[1,0,0,0,0], - [0,1,0,0,0], - [0,0,1,0,0], - [0,0,0,1,0], - [0,0,0,0,1]] - } - ].forEach(data => { - var actual = tasks.getIdentityMatrix(data.n); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('getIntervalArray should return the array of integers from start to end (inclusive)', function () { - [ - { - start: 1, - end: 5, - expected: [ 1, 2, 3, 4, 5 ] - }, { - start: -2, - end: 2, - expected: [ -2, -1, 0, 1, 2 ] - }, { - start: 0, - end: 100, - expected: [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, - 100 - ] - }, { - start: 3, - end: 3, - expected: [ 3 ] - } - ].forEach(data => { - var actual = tasks.getIntervalArray(data.start, data.end); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('distinct should return an array of unique items from the specified array', function () { - [ - { - arr: [ 1, 2, 3, 3, 2, 1 ], - expected: [ 1, 2, 3 ] - }, { - arr: [ 'a', 'a', 'a', 'a', 'a' ], - expected: [ 'a' ] - }, { - arr: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], - expected: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] - }, { - arr: [ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6 ], - expected: [ 1, 2, 3, 4, 5, 6 ] - } - ].forEach(data => { - var actual = tasks.distinct(data.arr); - assert.deepEqual( - data.expected, - actual - ); - }); - }); - - - - it.optional('group should return a map of grouped data by key and value selector', function () { - [ - { - arr: [ - { country: 'Belarus', city: 'Brest' }, - { country: 'Russia', city: 'Omsk' }, - { country: 'Russia', city: 'Samara' }, - { country: 'Belarus', city: 'Grodno' }, - { country: 'Belarus', city: 'Minsk' }, - { country: 'Poland', city: 'Lodz' } - ], - keySelector: item => item.country, - valueSelector: item => item.city, - expected: new Map([ - ['Belarus', ['Brest', 'Grodno', 'Minsk']], - ['Russia', ['Omsk', 'Samara']], - ['Poland', ['Lodz']] - ]) - }, { - arr: [ - { artist: 'ACDC', album: 'Highway to Hell' }, - { artist: 'Metallica', album: "Kill'em All" }, - { artist: 'Deep Purple', album: 'Machine Head' }, - { artist: 'Metallica', album: 'And Justice for All' }, - { artist: 'ACDC', album: 'Back in Black' }, - { artist: 'Manowar', album: 'Kings of Metal' } - ], - keySelector: item => item.artist, - valueSelector: item => item.album, - expected: new Map([ - ['ACDC', ['Highway to Hell', 'Back in Black']], - ['Metallica', ["Kill'em All", 'And Justice for All']], - ['Deep Purple', ['Machine Head']], - ['Manowar',['Kings of Metal']] - ]) - } - ].forEach(data => { - var actual = tasks.group(data.arr, data.keySelector, data.valueSelector); - assert.deepEqual( - Array.from(data.expected), - Array.from(actual) - ); - }); - }); - - - it.optional('selectMany should return an array of child items from the specified array', function () { - [ - { - arr: [[1, 2], [3, 4], [5, 6]], - childrenSelector : x => x, - expected: [ 1, 2, 3, 4, 5, 6 ] - }, { - arr: [[11, 12, 13, 14, 15], [21, 22, ,23, 24, 25], [31, 32, 34, 35]], - childrenSelector : x => x.slice(0,2), - expected: [ 11, 12, 21, 22, 31, 32 ] - }, { - arr: ['one','two','three'], - childrenSelector: x=>x.split(''), - expected: ['o','n','e','t','w','o','t','h','r','e','e'] - } - ].forEach(data => { - var actual = tasks.selectMany(data.arr, data.childrenSelector); - assert.deepStrictEqual( - data.expected, - actual - ); - }); - }); - - - it.optional('getElementByIndexes should return an element from array by specified indexes', function () { - [ - { - arr: [ [1, 2], [3, 4], [5, 6] ], - indexes: [ 0, 0 ], - expected: 1 - }, { - arr: ['one','two','three'], - indexes: [ 2 ], - expected: 'three' - }, { - arr: [[[1,2,3]]], - indexes: [ 0, 0, 1 ], - expected: 2 - } - ].forEach(data => { - var actual = tasks.getElementByIndexes(data.arr, data.indexes); - assert.equal( - data.expected, - actual, - `getElementByIndexes(${JSON.stringify(data.arr)}, ${JSON.stringify(data.indexes)}) returns an incorrect result. Expected ${data.expected}, but actual ${actual}` - ); - }); - }); - - - it.optional('swapHeadAndTail should swap the head and tail of the array', function () { - [ - { - arr: [ 1 ], - expected: [ 1 ] - },{ - arr: [ 1, 2 ], - expected: [ 2, 1 ] - },{ - arr: [ 1, 2, 3 ], - expected: [ 3, 2, 1 ] - },{ - arr: [ 1, 2, 3, 4 ], - expected: [ 3, 4, 1, 2 ] - },{ - arr: [ 1, 2, 3, 4, 5 ], - expected: [ 4, 5, 3, 1, 2 ] - } - ].forEach(data => { - var actual = tasks.swapHeadAndTail(Array.from(data.arr)); - assert.deepEqual( - data.expected, - actual, - `The result of swaping head and tail [${data.arr}] is not correct` - ); - }); - }); - - - it.optional('Functions from 04-array-test.js should not use basic loops statements', function () { - Object.getOwnPropertyNames(tasks) - .filter(x => tasks[x] instanceof Function) - .forEach(f => { - assert( - !/([;{]\s*(for|while)\s*\()|(\.forEach\s*\()/.test(tasks[f].toString()), - `Function "${f}" should not use basic loop statements (for, while or Array.forEach)! Please use specialized array methods (Array.map, Array.reduce etc).` - ); - }); + return _map; +} + + +/** + * Projects each element of the specified array to a sequence and flattens the resulting sequences into one array. + * + * @param {array} arr + * @param {Function} childrenSelector, a transform function to apply to each element that returns an array of children + * @return {array} + * + * @example + * [[1, 2], [3, 4], [5, 6]], (x) => x => [ 1, 2, 3, 4, 5, 6 ] + * ['one','two','three'], x=>x.split('') => ['o','n','e','t','w','o','t','h','r','e','e'] + */ +function selectMany(arr, childrenSelector) { + let myArr = []; + arr.map(elem => + { + myArr.push(...childrenSelector(elem)); }); - -}); + return myArr; +} + + +/** + * Returns an element from the multidimentional array by the specified indexes. + * + * @param {array} arr + * @param {array} indexes + * @return {any} element from array + * + * @example + * [[1, 2], [3, 4], [5, 6]], [0,0] => 1 (arr[0][0]) + * ['one','two','three'], [2] => 'three' (arr[2]) + * [[[ 1, 2, 3]]], [ 0, 0, 1 ] => 2 (arr[0][0][1]) + */ +function getElementByIndexes(arr, indexes) { + return indexes.map(elem => arr = arr[elem])[indexes.length - 1]; +} + + +/** + * Swaps the head and tail of the specified array: + * the head (first half) of array move to the end, the tail (last half) move to the start. + * The middle element (if exists) leave on the same position. + * + * + * @param {array} arr + * @return {array} + * + * @example + * [ 1, 2, 3, 4, 5 ] => [ 4, 5, 3, 1, 2 ] + * \----/ \----/ + * head tail + * + * [ 1, 2 ] => [ 2, 1 ] + * [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 5, 6, 7, 8, 1, 2, 3, 4 ] + * + */ +function swapHeadAndTail(arr) { + let len = Math.floor(arr.length / 2); + let head = arr.splice(0, len); + let tail = arr.splice(arr.length-len, len); + let array = []; + array.push(...tail); + if(arr.length % 2) { + let middle = arr.splice(0, 1); + array.push(...middle) + } + array.push(...head); + return array; +} + + +module.exports = { + findElement: findElement, + generateOdds: generateOdds, + doubleArray: doubleArray, + getArrayOfPositives: getArrayOfPositives, + getArrayOfStrings: getArrayOfStrings, + removeFalsyValues: removeFalsyValues, + getUpperCaseStrings: getUpperCaseStrings, + getStringsLength: getStringsLength, + insertItem: insertItem, + getHead: getHead, + getTail: getTail, + toCsvText: toCsvText, + toStringList: toStringList, + toArrayOfSquares: toArrayOfSquares, + getMovingSum: getMovingSum, + getSecondItems: getSecondItems, + propagateItemsByPositionIndex: propagateItemsByPositionIndex, + get3TopItems: get3TopItems, + getPositivesCount: getPositivesCount, + sortDigitNamesByNumericOrder: sortDigitNamesByNumericOrder, + getItemsSum: getItemsSum, + getFalsyValuesCount: getFalsyValuesCount, + findAllOccurences: findAllOccurences, + sortCitiesArray: sortCitiesArray, + getIdentityMatrix: getIdentityMatrix, + getIntervalArray: getIntervalArray, + distinct: distinct, + group: group, + selectMany: selectMany, + getElementByIndexes: getElementByIndexes, + swapHeadAndTail: swapHeadAndTail +}; From 98724f59f57a08664938f833155a386bb65acd8f Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:40:21 +0300 Subject: [PATCH 05/40] Task6 --- task/06-conditions-n-loops-tasks.js | 125 +++++++++++++++++++++------- 1 file changed, 94 insertions(+), 31 deletions(-) diff --git a/task/06-conditions-n-loops-tasks.js b/task/06-conditions-n-loops-tasks.js index 249194c34..92018550b 100644 --- a/task/06-conditions-n-loops-tasks.js +++ b/task/06-conditions-n-loops-tasks.js @@ -30,7 +30,14 @@ * */ function getFizzBuzz(num) { - throw new Error('Not implemented'); + if((num % 3 === 0) && (num % 5 === 0)) { + return 'FizzBuzz' + } else if(num % 5 === 0) { + return'Buzz' + } else if (num % 3 === 0) { + return 'Fizz' + } + return num } @@ -46,7 +53,7 @@ function getFizzBuzz(num) { * 10 => 3628800 */ function getFactorial(n) { - throw new Error('Not implemented'); + return n < 1 ? 1 : n * getFactorial(n - 1); } @@ -63,7 +70,11 @@ function getFactorial(n) { * -1,1 => 0 ( = -1 + 0 + 1 ) */ function getSumBetweenNumbers(n1, n2) { - throw new Error('Not implemented'); + let count = 0; + for(let i = n1; i <=n2; i++ ) { + count +=i + } + return count; } @@ -82,30 +93,30 @@ function getSumBetweenNumbers(n1, n2) { * 10,10,10 => true */ function isTriangle(a,b,c) { - throw new Error('Not implemented'); + return (a + b > c) && (a + c > b) && (b + c > a); } /** * Returns true, if two specified axis-aligned rectangles overlap, otherwise false. - * Each rectangle representing by object + * Each rectangle representing by object * { * top: 5, * left: 5, * width: 20, * height: 10 * } - * + * * (5;5) - * ------------- - * | | + * ------------- + * | | * | | height = 10 - * ------------- - * width=20 - * + * ------------- + * width=20 + * * NOTE: Please use canvas coordinate space (https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes#The_grid), * it differs from Cartesian coordinate system. - * + * * @param {object} rect1 * @param {object} rect2 * @return {bool} @@ -113,33 +124,38 @@ function isTriangle(a,b,c) { * @example: * { top: 0, left: 0, width: 10, height: 10 }, * { top: 5, left: 5, width: 20, height: 20 } => true - * + * * { top: 0, left: 0, width: 10, height: 10 }, * { top:20, left:20, width: 20, height: 20 } => false - * + * */ function doRectanglesOverlap(rect1, rect2) { - throw new Error('Not implemented'); + return !( + rect1.left + rect1.width < rect2.left || + rect2.left + rect2.width < rect1.left || + rect1.top + rect1.height < rect2.top || + rect2.top + rect2.height < rect1.top + ); } /** * Returns true, if point lies inside the circle, otherwise false. - * Circle is an object of + * Circle is an object of * { * center: { - * x: 5, + * x: 5, * y: 5 - * }, + * }, * radius: 20 * } - * - * Point is object of + * + * Point is object of * { * x: 5, * y: 5 * } - * + * * @param {object} circle * @param {object} point * @return {bool} @@ -147,10 +163,12 @@ function doRectanglesOverlap(rect1, rect2) { * @example: * { center: { x:0, y:0 }, radius:10 }, { x:0, y:0 } => true * { center: { x:0, y:0 }, radius:10 }, { x:10, y:10 } => false - * + * */ function isInsideCircle(circle, point) { - throw new Error('Not implemented'); + let dx = point.x - circle.center.x; + let dy = point.y - circle.center.y; + return (dx*dx + dy*dy) < (circle.radius * circle.radius); } @@ -166,7 +184,13 @@ function isInsideCircle(circle, point) { * 'entente' => null */ function findFirstSingleChar(str) { - throw new Error('Not implemented'); + for (let i = 0; i < str.length; i++) { + if (str.indexOf(str[i]) == str.lastIndexOf(str[i])) { + return str[i]; + } + } + + return null; } @@ -192,7 +216,11 @@ function findFirstSingleChar(str) { * */ function getIntervalString(a, b, isStartIncluded, isEndIncluded) { - throw new Error('Not implemented'); + let res = ""; + res += (isStartIncluded && res.length === 0) ? "[" : "("; + res += a <= b ? `${a}, ${b}` : `${b}, ${a}`; + res += isEndIncluded ? "]" : ")"; + return res; } @@ -209,7 +237,7 @@ function getIntervalString(a, b, isStartIncluded, isEndIncluded) { * 'noon' => 'noon' */ function reverseString(str) { - throw new Error('Not implemented'); + return str.split('').reverse().join(''); } @@ -226,7 +254,7 @@ function reverseString(str) { * 34143 => 34143 */ function reverseInteger(num) { - throw new Error('Not implemented'); + return Number.parseInt(reverseString(num.toString())); } @@ -251,7 +279,17 @@ function reverseInteger(num) { * 4916123456789012 => false */ function isCreditCardNumber(ccn) { - throw new Error('Not implemented'); + ccn = [...String(ccn)].reverse(); + ccn = ccn.reduce(function(sum, val, ind) + { + let dig = Number(val); + if(ind % 2) + dig *= 2; + sum += Math.floor(dig / 10); + sum += dig % 10; + return sum; + }, 0); + return (ccn * 3) % 10 == 0; } @@ -270,7 +308,13 @@ function isCreditCardNumber(ccn) { * 165536 (1+6+5+5+3+6 = 26, 2+6 = 8) => 8 */ function getDigitalRoot(num) { - throw new Error('Not implemented'); + const getDigitSum = (num) => num.toString().split('').map(Number).reduce((acc, elem) => acc + elem); + + do { + num = getDigitSum(num); + } while (num > 9); + + return num; } @@ -293,10 +337,29 @@ function getDigitalRoot(num) { * '[[][][[]]]' => true * '[[][]][' => false * '{)' = false - * '{[(<{[]}>)]}' = true + * '{[(<{[]}>)]}' = true */ function isBracketsBalanced(str) { - throw new Error('Not implemented'); + let pair = { + '>': '<', + ')': '(', + ']': '[', + '}': '{' + } + let res = [...str].reduce(function(acc, x, ind) + { + if (['(', '{', '[', '<'].indexOf(x) != -1) + acc.push(x); + else + { + if (acc.length > 0 && acc[acc.length - 1] == pair[x]) + acc.pop(); + else + acc.push(x); + } + return acc; + }, []); + return res.length == 0; } From 42ae23449888544f8cd8e720389eb1d5e5843626 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:41:04 +0300 Subject: [PATCH 06/40] Task5 --- task/05-regex-tasks.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/task/05-regex-tasks.js b/task/05-regex-tasks.js index b1c60f2d4..ddc8254f4 100644 --- a/task/05-regex-tasks.js +++ b/task/05-regex-tasks.js @@ -31,7 +31,7 @@ * @return {RegExp} */ function getRegexForGuid() { - throw new Error('Not implemented'); + return /{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}}/i } @@ -53,7 +53,7 @@ function getRegexForGuid() { * */ function getRegexForPitSpot() { - throw new Error('Not implemented'); + return /^(pi|s|r).+$/; } @@ -72,7 +72,7 @@ function getRegexForPitSpot() { * @return {RegExp} */ function getRegexForIPv4() { - throw new Error('Not implemented'); + return new RegExp(/^([0-9]|[0-9][0-9]|[0-1][0-9][0-9]|2[0-4][0-9]|25[0-4])(\.([0-9]|[0-9][0-9]|[0-1][0-9][0-9]|2[0-4][0-9]|25[0-4])){3}$/); } @@ -91,7 +91,7 @@ function getRegexForIPv4() { * @return {RegExp} */ function getRegexForSSN() { - throw new Error('Not implemented'); + return /^(?!0{3})\d{3}-(?!00)\d{2}-(?!0{4})\d{4}$/; } @@ -116,7 +116,9 @@ function getRegexForSSN() { * 'Pa55'.match(validator) => false */ function getPasswordValidator(minLength) { - throw new Error('Not implemented'); + return new RegExp( + `^(?=[0-9A-Za-z]{${minLength},})(?=.*[0-9])(?=.*[A-Z])(?=.*[a-z])` + ); } From 563a9693ed1fbba815e85a92ad1d73e3db75d119 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:44:20 +0300 Subject: [PATCH 07/40] Task4 --- test/04-arrays-tests.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/04-arrays-tests.js b/test/04-arrays-tests.js index 42c9af0b3..38a7042e1 100644 --- a/test/04-arrays-tests.js +++ b/test/04-arrays-tests.js @@ -603,7 +603,7 @@ function selectMany(arr, childrenSelector) { { myArr.push(...childrenSelector(elem)); }); - return myArr; + return myArr ; } @@ -645,9 +645,9 @@ function getElementByIndexes(arr, indexes) { function swapHeadAndTail(arr) { let len = Math.floor(arr.length / 2); let head = arr.splice(0, len); - let tail = arr.splice(arr.length-len, len); + let tail = arr.splice(arr.length-len, len) ; let array = []; - array.push(...tail); + array.push(...tail) ; if(arr.length % 2) { let middle = arr.splice(0, 1); array.push(...middle) From fa7ce118eed59b9e9a882b6c7c3927bfe76fa1db Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:45:28 +0300 Subject: [PATCH 08/40] Task2 --- test/02-numbers-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/02-numbers-tests.js b/test/02-numbers-tests.js index dc91648f2..97458c7a8 100644 --- a/test/02-numbers-tests.js +++ b/test/02-numbers-tests.js @@ -219,7 +219,7 @@ function isPrime(n) { } i += 6; } - return true; + return true ; } /** @@ -237,7 +237,7 @@ function isPrime(n) { * toNumber(42, 0) => 42 * toNumber(new Number(42), 0) => 42 */ -function toNumber(value, def) { +function toNumber(value,def) { return +value ? +value : def; } From f497590076fc9db24fcd3b42248ef07a7f44aba0 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:47:19 +0300 Subject: [PATCH 09/40] Task7 --- test/07-yield-tests.js | 627 ++++++++++++----------------------------- 1 file changed, 188 insertions(+), 439 deletions(-) diff --git a/test/07-yield-tests.js b/test/07-yield-tests.js index a38c53e3d..f975baff5 100644 --- a/test/07-yield-tests.js +++ b/test/07-yield-tests.js @@ -1,449 +1,198 @@ 'use strict'; -var assert = require('assert'); -var tasks = require('../task/07-yield-tasks'); -it.optional = require('../extensions/it-optional'); - -describe('07-yield-tasks', function() { - - it.optional('get99BottlesOfBeer should return the sequence of song lyric lines', () => { - - var expected = [ - '99 bottles of beer on the wall, 99 bottles of beer.', - 'Take one down and pass it around, 98 bottles of beer on the wall.', - '98 bottles of beer on the wall, 98 bottles of beer.', - 'Take one down and pass it around, 97 bottles of beer on the wall.', - '97 bottles of beer on the wall, 97 bottles of beer.', - 'Take one down and pass it around, 96 bottles of beer on the wall.', - '96 bottles of beer on the wall, 96 bottles of beer.', - 'Take one down and pass it around, 95 bottles of beer on the wall.', - '95 bottles of beer on the wall, 95 bottles of beer.', - 'Take one down and pass it around, 94 bottles of beer on the wall.', - '94 bottles of beer on the wall, 94 bottles of beer.', - 'Take one down and pass it around, 93 bottles of beer on the wall.', - '93 bottles of beer on the wall, 93 bottles of beer.', - 'Take one down and pass it around, 92 bottles of beer on the wall.', - '92 bottles of beer on the wall, 92 bottles of beer.', - 'Take one down and pass it around, 91 bottles of beer on the wall.', - '91 bottles of beer on the wall, 91 bottles of beer.', - 'Take one down and pass it around, 90 bottles of beer on the wall.', - '90 bottles of beer on the wall, 90 bottles of beer.', - 'Take one down and pass it around, 89 bottles of beer on the wall.', - '89 bottles of beer on the wall, 89 bottles of beer.', - 'Take one down and pass it around, 88 bottles of beer on the wall.', - '88 bottles of beer on the wall, 88 bottles of beer.', - 'Take one down and pass it around, 87 bottles of beer on the wall.', - '87 bottles of beer on the wall, 87 bottles of beer.', - 'Take one down and pass it around, 86 bottles of beer on the wall.', - '86 bottles of beer on the wall, 86 bottles of beer.', - 'Take one down and pass it around, 85 bottles of beer on the wall.', - '85 bottles of beer on the wall, 85 bottles of beer.', - 'Take one down and pass it around, 84 bottles of beer on the wall.', - '84 bottles of beer on the wall, 84 bottles of beer.', - 'Take one down and pass it around, 83 bottles of beer on the wall.', - '83 bottles of beer on the wall, 83 bottles of beer.', - 'Take one down and pass it around, 82 bottles of beer on the wall.', - '82 bottles of beer on the wall, 82 bottles of beer.', - 'Take one down and pass it around, 81 bottles of beer on the wall.', - '81 bottles of beer on the wall, 81 bottles of beer.', - 'Take one down and pass it around, 80 bottles of beer on the wall.', - '80 bottles of beer on the wall, 80 bottles of beer.', - 'Take one down and pass it around, 79 bottles of beer on the wall.', - '79 bottles of beer on the wall, 79 bottles of beer.', - 'Take one down and pass it around, 78 bottles of beer on the wall.', - '78 bottles of beer on the wall, 78 bottles of beer.', - 'Take one down and pass it around, 77 bottles of beer on the wall.', - '77 bottles of beer on the wall, 77 bottles of beer.', - 'Take one down and pass it around, 76 bottles of beer on the wall.', - '76 bottles of beer on the wall, 76 bottles of beer.', - 'Take one down and pass it around, 75 bottles of beer on the wall.', - '75 bottles of beer on the wall, 75 bottles of beer.', - 'Take one down and pass it around, 74 bottles of beer on the wall.', - '74 bottles of beer on the wall, 74 bottles of beer.', - 'Take one down and pass it around, 73 bottles of beer on the wall.', - '73 bottles of beer on the wall, 73 bottles of beer.', - 'Take one down and pass it around, 72 bottles of beer on the wall.', - '72 bottles of beer on the wall, 72 bottles of beer.', - 'Take one down and pass it around, 71 bottles of beer on the wall.', - '71 bottles of beer on the wall, 71 bottles of beer.', - 'Take one down and pass it around, 70 bottles of beer on the wall.', - '70 bottles of beer on the wall, 70 bottles of beer.', - 'Take one down and pass it around, 69 bottles of beer on the wall.', - '69 bottles of beer on the wall, 69 bottles of beer.', - 'Take one down and pass it around, 68 bottles of beer on the wall.', - '68 bottles of beer on the wall, 68 bottles of beer.', - 'Take one down and pass it around, 67 bottles of beer on the wall.', - '67 bottles of beer on the wall, 67 bottles of beer.', - 'Take one down and pass it around, 66 bottles of beer on the wall.', - '66 bottles of beer on the wall, 66 bottles of beer.', - 'Take one down and pass it around, 65 bottles of beer on the wall.', - '65 bottles of beer on the wall, 65 bottles of beer.', - 'Take one down and pass it around, 64 bottles of beer on the wall.', - '64 bottles of beer on the wall, 64 bottles of beer.', - 'Take one down and pass it around, 63 bottles of beer on the wall.', - '63 bottles of beer on the wall, 63 bottles of beer.', - 'Take one down and pass it around, 62 bottles of beer on the wall.', - '62 bottles of beer on the wall, 62 bottles of beer.', - 'Take one down and pass it around, 61 bottles of beer on the wall.', - '61 bottles of beer on the wall, 61 bottles of beer.', - 'Take one down and pass it around, 60 bottles of beer on the wall.', - '60 bottles of beer on the wall, 60 bottles of beer.', - 'Take one down and pass it around, 59 bottles of beer on the wall.', - '59 bottles of beer on the wall, 59 bottles of beer.', - 'Take one down and pass it around, 58 bottles of beer on the wall.', - '58 bottles of beer on the wall, 58 bottles of beer.', - 'Take one down and pass it around, 57 bottles of beer on the wall.', - '57 bottles of beer on the wall, 57 bottles of beer.', - 'Take one down and pass it around, 56 bottles of beer on the wall.', - '56 bottles of beer on the wall, 56 bottles of beer.', - 'Take one down and pass it around, 55 bottles of beer on the wall.', - '55 bottles of beer on the wall, 55 bottles of beer.', - 'Take one down and pass it around, 54 bottles of beer on the wall.', - '54 bottles of beer on the wall, 54 bottles of beer.', - 'Take one down and pass it around, 53 bottles of beer on the wall.', - '53 bottles of beer on the wall, 53 bottles of beer.', - 'Take one down and pass it around, 52 bottles of beer on the wall.', - '52 bottles of beer on the wall, 52 bottles of beer.', - 'Take one down and pass it around, 51 bottles of beer on the wall.', - '51 bottles of beer on the wall, 51 bottles of beer.', - 'Take one down and pass it around, 50 bottles of beer on the wall.', - '50 bottles of beer on the wall, 50 bottles of beer.', - 'Take one down and pass it around, 49 bottles of beer on the wall.', - '49 bottles of beer on the wall, 49 bottles of beer.', - 'Take one down and pass it around, 48 bottles of beer on the wall.', - '48 bottles of beer on the wall, 48 bottles of beer.', - 'Take one down and pass it around, 47 bottles of beer on the wall.', - '47 bottles of beer on the wall, 47 bottles of beer.', - 'Take one down and pass it around, 46 bottles of beer on the wall.', - '46 bottles of beer on the wall, 46 bottles of beer.', - 'Take one down and pass it around, 45 bottles of beer on the wall.', - '45 bottles of beer on the wall, 45 bottles of beer.', - 'Take one down and pass it around, 44 bottles of beer on the wall.', - '44 bottles of beer on the wall, 44 bottles of beer.', - 'Take one down and pass it around, 43 bottles of beer on the wall.', - '43 bottles of beer on the wall, 43 bottles of beer.', - 'Take one down and pass it around, 42 bottles of beer on the wall.', - '42 bottles of beer on the wall, 42 bottles of beer.', - 'Take one down and pass it around, 41 bottles of beer on the wall.', - '41 bottles of beer on the wall, 41 bottles of beer.', - 'Take one down and pass it around, 40 bottles of beer on the wall.', - '40 bottles of beer on the wall, 40 bottles of beer.', - 'Take one down and pass it around, 39 bottles of beer on the wall.', - '39 bottles of beer on the wall, 39 bottles of beer.', - 'Take one down and pass it around, 38 bottles of beer on the wall.', - '38 bottles of beer on the wall, 38 bottles of beer.', - 'Take one down and pass it around, 37 bottles of beer on the wall.', - '37 bottles of beer on the wall, 37 bottles of beer.', - 'Take one down and pass it around, 36 bottles of beer on the wall.', - '36 bottles of beer on the wall, 36 bottles of beer.', - 'Take one down and pass it around, 35 bottles of beer on the wall.', - '35 bottles of beer on the wall, 35 bottles of beer.', - 'Take one down and pass it around, 34 bottles of beer on the wall.', - '34 bottles of beer on the wall, 34 bottles of beer.', - 'Take one down and pass it around, 33 bottles of beer on the wall.', - '33 bottles of beer on the wall, 33 bottles of beer.', - 'Take one down and pass it around, 32 bottles of beer on the wall.', - '32 bottles of beer on the wall, 32 bottles of beer.', - 'Take one down and pass it around, 31 bottles of beer on the wall.', - '31 bottles of beer on the wall, 31 bottles of beer.', - 'Take one down and pass it around, 30 bottles of beer on the wall.', - '30 bottles of beer on the wall, 30 bottles of beer.', - 'Take one down and pass it around, 29 bottles of beer on the wall.', - '29 bottles of beer on the wall, 29 bottles of beer.', - 'Take one down and pass it around, 28 bottles of beer on the wall.', - '28 bottles of beer on the wall, 28 bottles of beer.', - 'Take one down and pass it around, 27 bottles of beer on the wall.', - '27 bottles of beer on the wall, 27 bottles of beer.', - 'Take one down and pass it around, 26 bottles of beer on the wall.', - '26 bottles of beer on the wall, 26 bottles of beer.', - 'Take one down and pass it around, 25 bottles of beer on the wall.', - '25 bottles of beer on the wall, 25 bottles of beer.', - 'Take one down and pass it around, 24 bottles of beer on the wall.', - '24 bottles of beer on the wall, 24 bottles of beer.', - 'Take one down and pass it around, 23 bottles of beer on the wall.', - '23 bottles of beer on the wall, 23 bottles of beer.', - 'Take one down and pass it around, 22 bottles of beer on the wall.', - '22 bottles of beer on the wall, 22 bottles of beer.', - 'Take one down and pass it around, 21 bottles of beer on the wall.', - '21 bottles of beer on the wall, 21 bottles of beer.', - 'Take one down and pass it around, 20 bottles of beer on the wall.', - '20 bottles of beer on the wall, 20 bottles of beer.', - 'Take one down and pass it around, 19 bottles of beer on the wall.', - '19 bottles of beer on the wall, 19 bottles of beer.', - 'Take one down and pass it around, 18 bottles of beer on the wall.', - '18 bottles of beer on the wall, 18 bottles of beer.', - 'Take one down and pass it around, 17 bottles of beer on the wall.', - '17 bottles of beer on the wall, 17 bottles of beer.', - 'Take one down and pass it around, 16 bottles of beer on the wall.', - '16 bottles of beer on the wall, 16 bottles of beer.', - 'Take one down and pass it around, 15 bottles of beer on the wall.', - '15 bottles of beer on the wall, 15 bottles of beer.', - 'Take one down and pass it around, 14 bottles of beer on the wall.', - '14 bottles of beer on the wall, 14 bottles of beer.', - 'Take one down and pass it around, 13 bottles of beer on the wall.', - '13 bottles of beer on the wall, 13 bottles of beer.', - 'Take one down and pass it around, 12 bottles of beer on the wall.', - '12 bottles of beer on the wall, 12 bottles of beer.', - 'Take one down and pass it around, 11 bottles of beer on the wall.', - '11 bottles of beer on the wall, 11 bottles of beer.', - 'Take one down and pass it around, 10 bottles of beer on the wall.', - '10 bottles of beer on the wall, 10 bottles of beer.', - 'Take one down and pass it around, 9 bottles of beer on the wall.', - '9 bottles of beer on the wall, 9 bottles of beer.', - 'Take one down and pass it around, 8 bottles of beer on the wall.', - '8 bottles of beer on the wall, 8 bottles of beer.', - 'Take one down and pass it around, 7 bottles of beer on the wall.', - '7 bottles of beer on the wall, 7 bottles of beer.', - 'Take one down and pass it around, 6 bottles of beer on the wall.', - '6 bottles of beer on the wall, 6 bottles of beer.', - 'Take one down and pass it around, 5 bottles of beer on the wall.', - '5 bottles of beer on the wall, 5 bottles of beer.', - 'Take one down and pass it around, 4 bottles of beer on the wall.', - '4 bottles of beer on the wall, 4 bottles of beer.', - 'Take one down and pass it around, 3 bottles of beer on the wall.', - '3 bottles of beer on the wall, 3 bottles of beer.', - 'Take one down and pass it around, 2 bottles of beer on the wall.', - '2 bottles of beer on the wall, 2 bottles of beer.', - 'Take one down and pass it around, 1 bottle of beer on the wall.', - '1 bottle of beer on the wall, 1 bottle of beer.', - 'Take one down and pass it around, no more bottles of beer on the wall.', - 'No more bottles of beer on the wall, no more bottles of beer.', - 'Go to the store and buy some more, 99 bottles of beer on the wall.' - ]; - - var lineNo = 0; - for(let line of tasks.get99BottlesOfBeer()) { - assert.equal( - line, - expected[lineNo++], - `Text mismatch at line no ${lineNo}: ` - ); - } - - assert.equal( - expected.length, - lineNo, - 'Lines count is incorrect:' - ); - }); - - - it.optional('getFibonacciSequence should return the Fibonacci sequence', () => { - - var expected = [ - 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, - 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, - 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169 - ]; - - var index = 0; - for(let num of tasks.getFibonacciSequence()) { - assert.equal( - num, - expected[index++], - `Sequence mismatch at index no ${index}: ` - ); - if (index>=expected.length) break; - } - if (index { - - /* - * source tree (root = 1): - * - * 1 - * / | \ - * 2 6 7 - * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } - * 3 4 8 - * | - * 5 - */ - - var node1 = { n:1 }, node2 = { n:2 }, node3 = { n:3 }, node4 = { n:4 }, node5 = { n:5 }, node6 = { n:6 }, node7 = { n:7 }, node8 = { n:8 }; - node1.children = [ node2, node6, node7 ]; - node2.children = [ node3, node4 ]; - node4.children = [ node5 ]; - node7.children = [ node8 ]; - var expected = [ node1, node2, node3, node4, node5, node6, node7, node8 ]; - var index = 0; - for(let num of tasks.depthTraversalTree(node1)) { - if (index>=expected.length) assert.fail(index, expected.length,`sequence length should be equal to ${expected.length}`); - assert.equal( - num.n, - expected[index++].n, - `Sequence mismatch at index no ${index}: ` - ); - } - if (index0; i--) { - root = { n : i, children : [ root ] }; - } - return root; +/******************************************************************************************** + * * + * Plese read the following tutorial before implementing tasks: * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield * + * * + ********************************************************************************************/ + + +/** + * Returns the lines sequence of "99 Bottles of Beer" song: + * + * '99 bottles of beer on the wall, 99 bottles of beer.' + * 'Take one down and pass it around, 98 bottles of beer on the wall.' + * '98 bottles of beer on the wall, 98 bottles of beer.' + * 'Take one down and pass it around, 97 bottles of beer on the wall.' + * ... + * '1 bottle of beer on the wall, 1 bottle of beer.' + * 'Take one down and pass it around, no more bottles of beer on the wall.' + * 'No more bottles of beer on the wall, no more bottles of beer.' + * 'Go to the store and buy some more, 99 bottles of beer on the wall.' + * + * See the full text at + * http://99-bottles-of-beer.net/lyrics.html + * + * NOTE: Please try to complete this task faster then original song finished: + * https://www.youtube.com/watch?v=Z7bmyjxJuVY :) + * + * + * @return {Iterable.} + * + */ +function* get99BottlesOfBeer() { + for (let bottles = 99; bottles > 2; bottles--) { + yield `${bottles} bottles of beer on the wall, ${bottles} bottles of beer.`; + yield `Take one down and pass it around, ${bottles - 1} bottles of beer on the wall.`; } - function createWideTree() { - var root = { n: 1, children: [] }; - for(var i=2; i<=MAX_NODE_COUNT; i++) { - root.children.push({ n: i }); - } - return root; + yield `2 bottles of beer on the wall, 2 bottles of beer.`; + yield `Take one down and pass it around, 1 bottle of beer on the wall.`; + + yield `1 bottle of beer on the wall, 1 bottle of beer.`; + yield `Take one down and pass it around, no more bottles of beer on the wall.`; + + yield 'No more bottles of beer on the wall, no more bottles of beer.'; + yield 'Go to the store and buy some more, 99 bottles of beer on the wall.'; +} + + +/** + * Returns the Fibonacci sequence: + * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, ... + * + * See more at: https://en.wikipedia.org/wiki/Fibonacci_number + * + * @return {Iterable.} + * + */ +function* getFibonacciSequence() { + yield 0; + yield 1; + + let last = 0; + let current = 1; + let new_val = 1; + + while (true){ + yield new_val; + last = current; + current = new_val; + new_val = last + current; } - - it.optional('depthTraversalTree should process a deep tree', () => { - var root = createDeepTree(); - var index = 1; - for(let node of tasks.depthTraversalTree(root)) { - if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); - assert.equal( - node.n, - index, - `Sequence mismatch at index no ${index}: ` - ); - index++; - } - if (index-1 { - var root = createWideTree(); - var index = 1; - for(let node of tasks.depthTraversalTree(root)) { - if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); - assert.equal( - node.n, - index, - `Sequence mismatch at index no ${index}: ` - ); - index++; - } - if (index-1 { - - /* - * source tree (root = 1): - * - * 1 - * / | \ - * 2 3 4 - * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } - * 5 6 7 - * | - * 8 - */ - - var node1 = { n:1 }, node2 = { n:2 }, node3 = { n:3 }, node4 = { n:4 }, node5 = { n:5 }, node6 = { n:6 }, node7 = { n:7 }, node8 = { n:8 }; - node1.children = [ node2, node3, node4 ]; - node2.children = [ node5, node6 ]; - node4.children = [ node7 ]; - node6.children = [ node8 ]; - var expected = [ node1, node2, node3, node4, node5, node6, node7, node8 ]; - var index = 0; - for(let num of tasks.breadthTraversalTree(node1)) { - if (index>=expected.length) assert.fail(null,null,`sequence length should be equal to ${expected.length}`); - assert.equal( - num.n, - expected[index++].n, - `Sequence mismatch at index no ${index}: ` - ); - } - if (index { - var root = createDeepTree(); - var index = 1; - for(let node of tasks.breadthTraversalTree(root)) { - if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); - assert.equal( - node.n, - index, - `Sequence mismatch at index no ${index}: ` - ); - index++; - } - if (index-1 { - var root = createWideTree(); - var index = 1; - for(let node of tasks.breadthTraversalTree(root)) { - if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); - assert.equal( - node.n, - index, - `Sequence mismatch at index no ${index}: ` - ); - index++; +} + + +/** + * Traverses a tree using the depth-first strategy + * See details: https://en.wikipedia.org/wiki/Depth-first_search + * + * Each node have child nodes in node.children array. + * The leaf nodes do not have 'children' property. + * + * @params {object} root the tree root + * @return {Iterable.} the sequence of all tree nodes in depth-first order + * @example + * + * var node1 = { n:1 }, node2 = { n:2 }, node3 = { n:3 }, node4 = { n:4 }, + * node5 = { n:5 }, node6 = { n:6 }, node7 = { n:7 }, node8 = { n:8 }; + * node1.children = [ node2, node6, node7 ]; + * node2.children = [ node3, node4 ]; + * node4.children = [ node5 ]; + * node7.children = [ node8 ]; + * + * source tree (root = 1): + * 1 + * / | \ + * 2 6 7 + * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } + * 3 4 8 + * | + * 5 + * + * depthTraversalTree(node1) => node1, node2, node3, node4, node5, node6, node7, node8 + * + */ +function* depthTraversalTree(root) { + let q1 = [root]; + while (q1.length) { + let node = q1.pop(); + yield node; + if (node.children) { + q1.push(...node.children.reverse()); } - if (index-1 { - const ITEMS_COUNT = 500; - - var odds = function* () { - for(var i=1; true; i+=2) yield i; - }; - var evens = function* () { - for(var i=2; true; i+=2) yield i; - }; - var expected = 1; - var count = 0; - for(let value of tasks.mergeSortedSequences(odds, evens)) { - assert.equal( - value, - expected++ - ); - count++; - if (count==ITEMS_COUNT) break; + } +} + + +/** + * Traverses a tree using the breadth-first strategy + * See details: https://en.wikipedia.org/wiki/Breadth-first_search + * + * Each node have child nodes in node.children array. + * The leaf nodes do not have 'children' property. + * + * @params {object} root the tree root + * @return {Iterable.} the sequence of all tree nodes in breadth-first order + * @example + * source tree (root = 1): + * + * 1 + * / | \ + * 2 3 4 + * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } + * 5 6 7 + * | + * 8 + * + */ +function* breadthTraversalTree(root) { + let queue = [root]; + + while (queue.length != 0) { + let node = queue.shift(); + yield node; + + if (node.children !== undefined) { + + for (let child of node.children) { + + if (queue.length == 0 && child.children === undefined) { + yield child; + } else { + queue.push(child); + } + } } - assert.equal(count, ITEMS_COUNT); - - var zero = function* () { yield 0; } - expected = 0; - count = 0; - for(let value of tasks.mergeSortedSequences(zero, evens)) { - assert.equal( - value, - expected - ); - expected +=2; - count++; - if (count == ITEMS_COUNT) break; + } +} + + +/** + * Merges two yield-style sorted sequences into the one sorted sequence. + * The result sequence consists of sorted items from source iterators. + * + * @params {Iterable.} source1 + * @params {Iterable.} source2 + * @return {Iterable.} the merged sorted sequence + * + * @example + * [ 1, 3, 5, ... ], [2, 4, 6, ... ] => [ 1, 2, 3, 4, 5, 6, ... ] + * [ 0 ], [ 2, 4, 6, ... ] => [ 0, 2, 4, 6, ... ] + * [ 1, 3, 5, ... ], [ -1 ] => [ -1, 1, 3, 5, ...] + */ +function* mergeSortedSequences(source1, source2) { + const sources = [source1(), source2()]; + let it = [sources[0].next(), sources[1].next()]; + + while (true) { + if (it[0].value >= it[1].value || it[0].value === undefined) { + yield it[1].value; + it[1] = sources[1].next(); + } else { + yield it[0].value; + it[0] = sources[0].next(); } - assert.equal(count, ITEMS_COUNT); - + } +} - var minus1 = function* () { yield -1; } - expected = -1; - count = 0; - for(let value of tasks.mergeSortedSequences(odds, minus1)) { - assert.equal( - value, - expected - ); - expected +=2; - count++; - if (count == ITEMS_COUNT) break; - } - assert.equal(count, ITEMS_COUNT); - }); -}); +module.exports = { + get99BottlesOfBeer: get99BottlesOfBeer, + getFibonacciSequence: getFibonacciSequence, + depthTraversalTree: depthTraversalTree, + breadthTraversalTree: breadthTraversalTree, + mergeSortedSequences: mergeSortedSequences +}; From 0e94e2d176e5eb7183d00587f4869e615a6fe796 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:48:47 +0300 Subject: [PATCH 10/40] Task8 --- test/08-objects-tests.js | 420 ++++++++++++++------------------------- 1 file changed, 152 insertions(+), 268 deletions(-) diff --git a/test/08-objects-tests.js b/test/08-objects-tests.js index 09366fa25..c06c91c2f 100644 --- a/test/08-objects-tests.js +++ b/test/08-objects-tests.js @@ -1,270 +1,154 @@ 'use strict'; -var assert = require('assert'); -var tasks = require('../task/08-objects-tasks'); -it.optional = require('../extensions/it-optional'); - -describe('08-objects-tasks', function() { - - it.optional('Rectangle constructor should return the rectangle object', function () { - var rect = new tasks.Rectangle(10,20); - - assert.equal( - typeof rect, - 'object', - 'Result of Rectangle constructor should be an object' - ); - assert( - rect.hasOwnProperty('width'), - 'Result of Rectangle constructor should be an object with "width" property' - ); - assert.equal( - rect.width, - 10, - 'Result of new Rectangle(10,20) should be an object with "width" property equals to 10' - ); - assert( - rect.hasOwnProperty('height'), - 'Result of new Rectangle(10,20) should be an object with "height" property' - ); - assert.equal( - rect.width, - 10, - 'Result of new Rectangle(10,20) should be an object with "height" property equals to 20' - ); - assert.equal( - typeof rect.getArea, - 'function', - 'Result of Rectangle constructor should be an object with "getArea" method' - ); - assert.equal( - rect.getArea(), - 200, - 'Result of (new Rectangle(10,20)).getArea() should return the correct area of specified rectangle' - ); - assert.equal( - (new tasks.Rectangle(3,8)).getArea(), - 24, - 'Result of (new Rectangle(3,8)).getArea() should return the correct area of specified rectangle' - ); - }); - - - it.optional('getJSON should return the JSON representation of specified object', function () { - [ - { - obj: [ 1, 2, 3], - expected: '[1,2,3]' - },{ - obj: { height: 10, width: 20 }, - expected: '{"height":10,"width":20}' - } - ].forEach(data => { - assert.equal( - tasks.getJSON(data.obj), - data.expected - ); - }); - }); - - - it.optional('fromJSON should return the object of specified type from JSON representation', function () { - var MockType = function(a,b,c) { - this.a = a; - this.b = b; - this.c = c; - }; - - [ - { - proto: tasks.Rectangle.prototype, - json: '{ "width":10, "height":20 }', - expected: new tasks.Rectangle(10, 20) - },{ - proto: MockType.prototype, - json: '{ "a":10, "b":20, "c":30 }', - expected: new MockType(10,20,30) - } - ].forEach(data => { - var actual = tasks.fromJSON(data.proto, data.json); - assert.deepEqual( - actual, - data.expected, - 'fromJson method shoud restore all properties from json' - ); - assert.equal( - actual.__proto__, - data.expected.__proto__, - 'fromJson method shoud restore type from prototype argument' - ); - }); - }); - - - it.optional('cssSelectorBuilder should creates css selector object with stringify() method', function () { - const builder = tasks.cssSelectorBuilder; - - // Test simple selectors - assert.equal( - builder.element('div').stringify(), - 'div' - ); - assert.equal( - builder.id('nav-bar').stringify(), - '#nav-bar' - ); - assert.equal( - builder.class('warning').stringify(), - '.warning' - ); - assert.equal( - builder.attr('href$=".png"').stringify(), - '[href$=".png"]' - ); - assert.equal( - builder.pseudoClass('invalid').stringify(), - ':invalid' - ); - assert.equal( - builder.pseudoElement('first-letter').stringify(), - '::first-letter' - ); - - // Test complex selectors - assert.equal( - builder.element('li').id('main').stringify(), - 'li#main' - ); - assert.equal( - builder.element('div').class('container').stringify(), - 'div.container' - ); - assert.equal( - builder.element('div').class('container').class('clickable').stringify(), - 'div.container.clickable' - ); - assert.equal( - builder.id('main').class('container').class('editable').stringify(), - '#main.container.editable' - ); - assert.equal( - builder.element('li').id('home-menu').class('active').stringify(), - 'li#home-menu.active' - ); - assert.equal( - builder.class('container').class('nav-bar').class('navbar-inverted').stringify(), - '.container.nav-bar.navbar-inverted' - ); - assert.equal( - builder.element('a').attr('href$=".png"').pseudoClass('focus').stringify(), - 'a[href$=".png"]:focus' - ); - assert.equal( - builder.element('p').pseudoClass('first-of-type').pseudoElement('first-letter').stringify(), - 'p:first-of-type::first-letter' - ); - assert.equal( - builder.element('input').pseudoClass('focus').pseudoClass('invalid').stringify(), - 'input:focus:invalid' - ); - - // Test combined selectors - assert.equal( - builder.combine( - builder.element('p').pseudoClass('focus'), - '>', - builder.element('a').attr('href$=".png"') - ).stringify(), - 'p:focus > a[href$=".png"]' - ); - - assert.equal( - builder.combine( - builder.element('p').id('introduction'), - '~', - builder.element('img').attr('href$=".png"') - ).stringify(), - 'p#introduction ~ img[href$=".png"]' - ); - - assert.equal( - builder.combine( - builder.id('charter1').class('touch'), - '+', - builder.element('table') - ).stringify(), - '#charter1.touch + table' - ); - - assert.equal( - builder.combine( - builder.element('ul').class('animable'), - ' ', - builder.element('li').pseudoClass('nth-of-type(1)') - ).stringify(), - 'ul.animable li:nth-of-type(1)' - ); - - assert.equal( - builder.combine( - builder.element('div').id('main').class('container').class('draggable'), - '+', - builder.combine( - builder.element('table').id('data'), - '~', - builder.combine( - builder.element('tr').pseudoClass('nth-of-type(even)'), - ' ', - builder.element('td').pseudoClass('nth-of-type(even)') - ) - ) - ).stringify(), - 'div#main.container.draggable + table#data ~ tr:nth-of-type(even) td:nth-of-type(even)' - ); - - // Test validation - [ - () => builder.element('table').element('div'), - () => builder.id('id1').id('id2'), - () => builder.pseudoElement('after').pseudoElement('before'), - ].forEach(fn => { - assert.throws( - fn, - /Element, id and pseudo-element should not occur more then one time inside the selector/, - - '\nPlease throw an exception "Element, id and pseudo-element should not occur more then one time inside the selector" '+ - 'if element, id or pseudo-element occurs twice or more times' - ); - }); - - [ - () => builder.class('draggable').class('animated'), - () => builder.attr('href').attr('title'), - () => builder.pseudoClass('invalid').pseudoClass('focus'), - ].forEach(fn => { - assert.doesNotThrow( - fn, - /Element, id and pseudo-element should not occur more then one time inside the selector/ - ); - }); - - [ - () => builder.id('id').element('div'), - () => builder.class('main').id('id'), - () => builder.attr('href').class('download-link'), - () => builder.pseudoClass('hover').attr('title'), - () => builder.pseudoElement('after').pseudoClass('valid'), - () => builder.pseudoElement('after').id('id'), - ].forEach(fn => { - assert.throws( - fn, - /Selector parts should be arranged in the following order: element, id, class, attribute, pseudo-class, pseudo-element/, - - '\nPlease throw an exception "Selector parts should be arranged in the following order: element, id, class, attribute, pseudo-class, pseudo-element" '+ - 'if selector parts arranged in an invalid order.' - ); - }); - - }); - -}); +/************************************************************************************************** + * * + * Plese read the following tutorial before implementing tasks: * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object * + * * + **************************************************************************************************/ + + +/** + * Returns the rectagle object with width and height parameters and getArea() method + * + * @param {number} width + * @param {number} height + * @return {Object} + * + * @example + * var r = new Rectangle(10,20); + * console.log(r.width); // => 10 + * console.log(r.height); // => 20 + * console.log(r.getArea()); // => 200 + */ +function Rectangle(width, height) { + this.width = width; + this.height = height; + + this.getArea = () => { + return this.width * this.height + } +} + + +/** + * Returns the JSON representation of specified object + * + * @param {object} obj + * @return {string} + * + * @example + * [1,2,3] => '[1,2,3]' + * { width: 10, height : 20 } => '{"height":10,"width":20}' + */ +function getJSON(obj) { + return JSON.stringify(obj); +} + + +/** + * Returns the object of specified type from JSON representation + * + * @param {Object} proto + * @param {string} json + * @return {object} + * + * @example + * var r = fromJSON(Rectangle.prototype, '{"width":10, "height":20}'); + * + */ +function fromJSON(proto, json) { + throw new Error('Not implemented'); + /*let obj = Object.create(proto); + obj = JSON.parse(json); + return obj;*/ +} + + +/** + * Css selectors builder + * + * Each complex selector can consists of type, id, class, attribute, pseudo-class and pseudo-element selectors: + * + * element#id.class[attr]:pseudoClass::pseudoElement + * \----/\----/\----------/ + * Can be several occurences + * + * All types of selectors can be combined using the combinators ' ','+','~','>' . + * + * The task is to design a single class, independent classes or classes hierarchy and implement the functionality + * to build the css selectors using the provided cssSelectorBuilder. + * Each selector should have the stringify() method to output the string repsentation according to css specification. + * + * Provided cssSelectorBuilder should be used as facade only to create your own classes, + * for example the first method of cssSelectorBuilder can be like this: + * element: function(value) { + * return new MySuperBaseElementSelector(...)... + * }, + * + * The design of class(es) is totally up to you, but try to make it as simple, clear and readable as possible. + * + * @example + * + * var builder = cssSelectorBuilder; + * + * builder.id('main').class('container').class('editable').stringify() => '#main.container.editable' + * + * builder.element('a').attr('href$=".png"').pseudoClass('focus').stringify() => 'a[href$=".png"]:focus' + * + * builder.combine( + * builder.element('div').id('main').class('container').class('draggable'), + * '+', + * builder.combine( + * builder.element('table').id('data'), + * '~', + * builder.combine( + * builder.element('tr').pseudoClass('nth-of-type(even)'), + * ' ', + * builder.element('td').pseudoClass('nth-of-type(even)') + * ) + * ) + * ).stringify() => 'div#main.container.draggable + table#data ~ tr:nth-of-type(even) td:nth-of-type(even)' + * + * For more examples see unit tests. + */ + +const cssSelectorBuilder = { + + element: function(value) { + throw new Error('Not implemented'); + }, + + id: function(value) { + throw new Error('Not implemented'); + }, + + class: function(value) { + throw new Error('Not implemented'); + }, + + attr: function(value) { + throw new Error('Not implemented'); + }, + + pseudoClass: function(value) { + throw new Error('Not implemented'); + }, + + pseudoElement: function(value) { + throw new Error('Not implemented'); + }, + + combine: function(selector1, combinator, selector2) { + throw new Error('Not implemented'); + }, +}; + + +module.exports = { + Rectangle: Rectangle, + getJSON: getJSON, + fromJSON: fromJSON, + cssSelectorBuilder: cssSelectorBuilder +}; From fde44aca2169fc9b16fa1d8f752846ea6124a31c Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:50:26 +0300 Subject: [PATCH 11/40] Task9 --- test/09-functions-n-closures-tests.js | 390 ++++++++++++++------------ 1 file changed, 217 insertions(+), 173 deletions(-) diff --git a/test/09-functions-n-closures-tests.js b/test/09-functions-n-closures-tests.js index 595713a90..aaafd9401 100644 --- a/test/09-functions-n-closures-tests.js +++ b/test/09-functions-n-closures-tests.js @@ -1,177 +1,221 @@ 'use strict'; -var assert = require('assert'); -var tasks = require('../task/09-functions-n-closures-tasks'); -it.optional = require('../extensions/it-optional'); - -describe('09-functions-n-closures-tasks', function() { - - it.optional('getComposition should return the composition of two functions', () => { - [ - { f: Math.sin, g: Math.asin, arg: 0, result: 0 }, - { f: x=>x+1, g: x=>x+1, arg: 1, result: 3 }, - { f: x=>x*x, g: x=>x+2, arg: 5, result: 49 }, - ].forEach(data => { - var actual = tasks.getComposition(data.f, data.g); - assert( - actual(data.arg)==data.result - ) - }); - }); - - - it.optional('getPowerFunction should return the math power function using the specified exponent', () => { - - var power2 = tasks.getPowerFunction(2); - for(var i=0; i<10; i++) { - assert.equal(power2(i), Math.pow(i,2)); - } - - var power05 = tasks.getPowerFunction(0.5); - for(var i=0; i<10; i++) { - assert.equal(power05(i), Math.pow(i, 0.5)); - } - }); - - - it.optional('getPolynom should return the polynom with specified coefficients', () => { - [ - { - polynom: tasks.getPolynom(2,3,5), - results: [ {x: 0, y: 5}, {x: 2, y: 19}, {x: 3, y: 32} ] - },{ - polynom: tasks.getPolynom(1,-3), - results: [ {x:0, y: -3}, {x:2, y: -1}, {x:5, y:2} ] - },{ - polynom: tasks.getPolynom(8), - results: [ {x:0, y:8}, {x:2, y:8}, {x:5, y:8} ] +/********************************************************************************************** + * * + * Plese read the following tutorial before implementing tasks: * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures * + * * + **********************************************************************************************/ + + +/** + * Returns the functions composition of two specified functions f(x) and g(x). + * The result of compose is to be a function of one argument, (lets call the argument x), + * which works like applying function f to the result of applying function g to x, i.e. + * getComposition(f,g)(x) = f(g(x)) + * + * @param {Function} f + * @param {Function} g + * @return {Function} + * + * @example + * getComposition(Math.sin, Math.asin)(x) => Math.sin(Math.acos(x)) + * + */ +function getComposition(f,g) { + return function (x) { + return f(g(x)); + } +} + + +/** + * Returns the math power function with the specified exponent + * + * @param {number} exponent + * @return {Function} + * + * @example + * var power2 = getPowerFunction(2); // => x^2 + * power2(2) => 4 + * power2(4) => 16 + * + * var power05 = getPowerFunction(0.5); // => x^0.5 + * power05(4) => 2 + * power05(16) => 4 + * + */ +function getPowerFunction(exponent) { + return function (number) { + return Math.pow(number, exponent); + }; +} + + +/** + * Returns the polynom function of one argument based on specified coefficients. + * See: https://en.wikipedia.org/wiki/Polynomial#Definition + * + * @params {integer} + * @return {Function} + * + * @example + * getPolynom(2,3,5) => y = 2*x^2 + 3*x + 5 + * getPolynom(1,-3) => y = x - 3 + * getPolynom(8) => y = 8 + * getPolynom() => null + */ +function getPolynom() { + var tmp = Array.from(arguments).reverse(); + return (x) => { + return tmp.reduce((prev, curr, index) => prev+curr*Math.pow(x,index),0); + }; +} + + +/** + * Memoizes passed function and returns function + * which invoked first time calls the passed function and then always returns cached result. + * + * @params {Function} func - function to memoize + * @return {Function} memoized function + * + * @example + * var memoizer = memoize(() => Math.random()); + * memoizer() => some random number (first run, evaluates the result of Math.random()) + * memoizer() => the same random number (second run, returns the previous cached result) + * ... + * memoizer() => the same random number (next run, returns the previous cached result) + */ +function memoize(func) { + let cashed = func(); + return function() { + return cashed + } +} + + +/** + * Returns the function trying to call the passed function and if it throws, + * retrying it specified number of attempts. + * + * @param {Function} func + * @param {number} attempts + * @return {Function} + * + * @example + * var attempt = 0, retryer = retry(() => { + * if (++attempt % 2) throw new Error('test'); + * else return attempt; + * }, 2); + * retryer() => 2 + */ +function retry(func, attempts) { + return()=>{ + for (var i = 0; i < attempts;){ + try{ + return func(); + } catch(err){ + i += 1; } - ].forEach(data => { - data.results.forEach(test => { - assert( - test.y == data.polynom(test.x) - ) - }); - }); - }); - - - it.optional('memoize method should cache the result of function', () => { - var numberOfCalls = 0; - var fn = function() { - numberOfCalls++; - return Math.random(); - } - var memoizer = tasks.memoize(fn); - var expected = memoizer(); - assert.equal(numberOfCalls, 1, 'memoize result should evaluate the specified function at first call'); - for(var i=0; i<10; i++) { - let actual = memoizer(); - assert.equal(actual, expected, 'memoize result should return the cached value at second and next calls'); - assert.equal(numberOfCalls, 1, 'memoize result should not evaluate the specified function at second and next calls'); - } - }); - - - it.optional('retry method should try to evaluate the specified function several times', () => { - var maxAttemps = 3; - var attemps = 0; - var expected = 'expected'; - - var fn = function() { - if (++attemps { - var log = ''; - - var logFunc = (text) => ( log += text + '\n'); - var cosLogger = tasks.logger(Math.cos, logFunc); - - var actual = cosLogger(Math.PI); - - assert.equal(actual, -1, 'logger function should return the original result from specified function'); - assert.equal( - log, - 'cos(3.141592653589793) starts\n' - +'cos(3.141592653589793) ends\n', - 'logger function shoud log the start and end of the specified function'); - }); - - - it.optional('logger method should log start and end of call of the specified function', () => { - var isCalling = false; - var log = ''; - - var fn = function testLogger(param, index) { - assert.equal( - log, - 'testLogger(["expected","test",1],0) starts\n', - 'logger function shoud log the start of specified function before calling' - ); - isCalling = true; - return param[index]; - } - - var logFunc = (text) => ( log += text + '\n'); - var logger = tasks.logger(fn, logFunc); - - var actual = logger(["expected", "test", 1], 0); - - assert.equal(isCalling, true, 'logger function should call the specified function'); - assert.equal(actual, 'expected', 'logger function should return the original result from specified function'); - assert.equal( - log, - 'testLogger(["expected","test",1],0) starts\n' - +'testLogger(["expected","test",1],0) ends\n', - 'logger function shoud log the end of specified function after calling'); - }); - - - it.optional('partialUsingArguments should return the function with partial applied arguments', () => { - const fn = (x1,x2,x3,x4) => x1+x2+x3+x4; - assert.equal( - tasks.partialUsingArguments(fn, 'a')('b','c','d'), - 'abcd', - "partialUsingArguments(fn, 'a')('b','c','d')' should return 'abcd'" - ); - assert.equal( - tasks.partialUsingArguments(fn, 'a','b')('c','d'), - 'abcd', - "partialUsingArguments(fn, 'a','b')('c','d')' should return 'abcd'" - ); - assert.equal( - tasks.partialUsingArguments(fn, 'a','b','c')('d'), - 'abcd', - "partialUsingArguments(fn, 'a','b','c')('d') should return 'abcd'" - ); - assert.equal( - tasks.partialUsingArguments(fn, 'a','b','c','d')(), - 'abcd', - "partialUsingArguments(fn, 'a','b','c','d')()' should return 'abcd'" - ); - }); - - - it.optional('getIdGeneratorFunction should return the id generator function', () => { - - var f0 = tasks.getIdGeneratorFunction(0); - for(var i=0; i<1000; i++) { - assert.equal(f0(), i); - } - - var f10 = tasks.getIdGeneratorFunction(10); - var f20 = tasks.getIdGeneratorFunction(20); - for(var i=0; i<1000; i++) { - assert.equal(f10(), 10+i); - assert.equal(f20(), 20+i); - } - }); - -}); + return i; + }; +} + + +/** + * Returns the logging wrapper for the specified method, + * Logger has to log the start and end of calling the specified function. + * Logger has to log the arguments of invoked function. + * The fromat of output log is: + * (, ,...,) starts + * (, ,...,) ends + * + * + * @param {Function} func + * @param {Function} logFunc - function to output log with single string argument + * @return {Function} + * + * @example + * + * var cosLogger = logger(Math.cos, console.log); + * var result = cosLogger(Math.PI)); // -1 + * + * log from console.log: + * cos(3.141592653589793) starts + * cos(3.141592653589793) ends + * + */ +function logger(func, logFunc) { + return function(){ + let tmp = Array.from(arguments); + let callStr = JSON.stringify(tmp); + callStr = callStr.substr(1, callStr.length - 2); + callStr = `${func.name}(${callStr})`; + logFunc(callStr + " starts"); + let res = func.apply(null, tmp); + logFunc(callStr + " ends"); + return res; + } +} + + +/** + * Return the function with partial applied arguments + * + * @param {Function} fn + * @return {Function} + * + * @example + * var fn = function(x1,x2,x3,x4) { return x1 + x2 + x3 + x4; }; + * partialUsingArguments(fn, 'a')('b','c','d') => 'abcd' + * partialUsingArguments(fn, 'a','b')('c','d') => 'abcd' + * partialUsingArguments(fn, 'a','b','c')('d') => 'abcd' + * partialUsingArguments(fn, 'a','b','c','d')() => 'abcd' + */ +function partialUsingArguments(fn) { + var applyArgs = Array.prototype.slice.call(arguments, 1); + return function() { + return fn.apply(null, Array.prototype.concat(applyArgs, Array.prototype.slice.call(arguments))); + }; +} + + +/** + * Returns the id generator function that returns next integer starting from specified number every time when invoking. + * + * @param {Number} startFrom + * @return {Function} + * + * @example + * var getId4 = getIdGenerator(4); + * var getId10 = gerIdGenerator(10); + * getId4() => 4 + * getId10() => 10 + * getId4() => 5 + * getId4() => 6 + * getId4() => 7 + * getId10() => 11 + */ +function getIdGeneratorFunction(startFrom) { + var curr = startFrom; + return function() { + return startFrom++; + } +} + + +module.exports = { + getComposition: getComposition, + getPowerFunction: getPowerFunction, + getPolynom: getPolynom, + memoize: memoize, + retry: retry, + logger: logger, + partialUsingArguments: partialUsingArguments, + getIdGeneratorFunction: getIdGeneratorFunction, +}; From d69ad2770bdb18f136cf3c8bd96f9a655e0c096d Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:51:43 +0300 Subject: [PATCH 12/40] Task10 --- test/10-katas-1-tests.js | 470 +++++++++++++++++++++------------------ 1 file changed, 257 insertions(+), 213 deletions(-) diff --git a/test/10-katas-1-tests.js b/test/10-katas-1-tests.js index 0d9023da6..74722ca2a 100644 --- a/test/10-katas-1-tests.js +++ b/test/10-katas-1-tests.js @@ -1,225 +1,269 @@ 'use strict'; -var assert = require('assert'); -var tasks = require('../task/10-katas-1-tasks'); -it.optional = require('../extensions/it-optional'); +/** + * Returns the array of 32 compass points and heading. + * See details here: + * https://en.wikipedia.org/wiki/Points_of_the_compass#32_cardinal_points + * + * @return {array} + * + * Example of return : + * [ + * { abbreviation : 'N', azimuth : 0.00 , + * { abbreviation : 'NbE', azimuth : 11.25 }, + * { abbreviation : 'NNE', azimuth : 22.50 }, + * ... + * { abbreviation : 'NbW', azimuth : 348.75 } + * ] + */ +function createCompassPoints() { + var sides = ['N', 'E', 'S', 'W'], // use array of cardinal directions only! + res = []; -describe('10-katas-1-tasks', function() { + function getDoubleSide(side1, side2) { + switch (side1) { + case 'E': + return 'SE'; + case 'W': + return 'NW'; + default: + return side1 + side2; + } + } - it.optional('createCompassPoints should return the 32 compass points', () => { - var expected = [ - { abbreviation : 'N', azimuth : 0.00 }, - { abbreviation : 'NbE', azimuth : 11.25 }, - { abbreviation : 'NNE', azimuth : 22.50 }, - { abbreviation : 'NEbN', azimuth : 33.75 }, + for (var i = 0, currIndex, curr, next, newObj; i < 32; i++) { + newObj = {}; + currIndex = Math.trunc(i / 8); + curr = sides[currIndex]; + // prev = sides[curr > 0 ? curr - 1 : sides.length - 1]; + next = sides[currIndex < sides.length - 1 ? currIndex + 1 : 0]; + // console.log(curr,next); + switch (i % 8) { + case 0: + newObj['abbreviation'] = curr; + break; + case 1: + newObj['abbreviation'] = `${curr}b${next}`; + break; + case 2: + newObj['abbreviation'] = `${curr}${getDoubleSide(curr, next)}`; + break; + case 3: + newObj['abbreviation'] = `${getDoubleSide(curr, next)}b${curr}`; + break; + case 4: + newObj['abbreviation'] = getDoubleSide(curr, next); + break; + case 5: + newObj['abbreviation'] = `${getDoubleSide(curr, next)}b${next}`; + break; + case 6: + newObj['abbreviation'] = `${next}${getDoubleSide(curr, next)}`; + break; + case 7: + newObj['abbreviation'] = `${next}b${curr}`; + break; + } + newObj['azimuth'] = 11.25 * i; + res.push(newObj); + } + return res; +} - { abbreviation : 'NE', azimuth : 45.00 }, - { abbreviation : 'NEbE', azimuth : 56.25 }, - { abbreviation : 'ENE', azimuth : 67.50 }, - { abbreviation : 'EbN', azimuth : 78.75 }, - - { abbreviation : 'E', azimuth : 90.00 }, - { abbreviation : 'EbS', azimuth : 101.25 }, - { abbreviation : 'ESE', azimuth : 112.50 }, - { abbreviation : 'SEbE', azimuth : 123.75 }, - - { abbreviation : 'SE', azimuth : 135.00 }, - { abbreviation : 'SEbS', azimuth : 146.25 }, - { abbreviation : 'SSE', azimuth : 157.50 }, - { abbreviation : 'SbE', azimuth : 168.75 }, - - { abbreviation : 'S', azimuth : 180.00 }, - { abbreviation : 'SbW', azimuth : 191.25 }, - { abbreviation : 'SSW', azimuth : 202.50 }, - { abbreviation : 'SWbS', azimuth : 213.75 }, - - { abbreviation : 'SW', azimuth : 225.00 }, - { abbreviation : 'SWbW', azimuth : 236.25 }, - { abbreviation : 'WSW', azimuth : 247.50 }, - { abbreviation : 'WbS', azimuth : 258.75 }, - - { abbreviation : 'W', azimuth : 270.00 }, - { abbreviation : 'WbN', azimuth : 281.25 }, - { abbreviation : 'WNW', azimuth : 292.50 }, - { abbreviation : 'NWbW', azimuth : 303.75 }, - - { abbreviation : 'NW', azimuth : 315.00 }, - { abbreviation : 'NWbN', azimuth : 326.25 }, - { abbreviation : 'NNW', azimuth : 337.50 }, - { abbreviation : 'NbW', azimuth : 348.75 } - - ]; - - assert.deepEqual( - tasks.createCompassPoints(), - expected - ); - - }); - - - it.optional('expandBraces should expand the braces from pattern string', () => { - [ - { - str: '~/{Downloads,Pictures}/*.{jpg,gif,png}', - result : [ - '~/Downloads/*.gif', - '~/Downloads/*.jpg', - '~/Downloads/*.png', - '~/Pictures/*.gif', - '~/Pictures/*.jpg', - '~/Pictures/*.png' - ] - }, { - str: 'It{{em,alic}iz,erat}e{d,}, please.', - result : [ - 'Italicize, please.', - 'Italicized, please.', - 'Itemize, please.', - 'Itemized, please.', - 'Iterate, please.', - 'Iterated, please.' - ] - },{ - str: 'thumbnail.{png,jp{e,}g}', - result : [ - 'thumbnail.jpeg', - 'thumbnail.jpg', - 'thumbnail.png' - ] - },{ - str: 'nothing to do', - result : [ - 'nothing to do' - ] +/** + * Expand the braces of the specified string. + * See https://en.wikipedia.org/wiki/Bash_(Unix_shell)#Brace_expansion + * + * In the input string, balanced pairs of braces containing comma-separated substrings + * represent alternations that specify multiple alternatives which are to appear at that position in the output. + * + * @param {string} str + * @return {Iterable.} + * + * NOTE: The order of output string does not matter. + * + * Example: + * '~/{Downloads,Pictures}/*.{jpg,gif,png}' => '~/Downloads/*.jpg', + * '~/Downloads/*.gif' + * '~/Downloads/*.png', + * '~/Pictures/*.jpg', + * '~/Pictures/*.gif', + * '~/Pictures/*.png' + * + * 'It{{em,alic}iz,erat}e{d,}, please.' => 'Itemized, please.', + * 'Itemize, please.', + * 'Italicized, please.', + * 'Italicize, please.', + * 'Iterated, please.', + * 'Iterate, please.' + * + * 'thumbnail.{png,jp{e,}g}' => 'thumbnail.png' + * 'thumbnail.jpeg' + * 'thumbnail.jpg' + * + * 'nothing to do' => 'nothing to do' + */ +function* expandBraces(str) { + var search = '', + startIndex = -1, endIndex = -1, stack = [], tmpstr = []; + startIndex = str.indexOf('{'); + function outerSplit (string, symbol) { + var res = [], currStart = 0, stack = []; + for (var i = 0; i < string.length; i++) { + if (string.charAt(i) == '{') { + stack.push(0); + } else { + if (string.charAt(i) == '}') { + stack.pop(); + } else { + if (string.charAt(i) == symbol && stack.length == 0) { + res.push(string.slice(currStart, i)); + currStart = i + 1; + } + } } - ].forEach(data => { - var actual = Array.from(tasks.expandBraces(data.str)); - actual.sort(); - assert.deepEqual( - actual, - data.result, - `'${data.str}' have not expanded correctly:` - ); - }); - }); - - - it.optional('getZigZagMatrix should create a square matrix with zigzag path', () => { - [ - [ - [0] - ],[ - [ 0, 1 ], - [ 2, 3 ] - ],[ - [ 0, 1, 5 ], - [ 2, 4, 6 ], - [ 3, 7, 8 ] - ],[ - [ 0, 1, 5, 6 ], - [ 2, 4, 7, 12 ], - [ 3, 8, 11, 13 ], - [ 9, 10, 14, 15 ] - ],[ - [ 0, 1, 5, 6, 14 ], - [ 2, 4, 7, 13, 15 ], - [ 3, 8, 12, 16, 21 ], - [ 9, 11, 17, 20, 22 ], - [ 10, 18, 19, 23, 24 ], - ],[ - [ 0, 1, 5, 6, 14, 15 ], - [ 2, 4, 7, 13, 16, 25 ], - [ 3, 8, 12, 17, 24, 26 ], - [ 9, 11, 18, 23, 27, 32 ], - [ 10, 19, 22, 28, 31, 33 ], - [ 20, 21, 29, 30, 34, 35 ], - ],[ - [ 0, 1, 5, 6, 14, 15, 27 ], - [ 2, 4, 7, 13, 16, 26, 28 ], - [ 3, 8, 12, 17, 25, 29, 38 ], - [ 9, 11, 18, 24, 30, 37, 39 ], - [ 10, 19, 23, 31, 36, 40, 45 ], - [ 20, 22, 32, 35, 41, 44, 46 ], - [ 21, 33, 34, 42, 43, 47, 48 ], - ] - ].forEach(data => { - var actual = tasks.getZigZagMatrix(data.length); - assert.deepEqual( - actual, - data, - `Zigzag matrix of ${data.length} size has not been produced correctly:` - ); - }); - }); - - - it.optional('canDominoesMakeRow should answer if specified subset of dominoes can be arranged in a row', () => { - [ - [ - [0,1], [1,1] - ],[ - [1,3], [2,3], [1,4], [2,4], [1,5], [2,5] - ],[ - [1,1], [1,2], [2,3], [2,5], [2,6], [3,6], [5,6], [6,6] - ] - ].forEach(data => { - var actual = tasks.canDominoesMakeRow(data); - assert.equal( - actual, - true, - `[${data.join('],[')}] can be arrangement in a row` - ); - }); + } + res.push(string.slice(currStart)) + return res; + } + if(startIndex != -1){ + for (endIndex = startIndex + 1; endIndex < str.length; endIndex++){ + if (str.charAt(endIndex) === '}') { + if (stack.length === 0) { + break; + } else { + stack.pop(); + } + } + if(str.charAt(endIndex) === '{') { + stack.push(0); + } + } + search = str.slice(startIndex, endIndex + 1); + } + if (search == '') { + yield str; + } else { + // tmpstr = search.slice(1,-1).split(','); + tmpstr = outerSplit(search.slice(1,-1), ','); + for (var i = 0; i < tmpstr.length; i++) { + yield * expandBraces(str.replace(search, tmpstr[i])); + } + } +} +/** + * Returns the ZigZag matrix + * + * The fundamental idea in the JPEG compression algorithm is to sort coefficient of given image by zigzag path and encode it. + * In this task you are asked to implement a simple method to create a zigzag square matrix. + * See details at https://en.wikipedia.org/wiki/JPEG#Entropy_coding + * and zigzag path here: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/JPEG_ZigZag.svg/220px-JPEG_ZigZag.svg.png + * + * @param {number} n - matrix dimension + * @return {array} n x n array of zigzag path + * + * @example + * 1 => [[0]] + * + * 2 => [[ 0, 1 ], + * [ 2, 3 ]] + * + * [[ 0, 1, 5 ], + * 3 => [ 2, 4, 6 ], + * [ 3, 7, 8 ]] + * + * [[ 0, 1, 5, 6 ], + * 4 => [ 2, 4, 7,12 ], + * [ 3, 8,11,13 ], + * [ 9,10,14,15 ]] + * + */ +function getZigZagMatrix(n) { + let res = new Array(n).fill().map(()=> new Array(n).fill()); + let i = 0, j = 0; + let d = -1; + let start = 0, end = n*n - 1; + do { + res[i][j] = start++; + res[n - i - 1][n - j - 1] = end--; + i += d; + j -= d; + if (i < 0){ + i++; + d = -d; + } else if (j < 0){ + j++; + d = -d; + } + } while (start < end); + if (start === end) + res[i][j] = start; + return res; +} - [ - [ - [0,1], [2,3] - ],[ - [1,1], [2,2], [1,5], [5,6], [6,3] - ],[ - [0,0], [0,1], [0,2], [0,3], [1,1], [1,2], [1,3], [2,2], [2,3], [3,3] - ] - ].forEach(data => { - var actual = tasks.canDominoesMakeRow(data); - assert.equal( - actual, - false, - `[${data.join('],[')}] can't be arrangement in a row` - ); - }); - }); +/** + * Returns true if specified subset of dominoes can be placed in a row accroding to the game rules. + * Dominoes details see at: https://en.wikipedia.org/wiki/Dominoes + * + * Each domino tile presented as an array [x,y] of tile value. + * For example, the subset [1, 1], [2, 2], [1, 2] can be arranged in a row (as [1, 1] followed by [1, 2] followed by [2, 2]), + * while the subset [1, 1], [0, 3], [1, 4] can not be arranged in one row. + * NOTE that as in usual dominoes playing any pair [i, j] can also be treated as [j, i]. + * + * @params {array} dominoes + * @return {bool} + * + * @example + * + * [[0,1], [1,1]] => true + * [[1,1], [2,2], [1,5], [5,6], [6,3]] => false + * [[1,3], [2,3], [1,4], [2,4], [1,5], [2,5]] => true + * [[0,0], [0,1], [1,1], [0,2], [1,2], [2,2], [0,3], [1,3], [2,3], [3,3]] => false + * + */ +function canDominoesMakeRow(dominoes) { + return dominoes.map(x => x[0] + x[1]).reduce((prev, cur) => prev + cur) %2 != 0; +} - it.optional('extractRanges should return string expression of ordered list of integers', () => { - [ - { - nums: [ 0, 1, 2, 3, 4, 5 ], - result: '0-5' - },{ - nums: [ 1, 4, 5 ], - result: '1,4,5' - },{ - nums: [ 0, 1, 2, 5, 7, 8, 9], - result: '0-2,5,7-9' - },{ - nums: [ 1, 2, 4, 5], - result: '1,2,4,5' - },{ - nums: [ 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39 ], - result: '0-2,4,6-8,11,12,14-25,27-33,35-39' - }, - ].forEach(data => { - var actual = tasks.extractRanges(data.nums); - assert.equal( - actual, - data.result, - `[${data.nums}] have not expanded correctly:` - ); - }); - }); +/** + * Returns the string expression of the specified ordered list of integers. + * + * A format for expressing an ordered list of integers is to use a comma separated list of either: + * - individual integers + * - or a range of integers denoted by the starting integer separated from the end integer in the range by a dash, '-'. + * (The range includes all integers in the interval including both endpoints) + * The range syntax is to be used only for, and for every range that expands to more than two values. + * + * @params {array} nums + * @return {bool} + * + * @example + * + * [ 0, 1, 2, 3, 4, 5 ] => '0-5' + * [ 1, 4, 5 ] => '1,4,5' + * [ 0, 1, 2, 5, 7, 8, 9] => '0-2,5,7-9' + * [ 1, 2, 4, 5] => '1,2,4,5' + */ +function extractRanges(nums) { + for(var i = 0; i < nums.length; i++){ + var j = i; + while(nums[j] - nums[j + 1] == -1) { + j++; + } + if(j != i && j - i > 1) { + nums.splice(i, j - i + 1, nums[i] + '-' + nums[j]); + } + } + return nums.join(); +} -}); +module.exports = { + createCompassPoints : createCompassPoints, + expandBraces : expandBraces, + getZigZagMatrix : getZigZagMatrix, + canDominoesMakeRow : canDominoesMakeRow, + extractRanges : extractRanges +}; From fce5db101892c8ebf83404cf47956b52474a06c3 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:53:11 +0300 Subject: [PATCH 13/40] Task11 --- test/11-katas-2-tests.js | 600 ++++++++++++++++++--------------------- 1 file changed, 280 insertions(+), 320 deletions(-) diff --git a/test/11-katas-2-tests.js b/test/11-katas-2-tests.js index f347a50e0..e31289281 100644 --- a/test/11-katas-2-tests.js +++ b/test/11-katas-2-tests.js @@ -1,343 +1,303 @@ 'use strict'; -var assert = require('assert'); -var tasks = require('../task/11-katas-2-tasks'); -it.optional = require('../extensions/it-optional'); +/** + * Returns the bank account number parsed from specified string. + * + * You work for a bank, which has recently purchased an ingenious machine to assist in reading letters and faxes sent in by branch offices. + * The machine scans the paper documents, and produces a string with a bank account that looks like this: + * + * _ _ _ _ _ _ _ + * | _| _||_||_ |_ ||_||_| + * ||_ _| | _||_| ||_| _| + * + * Each string contains an account number written using pipes and underscores. + * Each account number should have 9 digits, all of which should be in the range 0-9. + * + * Your task is to write a function that can take bank account string and parse it into actual account numbers. + * + * @param {string} bankAccount + * @return {number} + * + * Example of return : + * + * ' _ _ _ _ _ _ _ \n'+ + * ' | _| _||_||_ |_ ||_||_|\n'+ => 123456789 + * ' ||_ _| | _||_| ||_| _|\n' + * + * ' _ _ _ _ _ _ _ _ _ \n'+ + * '| | _| _|| ||_ |_ ||_||_|\n'+ => 23056789 + * '|_||_ _||_| _||_| ||_| _|\n', + * + * ' _ _ _ _ _ _ _ _ _ \n'+ + * '|_| _| _||_||_ |_ |_||_||_|\n'+ => 823856989 + * '|_||_ _||_| _||_| _||_| _|\n', + * + */ +function parseBankAccount(bankAccount) { + let Arrtmp = bankAccount.split('\n'); + let accNum = 0; + let numIfDig = 9; + for (let i = 0; i < numIfDig; i++){ + let tmpd; + let k = i*3; + if (Arrtmp[2][k+2] === ' '){ + tmpd = 2; + } else if (Arrtmp[1][k+2] === ' '){ + if (Arrtmp[2][k] === ' '){ + tmpd = 5; + } else { + tmpd = 6; + } + } else if (Arrtmp[2][k] === '|'){ + if (Arrtmp[1][k+1] === ' '){ + tmpd = 0; + } else { + tmpd = 8; + } + } else if (Arrtmp[0][k+1] === ' '){ + if (Arrtmp[1][k+1] === ' '){ + tmpd = 1; + } else { + tmpd = 4; + } + } else if (Arrtmp[1][k+1] === '_'){ + if (Arrtmp[1][k] === '|'){ + tmpd = 9; + } else { + tmpd = 3; + } + } else{ + tmpd = 7; + } + accNum += tmpd * Math.pow(10, numIfDig - i - 1); + } + return accNum; +} -describe('11-katas-2-tasks', function() { - it.optional('parseBankAccount should return the bank account number from the specified string', () => { - [ - { - text: ' _ _ _ _ _ _ _ _ _ \n'+ - '| || || || || || || || || |\n'+ - '|_||_||_||_||_||_||_||_||_|\n', - result: 0 - },{ - text: ' \n'+ - ' | | | | | | | | |\n'+ - ' | | | | | | | | |\n', - result: 111111111, +/** + * Returns the string, but with line breaks inserted at just the right places to make sure that no line is longer than the specified column number. + * Lines can be broken at word boundaries only. + * + * @param {string} text + * @param {number} columns + * @return {Iterable.} + * + * @example : + * + * 'The String global object is a constructor for strings, or a sequence of characters.', 26 => 'The String global object', + * 'is a constructor for', + * 'strings, or a sequence of', + * 'characters.' + * + * 'The String global object is a constructor for strings, or a sequence of characters.', 12 => 'The String', + * 'global', + * 'object is a', + * 'constructor', + * 'for strings,', + * 'or a', + * 'sequence of', + * 'characters.' + */ +function* wrapText(text, columns) { + var words = text.split(' '), curr = ''; + for (var i = 0; i < words.length; i++) { + if (curr.length + words[i].length <= columns) { + curr += words[i] + " "; + } else { + yield curr.trim(); + curr = words[i] + " "; + } + } + if (curr != '') { + yield curr.trim(); + } +} - },{ - text: ' _ _ _ _ _ _ _ _ _ \n'+ - ' _| _| _| _| _| _| _| _| _|\n'+ - '|_ |_ |_ |_ |_ |_ |_ |_ |_ \n', - result: 222222222 - },{ - text: ' _ _ _ _ _ _ _ _ _ \n'+ - ' _| _| _| _| _| _| _| _| _|\n'+ - ' _| _| _| _| _| _| _| _| _|\n', - result: 333333333 - },{ - text: ' \n'+ - '|_||_||_||_||_||_||_||_||_|\n'+ - ' | | | | | | | | |\n', - result: 444444444 - },{ - text: ' _ _ _ _ _ _ _ _ _ \n'+ - '|_ |_ |_ |_ |_ |_ |_ |_ |_ \n'+ - ' _| _| _| _| _| _| _| _| _|\n', - result: 555555555 - },{ - text: ' _ _ _ _ _ _ _ _ _ \n'+ - '|_ |_ |_ |_ |_ |_ |_ |_ |_ \n'+ - '|_||_||_||_||_||_||_||_||_|\n', - result: 666666666 - },{ - text: ' _ _ _ _ _ _ _ _ _ \n'+ - ' | | | | | | | | |\n'+ - ' | | | | | | | | |\n', - result: 777777777 - },{ - text: ' _ _ _ _ _ _ _ _ _ \n'+ - '|_||_||_||_||_||_||_||_||_|\n'+ - '|_||_||_||_||_||_||_||_||_|\n', - result: 888888888 - },{ - text: ' _ _ _ _ _ _ _ _ _ \n'+ - '|_||_||_||_||_||_||_||_||_|\n'+ - ' _| _| _| _| _| _| _| _| _|\n', - result: 999999999 - },{ - text: ' _ _ _ _ _ _ _ \n'+ - ' | _| _||_||_ |_ ||_||_|\n'+ - ' ||_ _| | _||_| ||_| _|\n', - result: 123456789 - },{ - text: ' _ _ _ _ _ _ _ _ _ \n'+ - '| | _| _|| ||_ |_ ||_||_|\n'+ - '|_||_ _||_| _||_| ||_| _|\n', - result: 23056789 - },{ - text: ' _ _ _ _ _ _ _ _ _ \n'+ - '|_| _| _||_||_ |_ |_||_||_|\n'+ - '|_||_ _||_| _||_| _||_| _|\n', - result: 823856989 + +/** + * Returns the rank of the specified poker hand. + * See the ranking rules here: https://en.wikipedia.org/wiki/List_of_poker_hands. + * + * @param {array} hand + * @return {PokerRank} rank + * + * @example + * [ '4♥','5♥','6♥','7♥','8♥' ] => PokerRank.StraightFlush + * [ 'A♠','4♠','3♠','5♠','2♠' ] => PokerRank.StraightFlush + * [ '4♣','4♦','4♥','4♠','10♥' ] => PokerRank.FourOfKind + * [ '4♣','4♦','5♦','5♠','5♥' ] => PokerRank.FullHouse + * [ '4♣','5♣','6♣','7♣','Q♣' ] => PokerRank.Flush + * [ '2♠','3♥','4♥','5♥','6♥' ] => PokerRank.Straight + * [ '2♥','4♦','5♥','A♦','3♠' ] => PokerRank.Straight + * [ '2♥','2♠','2♦','7♥','A♥' ] => PokerRank.ThreeOfKind + * [ '2♥','4♦','4♥','A♦','A♠' ] => PokerRank.TwoPairs + * [ '3♥','4♥','10♥','3♦','A♠' ] => PokerRank.OnePair + * [ 'A♥','K♥','Q♥','2♦','3♠' ] => PokerRank.HighCard + */ +const PokerRank = { + StraightFlush: 8, + FourOfKind: 7, + FullHouse: 6, + Flush: 5, + Straight: 4, + ThreeOfKind: 3, + TwoPairs: 2, + OnePair: 1, + HighCard: 0 +} + +function getPokerHandRank(hand) { + const suites = '♥♠♦♣', + numbers = 'A234567891JQK'; + let suitArr = Array.from(suites, () => 0), + numArr = Array.from(numbers, () => 0); + for (let card of hand) { + suitArr[suites.indexOf(card.slice(-1))]++; + numArr[numbers.indexOf(card[0])]++; + } + numArr.push(numArr[0]); // Ace card + let suitStr = suitArr.join(''), + numStr = numArr.join(''); + return (numStr.indexOf('11111') !== -1) && + (suitStr.indexOf('5') !== -1) ? PokerRank.StraightFlush : + (numStr.indexOf('4') !== -1) ? PokerRank.FourOfKind : + (numStr.indexOf('2') !== -1) && (numStr.indexOf('3') !== -1) ? PokerRank.FullHouse : + (suitStr.indexOf('5') !== -1) ? PokerRank.Flush : + (numStr.indexOf('11111') !== -1) ? PokerRank.Straight : + (numStr.indexOf('3') !== -1) ? PokerRank.ThreeOfKind : + (numStr.match(/2.*2.+/)) ? PokerRank.TwoPairs : + (numStr.indexOf('2') !== -1) ? PokerRank.OnePair : + PokerRank.HighCard; +} +/** + * Returns the rectangles sequence of specified figure. + * The figure is ASCII multiline string comprised of minus signs -, plus signs +, vertical bars | and whitespaces. + * The task is to break the figure in the rectangles it is made of. + * + * NOTE: The order of rectanles does not matter. + * + * @param {string} figure + * @return {Iterable.} decomposition to basic parts + * + * @example + * + * '+------------+\n'+ + * '| |\n'+ + * '| |\n'+ '+------------+\n'+ + * '| |\n'+ '| |\n'+ '+------+\n'+ '+-----+\n'+ + * '+------+-----+\n'+ => '| |\n'+ , '| |\n'+ , '| |\n'+ + * '| | |\n'+ '| |\n'+ '| |\n'+ '| |\n'+ + * '| | |\n' '+------------+\n' '+------+\n' '+-----+\n' + * '+------+-----+\n' + * + * + * + * ' +-----+ \n'+ + * ' | | \n'+ '+-------------+\n'+ + * '+--+-----+----+\n'+ '+-----+\n'+ '| |\n'+ + * '| |\n'+ => '| |\n'+ , '| |\n'+ + * '| |\n'+ '+-----+\n' '+-------------+\n' + * '+-------------+\n' + */ +function* getFigureRectangles(figure) { + const Arrtmp = figure.split('\n'); + const pluses = []; + const horizontalLines = []; + const rectangles = []; + + for (let i = 0; i < Arrtmp.length; i++) + for (let j = 0; j < Arrtmp[0].length; j++) + if (Arrtmp[i][j] === '+') { + pluses.push({x: j, y: i}); } - ].forEach(data => { - assert.equal( - tasks.parseBankAccount(data.text), - data.result, - `${data.text} has not parsed correctly:` - ); - }); - }); + for (let i = 0; i < pluses.length; i++) + for (let j = i + 1; j < pluses.length; j++) + if (pluses[i].y === pluses[j].y) { + if (checkHorizontalLine(Arrtmp, pluses[i], pluses[j])) + horizontalLines.push([pluses[i], pluses[j]]); + } - it.optional('wrapText should return the sequence of lines from the specified string', () => { - const text = 'The String global object is a constructor for strings, or a sequence of characters.'; - [ - { - cols: 26, - expected: [ - 'The String global object', - 'is a constructor for', - 'strings, or a sequence of', - 'characters.' - ] - },{ - cols: 12, - expected: [ - 'The String', - 'global', - 'object is a', - 'constructor', - 'for strings,', - 'or a', - 'sequence of', - 'characters.' - ] - },{ - cols: Number.MAX_SAFE_INTEGER, - expected: [ text ] + for (let i = 0; i < horizontalLines.length; i++) + for (let j = i + 1; j < horizontalLines.length; j++) + if (checkRectangle(Arrtmp, horizontalLines[i], horizontalLines[j])) { + rectangles.push([horizontalLines[i], horizontalLines[j]]); } - ].forEach(data => { - assert.deepEqual( - Array.from(tasks.wrapText(text, data.cols)), - data.expected, - `'${text}' has not wrapped correctly for ${data.cols} columns:` - ); - }); - }); + for (let i = 0; i < rectangles.length; i++) { + let rectangle = drawRectangle(rectangles[i]); - it.optional('getPokerHandRank should return the rank of the specified poker hand', () => { - var rankNames = []; - var PokerRank = tasks.PokerRank; - rankNames[PokerRank.StraightFlush] = 'StraightFlush'; - rankNames[PokerRank.FourOfKind] = 'FourOfKind'; - rankNames[PokerRank.FullHouse] = 'FullHouse'; - rankNames[PokerRank.Flush] = 'Flush'; - rankNames[PokerRank.Straight] = 'Straight'; - rankNames[PokerRank.ThreeOfKind] = 'ThreeOfKind'; - rankNames[PokerRank.TwoPairs] = 'TwoPairs'; - rankNames[PokerRank.OnePair] = 'OnePair'; - rankNames[PokerRank.HighCard] = 'HighCard'; + yield rectangle; + } +} - [ - { - hand: [ '4♥','5♥','6♥','7♥','8♥' ], - expected: PokerRank.StraightFlush - },{ - hand: [ 'A♣','K♣','Q♣','J♣','10♣' ], - expected: PokerRank.StraightFlush - },{ - hand: [ '10♦','9♦','6♦','7♦','8♦' ], - expected: PokerRank.StraightFlush - },{ - hand: [ 'A♠','4♠','3♠','5♠','2♠' ], - expected: PokerRank.StraightFlush - },{ - hand: [ '4♣','4♦','4♥','4♠','10♥' ], - expected: PokerRank.FourOfKind - },{ - hand: [ '2♣','A♦','A♣','A♠','A♥' ], - expected: PokerRank.FourOfKind - },{ - hand: [ '10♣','10♦','6♦','10♠','10♥' ], - expected: PokerRank.FourOfKind - },{ - hand: [ '4♣','4♦','5♦','5♠','5♥' ], - expected: PokerRank.FullHouse - },{ - hand: [ 'A♣','2♦','A♦','2♠','2♥' ], - expected: PokerRank.FullHouse - },{ - hand: [ '4♣','4♦','5♦','5♠','5♥' ], - expected: PokerRank.FullHouse - },{ - hand: [ '4♣','5♣','6♣','7♣','Q♣' ], - expected: PokerRank.Flush - },{ - hand: [ 'A♦','2♦','3♦','4♦','K♦' ], - expected: PokerRank.Flush - },{ - hand: [ 'A♠','Q♠','J♠','10♠','9♠' ], - expected: PokerRank.Flush - },{ - hand: [ '2♥','4♥','5♥','7♥','A♥' ], - expected: PokerRank.Flush - },{ - hand: [ '2♠','3♥','4♥','5♥','6♥' ], - expected: PokerRank.Straight - },{ - hand: [ 'A♠','K♦','Q♦','J♦','10♦' ], - expected: PokerRank.Straight - },{ - hand: [ '10♥','8♥','9♠','7♥','6♦' ], - expected: PokerRank.Straight - },{ - hand: [ '2♥','4♦','5♥','A♦','3♠' ], - expected: PokerRank.Straight - },{ - hand: [ '2♥','2♠','2♦','7♥','A♥' ], - expected: PokerRank.ThreeOfKind - },{ - hand: [ '2♥','4♥','A♥','A♦','A♠' ], - expected: PokerRank.ThreeOfKind - },{ - hand: [ '10♥','9♥','10♦','J♥','10♠' ], - expected: PokerRank.ThreeOfKind - },{ - hand: [ '2♥','4♦','4♥','A♦','A♠' ], - expected: PokerRank.TwoPairs - },{ - hand: [ '3♥','4♥','A♥','3♦','A♠' ], - expected: PokerRank.TwoPairs - },{ - hand: [ '5♥','6♥','A♥','6♦','5♠' ], - expected: PokerRank.TwoPairs - },{ - hand: [ '2♥','4♦','5♥','A♦','A♠' ], - expected: PokerRank.OnePair - },{ - hand: [ '3♥','4♥','10♥','3♦','A♠' ], - expected: PokerRank.OnePair - },{ - hand: [ '5♥','6♥','7♥','8♦','5♠' ], - expected: PokerRank.OnePair - },{ - hand: [ '3♥','4♥','5♥','7♦','8♥' ], - expected: PokerRank.HighCard - },{ - hand: [ 'A♥','K♥','Q♥','J♦','5♠' ], - expected: PokerRank.HighCard - },{ - hand: [ 'A♥','K♥','Q♥','2♦','3♠' ], - expected: PokerRank.HighCard - } - ].forEach(data => { - var actual = tasks.getPokerHandRank(data.hand); - assert( - actual >= PokerRank.HighCard, - 'Invalid return value. The return value should be >= PokerRank.HighCard' - ); - assert( - actual <= PokerRank.StraightFlush, - 'Invalid return value. The return value should be <= PokerRank.StraightFlush' - ); - assert( - actual == data.expected, - `'${data.hand}' is ranked as ${rankNames[data.expected]}, but actually ${rankNames[actual]} ` - ); - }); - }); +function checkHorizontalLine(Arrtmp, s, f) { + for (let i = s.x; i <= f.y; i++) + if (Arrtmp[s.y][i] !== '-' && Arrtmp[s.y][i] !== '+') + return false; + return true; +} - it.optional('getFigureRectangles should return the sequence of rectagles parts of the specified figure', () => { - [ - { - figure: '+------------+\n'+ - '| |\n'+ - '| |\n'+ - '| |\n'+ - '+------+-----+\n'+ - '| | |\n'+ - '| | |\n'+ - '+------+-----+\n', - expected: [ - '+------------+\n'+ - '| |\n'+ - '| |\n'+ - '| |\n'+ - '+------------+\n', +function checkRectangle(Arrtmp, top, bottom) { + if (top[0].x !== bottom[0].x) + return false; - '+------+\n'+ - '| |\n'+ - '| |\n'+ - '+------+\n', + if (top[1].x !== bottom[1].x) + return false; - '+-----+\n'+ - '| |\n'+ - '| |\n'+ - '+-----+\n' - ] - },{ - figure: ' +-----+ \n'+ - ' | | \n'+ - '+--+-----+----+\n'+ - '| |\n'+ - '| |\n'+ - '+-------------+\n', - expected: [ - '+-----+\n'+ - '| |\n'+ - '+-----+\n', + const leftX = top[0].x, + rightX = top[1].x, + topY = top[0].y, + bottomY = bottom[0].y; - '+-------------+\n'+ - '| |\n'+ - '| |\n'+ - '+-------------+\n' - ] - },{ - figure: ' +--+ \n'+ - ' | | \n'+ - '+--+--+--+\n'+ - '| | |\n'+ - '+--+--+--+\n'+ - ' | | \n'+ - ' +--+ \n', - expected: [ - '+--+\n'+ - '| |\n'+ - '+--+\n', + for (let j = leftX + 1; j < rightX; j++) + if (Arrtmp[topY][j] === '+' && Arrtmp[bottomY][j] === '+') { + let hasWhiteSpace = false; - '+--+\n'+ - '| |\n'+ - '+--+\n', + for (let i = topY + 1; i < bottomY; i++) + if (Arrtmp[i][j] === ' ') + hasWhiteSpace = true; - '+--+\n'+ - '| |\n'+ - '+--+\n', - '+-----+\n'+ - '| |\n'+ - '+-----+\n' - ] - },{ - figure: '++++\n'+ - '++++\n', - expected: [ - '++\n'+ - '++\n', + if (!hasWhiteSpace) + return false; + } - '++\n'+ - '++\n', + for (let i = topY + 1; i < bottomY; i++) { + if (Arrtmp[i][leftX] !== '|' && Arrtmp[i][leftX] !== '+') + return false; + + if (Arrtmp[i][rightX] !== '|' && Arrtmp[i][rightX] !== '+') + return false; + + for (let j = leftX + 1; j < rightX; j++) + if (Arrtmp[i][j] !== ' ') + return false; + } + + return true; +} + +function drawRectangle(item) { + let width = item[0][1].x - item[0][0].x + 1, + height = item[1][0].y - item[0][0].y + 1, + result = '', + topLine = '+' + ('-').repeat(width - 2) + '+' + '\n'; + + result += topLine; + result += ( '|' + (' ').repeat(width - 2) + '|' + '\n' ).repeat(height - 2); + result += topLine; + + return result; +} - '++\n'+ - '++\n', - ] - } - ].forEach(data => { - var actual = Array.from(tasks.getFigureRectangles(data.figure)).sort(); - var expected = data.expected.sort(); - assert.deepEqual( - actual, - expected, - `Figure \n${data.figure} has the following parts:\n${expected.join(',\n')} but actually :\n${actual.join(',\n')}` - ); - }); - }); -}); +module.exports = { + parseBankAccount : parseBankAccount, + wrapText: wrapText, + PokerRank: PokerRank, + getPokerHandRank: getPokerHandRank, + getFigureRectangles: getFigureRectangles +}; From 537df6e46070d4df70f6395f811d503d8eb8ee13 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:54:36 +0300 Subject: [PATCH 14/40] Task12 --- test/12-katas-3-tests.js | 289 +++++++++++++++++++++++---------------- 1 file changed, 169 insertions(+), 120 deletions(-) diff --git a/test/12-katas-3-tests.js b/test/12-katas-3-tests.js index 732e6e207..ae4cd33af 100644 --- a/test/12-katas-3-tests.js +++ b/test/12-katas-3-tests.js @@ -1,134 +1,183 @@ 'use strict'; -var assert = require('assert'); -var tasks = require('../task/12-katas-3-tasks'); -it.optional = require('../extensions/it-optional'); +/** + * Returns true if word occurrs in the specified word snaking puzzle. + * Each words can be constructed using "snake" path inside a grid with top, left, right and bottom directions. + * Each char can be used only once ("snake" should not cross itself). + * + * @param {array} puzzle + * @param {array} searchStr + * @return {bool} + * + * @example + * var puzzle = [ + * 'ANGULAR', + * 'REDNCAE', + * 'RFIDTCL', + * 'AGNEGSA', + * 'YTIRTSP', + * ]; + * 'ANGULAR' => true (first row) + * 'REACT' => true (starting from the top-right R adn follow the ↓ ← ← ↓ ) + * 'UNDEFINED' => true + * 'RED' => true + * 'STRING' => true + * 'CLASS' => true + * 'ARRAY' => true (first column) + * 'FUNCTION' => false + * 'NULL' => false + */ +function findStringInSnakingPuzzle(puzzle, searchStr) { + function dfs(current, step) { + let save = data[current.y][current.x]; + data[current.y][current.x] = ""; + if (step == search.length) + return true; + let result = false, + steps = [[1, 0], [-1, 0], [0, -1], [0, 1]]; + for (let i = 0; i < 4; i++) { + let newX = current.x + steps[i][0], + newY = current.y + steps[i][1]; + if (data[newY][newX] == search[step]) { + result = result || dfs({x: newX, y: newY}, step + 1); + } + } + data[current.y][current.x] = save; + return result; + } + let data = Array.from(puzzle); + let Arrtmp = Array.from({length: data[0].length + 2}, () => ''); + data = data.map(item => [''].concat(item.split(''), [''])); + data = [Arrtmp].concat(data, [Arrtmp]); + let search = searchStr.split(''), + n = data[0].length - 1, + m = data.length - 1; + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) + if (data[i][j] == search[0]) { + if (dfs({y: i, x: j}, 1)) + return true; + } + } -describe('12-katas-3-tasks', function() { + return false; +} - it.optional('findStringInSnakingPuzzle shoud return true if word occurrs in the specified puzzle', () => { - var puzzle = [ - 'ANGULAR', - 'REDNCAE', - 'RFIDTCL', - 'AGNEGSA', - 'YTIRTSP', - ]; - var puzzleToString = (p) => p.map(x=>' '+x).join('\n'); - [ - 'ANGULAR', 'REACT', 'UNDEFINED', 'RED', 'STRING', 'CLASS', 'ARRAY' - ].forEach(word => { - assert( - tasks.findStringInSnakingPuzzle(puzzle, word), - `Word "${word}" occurrs in puzzle\n${puzzleToString(puzzle)}` - ); - }); - [ - 'FUNCTION', 'NULL', 'EMBER', 'HOISTING', 'GIT', 'ARENA' - ].forEach(word => { - assert( - !tasks.findStringInSnakingPuzzle(puzzle, word), - `Word "${word}" does not occurr in puzzle\n${puzzleToString(puzzle)}` - ); - }); - }); +/** + * Returns all permutations of the specified string. + * Assume all chars in the specified string are different. + * The order of permutations does not matter. + * + * @param {string} chars + * @return {Iterable.} all posible strings constructed with the chars from the specfied string + * + * @example + * 'ab' => 'ab','ba' + * 'abc' => 'abc','acb','bac','bca','cab','cba' + */ +function* getPermutations(chars) { + function *permute(a, len){ + if (len < 2) + yield a.join(''); + else{ + for (let i = 0; i < len; i++){ + yield *permute(a,len - 1); + const toSwap = len % 2 ? 0 : i; + let tmp = a[len - 1]; + a[len - 1] = a[toSwap]; + a[toSwap] = tmp; + } + } + } + yield *permute(chars.split(''), chars.length); +} - it.optional('getPermutations should return all possible string permutations', () => { - [ - { - chars: 'a', - expected: [ 'a' ] - },{ - chars: 'ab', - expected: [ 'ab', 'ba' ] - },{ - chars: 'abc', - expected: [ 'abc', 'acb', 'bac', 'bca', 'cab', 'cba' ] - },{ - chars: 'abcd', - expected: [ - 'abcd', 'abdc', 'acbd', 'acdb', 'adbc', 'adcb', - 'bacd', 'badc', 'bcad', 'bcda', 'bdac', 'bdca', - 'cabd', 'cadb', 'cbad', 'cbda', 'cdab', 'cdba', - 'dabc', 'dacb', 'dbac', 'dbca', 'dcab', 'dcba' - ] - } - ].forEach(data => { - assert.deepEqual( - Array.from(tasks.getPermutations(data.chars)).sort(), - data.expected, - `Incorrect permutations of "${data.chars}"` - ); - }); - assert.equal( - Array.from(tasks.getPermutations('12345')).length, - 120, - 'Number of 5 chars permutations should be 120.' - ); - }); +/** + * Returns the most profit from stock quotes. + * Stock quotes are stores in an array in order of date. + * The stock profit is the difference in prices in buying and selling stock. + * Each day, you can either buy one unit of stock, sell any number of stock units you have already bought, or do nothing. + * Therefore, the most profit is the maximum difference of all pairs in a sequence of stock prices. + * + * @param {array} quotes + * @return {number} max profit + * + * @example + * [ 1, 2, 3, 4, 5, 6] => 15 (buy at 1,2,3,4,5 and then sell all at 6) + * [ 6, 5, 4, 3, 2, 1] => 0 (nothing to buy) + * [ 1, 6, 5, 10, 8, 7 ] => 18 (buy at 1,6,5 and sell all at 10) + */ +function getMostProfitFromStockQuotes(quotes) { + let tmpMax = quotes + .reduceRight((prev, curr)=>{ + if(prev.length == 0) + return [curr]; + prev.push(Math.max(prev[prev.length - 1],curr)); + return prev; + }, []) + .reverse(); + tmpMax.push(0); + return quotes.reduce((prev, curr, index)=>{ + return prev + Math.max(0, tmpMax[index + 1] - curr); + }, 0); +} - it.optional('getMostProfitFromStockQuotes should return the max profit from stock trading', () => { - [ - { - quotes: [ 1, 2, 3, 4, 5, 6 ], - expected: 15 - },{ - quotes: [ 6, 5, 4, 3, 2, 1 ], - expected: 0 - },{ - quotes: [ 1, 6, 5, 10, 8, 7 ], - expected: 18 - },{ - quotes: [ 31, 312, 3, 35, 33, 3, 44, 123, 126, 2, 4, 1 ], - expected: 798 - },{ - quotes: [ 1, 20, 1, 30, 1, 40, 1, 50, 1, 40, 1, 30, 1, 20, 1 ], - expected: 343 - } - ].forEach(data => { - var actual = tasks.getMostProfitFromStockQuotes(data.quotes); - assert.equal( - actual, - data.expected, - `Most profit for [${data.quotes}] quotes is ${data.expected} but actually ${actual}` - ); - }); - }); +/** + * Class representing the url shorting helper. + * Feel free to implement any algorithm, but do not store link in the key\value stores. + * The short link can be at least 1.5 times shorter than the original url. + * + * @class + * + * @example + * + * var urlShortener = new UrlShortener(); + * var shortLink = urlShortener.encode('https://en.wikipedia.org/wiki/URL_shortening'); + * var original = urlShortener.decode(shortLink); // => 'https://en.wikipedia.org/wiki/URL_shortening' + * + */ +function UrlShortener() { + this.urlAllowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ + "abcdefghijklmnopqrstuvwxyz"+ + "0123456789-_.~!*'();:@&=+$,/?#[]"; +} +UrlShortener.prototype = { - it.optional('urlShortener should return encoded string shorter than original url', () => { - [ - 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul', - 'https://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters', - 'https://en.wikipedia.org/wiki/Binary-to-text_encoding#Encoding_plain_text' - ].forEach(data => { - var urlShortener = new tasks.UrlShortener(); - var actual = urlShortener.encode(data); - assert( - data.length / actual.length > 1.5, - `urlShortener.encode for "${data}" returns "${actual}" that is only ${data.length/actual.length} times less than original url` - ); - }); - }); + encode: function(url) { + let res = ""; + for (let i = 0; i < url.length; i += 2) { + let tmp1 = url.charCodeAt(i); + let tmp2 = url.charCodeAt(i + 1); + let code = (tmp1 << 8) |tmp2; + res += String.fromCharCode(code); + } + return res; + }, + decode: function(code) { + let res = ""; + for (let i = 0; i < code.length; i++) { + let char = parseInt(code.charCodeAt(i), 10); + let tmp1 = char & 255; + let tmp2 = (char >> 8) & 255; + if (tmp1 === 0) { + res += String.fromCharCode(tmp2) + } else { + res += String.fromCharCode(tmp2) + String.fromCharCode(tmp1); + } + } + return res; + } +} - it.optional('urlShortener should decode shorten link to to the original url', () => { - [ - 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul', - 'https://www.example.com/catalog.html?search=mobile+phones&price=100-200&year=2016#top_links', - ].forEach(data => { - var urlShortener = new tasks.UrlShortener(); - var encoded = urlShortener.encode(data); - var actual = urlShortener.decode(encoded); - assert.equal( - data, - actual, - `urlShortener.encode for "${data}" returns "${encoded}" but decode returns "${actual}"` - ); - }); - }); -}); +module.exports = { + findStringInSnakingPuzzle: findStringInSnakingPuzzle, + getPermutations: getPermutations, + getMostProfitFromStockQuotes: getMostProfitFromStockQuotes, + UrlShortener: UrlShortener +}; From 26a1e40ff82ba99f5f8e8ce91ab2497313dcc5a6 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:56:45 +0300 Subject: [PATCH 15/40] Task6 --- test/06-conditions-n-loops-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/06-conditions-n-loops-tests.js b/test/06-conditions-n-loops-tests.js index 93ffa3eee..eec404ef9 100644 --- a/test/06-conditions-n-loops-tests.js +++ b/test/06-conditions-n-loops-tests.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' ; var assert = require('assert'); var tasks = require('../task/06-conditions-n-loops-tasks'); From 941d569e753b4af7317b24780ad9e5eb5f6f6a81 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:57:39 +0300 Subject: [PATCH 16/40] Task5 --- test/05-regex-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/05-regex-tests.js b/test/05-regex-tests.js index 7e789f8a1..2012821fc 100644 --- a/test/05-regex-tests.js +++ b/test/05-regex-tests.js @@ -1,6 +1,6 @@ 'use strict'; -var assert = require('assert'); +var assert = require('assert') ; var tasks = require('../task/05-regex-tasks'); it.optional = require('../extensions/it-optional'); From 494ef06dd3983cf5d666f4fc257b017ff72531e1 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:58:49 +0300 Subject: [PATCH 17/40] Task3 --- test/03-date-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/03-date-tests.js b/test/03-date-tests.js index 8423865b0..8b4696763 100644 --- a/test/03-date-tests.js +++ b/test/03-date-tests.js @@ -1,6 +1,6 @@ 'use strict'; -var assert = require('assert'); +var assert = require('assert') ; var tasks = require('../task/03-date-tasks'); it.optional = require('../extensions/it-optional'); From abfc6fb91bc23145002bdbcd79259595a998f514 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 06:59:48 +0300 Subject: [PATCH 18/40] Task1 --- test/01-strings-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/01-strings-tests.js b/test/01-strings-tests.js index e044644e5..6716f76ac 100644 --- a/test/01-strings-tests.js +++ b/test/01-strings-tests.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' ; var assert = require('assert'); var tasks = require('../task/01-strings-tasks'); From 1816927d2f5bf7efa50b5fb1bfd87a61ef51c047 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 07:05:31 +0300 Subject: [PATCH 19/40] Task2 --- test/02-numbers-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/02-numbers-tests.js b/test/02-numbers-tests.js index 97458c7a8..dfbb68462 100644 --- a/test/02-numbers-tests.js +++ b/test/02-numbers-tests.js @@ -205,7 +205,7 @@ function roundToPowerOfTen(num, pow) { */ function isPrime(n) { if (n <= 1) { - return false; + return false ; } else if (n <= 3) { return true; } else if (!(n % 2) || !(n % 3)) { From 4380f24c53027cce31c46d815907ed3e078b0736 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 07:09:08 +0300 Subject: [PATCH 20/40] Task2 --- task/02-numbers-tasks.js | 45 +++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/task/02-numbers-tasks.js b/task/02-numbers-tasks.js index c9ed20208..dc91648f2 100644 --- a/task/02-numbers-tasks.js +++ b/task/02-numbers-tasks.js @@ -22,7 +22,7 @@ * 5, 5 => 25 */ function getRectangleArea(width, height) { - throw new Error('Not implemented'); + return width * height; } @@ -38,7 +38,7 @@ function getRectangleArea(width, height) { * 0 => 0 */ function getCicleCircumference(radius) { - throw new Error('Not implemented'); + return 2 * Math.PI * radius; } /** @@ -54,7 +54,7 @@ function getCicleCircumference(radius) { * -3, 3 => 0 */ function getAverage(value1, value2) { - throw new Error('Not implemented'); + return value1 / 2 + value2 / 2; } /** @@ -73,7 +73,7 @@ function getAverage(value1, value2) { * (-5,0) (10,-10) => 18.027756377319946 */ function getDistanceBetweenPoints(x1, y1, x2, y2) { - throw new Error('Not implemented'); + return Math.hypot((x2 - x1), (y2 - y1)); } /** @@ -89,7 +89,7 @@ function getDistanceBetweenPoints(x1, y1, x2, y2) { * 5*x = 0 => 0 */ function getLinearEquationRoot(a, b) { - throw new Error('Not implemented'); + return -b / a; } @@ -111,7 +111,9 @@ function getLinearEquationRoot(a, b) { * (0,1) (1,2) => 0 */ function getAngleBetweenVectors(x1, y1, x2, y2) { - throw new Error('Not implemented'); + return Math.acos( + (x1 * x2 + y1 * y2) / (Math.sqrt(x1 * x1 + y1 * y1) * Math.sqrt(x2 * x2 + y2 * y2)) + ); } /** @@ -127,7 +129,7 @@ function getAngleBetweenVectors(x1, y1, x2, y2) { * 0 => 0 */ function getLastDigit(value) { - throw new Error('Not implemented'); + return value % 10; } @@ -143,7 +145,7 @@ function getLastDigit(value) { * '-525.5' => -525.5 */ function parseNumberFromString(value) { - throw new Error('Not implemented'); + return Number.parseFloat(value); } /** @@ -160,7 +162,7 @@ function parseNumberFromString(value) { * 1,2,3 => 3.741657386773941 */ function getParallelipidedDiagonal(a,b,c) { - throw new Error('Not implemented'); + return Math.sqrt(a * a + b * b + c * c); } /** @@ -169,7 +171,7 @@ function getParallelipidedDiagonal(a,b,c) { * @param {number} num * @param {number} pow * @return {number} - * + * * @example: * 1234, 0 => 1234 * 1234, 1 => 1230 @@ -181,7 +183,7 @@ function getParallelipidedDiagonal(a,b,c) { * 1678, 3 => 2000 */ function roundToPowerOfTen(num, pow) { - throw new Error('Not implemented'); + return Math.round(num / Math.pow(10, pow)) * Math.pow(10, pow); } /** @@ -190,7 +192,7 @@ function roundToPowerOfTen(num, pow) { * * @param {number} n * @return {bool} - * + * * @example: * 4 => false * 5 => true @@ -202,7 +204,22 @@ function roundToPowerOfTen(num, pow) { * 17 => true */ function isPrime(n) { - throw new Error('Not implemented'); + if (n <= 1) { + return false; + } else if (n <= 3) { + return true; + } else if (!(n % 2) || !(n % 3)) { + return false; + } + + let i = 5; + while (i * i <= n) { + if (!(n % i) || !(n % (i + 2))) { + return false; + } + i += 6; + } + return true; } /** @@ -221,7 +238,7 @@ function isPrime(n) { * toNumber(new Number(42), 0) => 42 */ function toNumber(value, def) { - throw new Error('Not implemented'); + return +value ? +value : def; } module.exports = { From 152e4fbd34d2051b059db2534a376316209057a3 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:21:48 +0300 Subject: [PATCH 21/40] Task4 --- test/04-arrays-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/04-arrays-tests.js b/test/04-arrays-tests.js index 38a7042e1..4daac09f7 100644 --- a/test/04-arrays-tests.js +++ b/test/04-arrays-tests.js @@ -582,7 +582,7 @@ function group(array, keySelector, valueSelector) { else _map.set(keySelector(x), [valueSelector(x)]); }); - return _map; + return _map ; } From 92bc34a07a92580d5347f6ae4718349d798f4683 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:25:39 +0300 Subject: [PATCH 22/40] Task4 --- task/04-arrays-tasks.js | 424 ++++++++++++++++++++++++---------------- 1 file changed, 259 insertions(+), 165 deletions(-) diff --git a/task/04-arrays-tasks.js b/task/04-arrays-tasks.js index ff3a4c019..75a9bbb7e 100644 --- a/task/04-arrays-tasks.js +++ b/task/04-arrays-tasks.js @@ -9,257 +9,282 @@ * * *********************************************************************************************/ - + /** * Returns an index of the specified element in array or -1 if element is not found - * + * * @param {array} arr * @param {any} value * @return {number} - * + * * @example - * ['Ace', 10, true], 10 => 1 - * ['Array', 'Number', 'string'], 'Date' => -1 + * ['Ace', 10, true], 10 => 1 + * ['Array', 'Number', 'string'], 'Date' => -1 * [0, 1, 2, 3, 4, 5], 5 => 5 */ function findElement(arr, value) { - throw new Error('Not implemented'); + return arr.indexOf(value); } /** * Generates an array of odd numbers of the specified length - * + * * @param {number} len * @return {array} - * + * * @example - * 1 => [ 1 ] - * 2 => [ 1, 3 ] + * 1 => [ 1 ] + * 2 => [ 1, 3 ] * 5 => [ 1, 3, 5, 7, 9 ] */ function generateOdds(len) { - throw new Error('Not implemented'); + let arr = new Array(len); + arr.fill(1); + return arr.map((elem, index) => + { + return elem = index*2 + 1; + }); } /** * Returns the doubled array - elements of the specified array are repeated twice using original order - * + * * @param {array} arr * @return {array} - * + * * @example - * ['Ace', 10, true] => ['Ace', 10, true, 'Ace', 10, true] + * ['Ace', 10, true] => ['Ace', 10, true, 'Ace', 10, true] * [0, 1, 2, 3, 4, 5] => [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5] - * [] => [] + * [] => [] */ function doubleArray(arr) { - throw new Error('Not implemented'); + arr.map(elem => + { + return arr.push(elem); + }); + return arr; } /** * Returns an array of positive numbers from the specified array in original order - * + * * @param {array} arr * @return {array} - * + * * @example * [ 0, 1, 2, 3, 4, 5 ] => [ 1, 2, 3, 4, 5 ] * [-1, 2, -5, -4, 0] => [ 2 ] - * [] => [] + * [] => [] */ function getArrayOfPositives(arr) { - throw new Error('Not implemented'); + return arr.filter((item, i, arr) => item > 0); } /** * Returns the array with strings only in the specified array (in original order) - * + * * @param {array} arr * @return {array} - * + * * @example * [ 0, 1, 'cat', 3, true, 'dog' ] => [ 'cat', 'dog' ] * [ 1, 2, 3, 4, 5 ] => [] * [ 'cat, 'dog', 'raccon' ] => [ 'cat', 'dog', 'racoon' ] */ function getArrayOfStrings(arr) { - throw new Error('Not implemented'); + return arr.filter(elem => typeof elem === 'string' || elem instanceof String); } /** * Removes falsy values from the specified array * Falsy values: false, null, 0, "", undefined, and NaN. * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean#Description) - * + * * @param {array} arr * @return {array} - * + * * @example * [ 0, false, 'cat', NaN, true, '' ] => [ 'cat', true ] * [ 1, 2, 3, 4, 5, 'false' ] => [ 1, 2, 3, 4, 5, 'false' ] * [ false, 0, NaN, '', undefined ] => [ ] */ function removeFalsyValues(arr) { - throw new Error('Not implemented'); + return arr.filter((item, i, arr) => !!item); } /** * Returns the array of useprcase strings from the specified array - * + * * @param {array} arr * @return {array} - * + * * @example * [ 'permanent-internship', 'glutinous-shriek', 'multiplicative-elevation' ] => [ 'PERMANENT-INTERNSHIP', 'GLUTINOUS-SHRIEK', 'MULTIPLICATIVE-ELEVATION' ] * [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ] => [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ] */ function getUpperCaseStrings(arr) { - throw new Error('Not implemented'); + return arr.map(elem => + { + return elem.toUpperCase(); + }); } /** * Returns the array of string lengths from the specified string array. - * + * * @param {array} arr * @return {array} - * + * * @example * [ '', 'a', 'bc', 'def', 'ghij' ] => [ 0, 1, 2, 3, 4 ] * [ 'angular', 'react', 'ember' ] => [ 7, 5, 5 ] */ function getStringsLength(arr) { - throw new Error('Not implemented'); + return arr.map((item, i, arr) => item.length); } /** * Inserts the item into specified array at specified index - * + * * @param {array} arr * @param {any} item - * @param {number} index - * + * @param {number} index + * * @example * [ 1, 3, 4, 5 ], 2, 1 => [ 1, 2, 3, 4, 5 ] * [ 1, 'b', 'c'], 0, 'x' => [ 'x', 1, 'b', 'c' ] */ function insertItem(arr, item, index) { - throw new Error('Not implemented'); + return arr.splice(index, 0, item); } /** * Returns the n first items of the specified array - * + * * @param {array} arr - * @param {number} n - * + * @param {number} n + * * @example * [ 1, 3, 4, 5 ], 2 => [ 1, 2 ] * [ 'a', 'b', 'c', 'd'], 3 => [ 'a', 'b', 'c' ] */ function getHead(arr, n) { - throw new Error('Not implemented'); + return arr.slice(0, n); } /** * Returns the n last items of the specified array - * + * * @param {array} arr - * @param {number} n - * + * @param {number} n + * * @example * [ 1, 3, 4, 5 ], 2 => [ 4, 5 ] * [ 'a', 'b', 'c', 'd'], 3 => [ 'b', 'c', 'd' ] */ function getTail(arr, n) { - throw new Error('Not implemented'); + return arr.splice( -n); } /** * Returns CSV represebtation of two-dimentional numeric array. * https://en.wikipedia.org/wiki/Comma-separated_values - * + * * @param {array} arr * @return {string} - * + * * @example * [ * [ 0, 1, 2, 3, 4 ], * [ 10,11,12,13,14 ], * [ 20,21,22,23,24 ], * [ 30,31,32,33,34 ] - * ] - * => + * ] + * => * '0,1,2,3,4\n' * +'10,11,12,13,14\n' * +'20,21,22,23,24\n' * +'30,31,32,33,34' */ function toCsvText(arr) { - throw new Error('Not implemented'); + let arrStr = arr.map((elem, index) => + { + if (index != arr.length-1) + return elem.join(',') + '\n'; + else + return elem.join(','); + }).join(''); + return arrStr; } /** * Transforms the numeric array into the according array of squares: * f(x) = x * x - * + * * @param {array} arr * @return {array} - * + * * @example * [ 0, 1, 2, 3, 4, 5 ] => [ 0, 1, 4, 9, 16, 25 ] * [ 10, 100, -1 ] => [ 100, 10000, 1 ] */ function toArrayOfSquares(arr) { - throw new Error('Not implemented'); + return arr.map((item, i, arr) => item ** 2); } /** * Transforms the numeric array to the according moving sum array: - * f[n] = x[0] + x[1] + x[2] +...+ x[n] + * f[n] = x[0] + x[1] + x[2] +...+ x[n] * or f[n] = f[n-1] + x[n] - * + * * @param {array} arr * @return {array} - * + * * Example : * [ 1, 1, 1, 1, 1 ] => [ 1, 2, 3, 4, 5 ] * [ 10, -10, 10, -10, 10 ] => [ 10, 0, 10, 0, 10 ] - * [ 0, 0, 0, 0, 0] => [ 0, 0, 0, 0, 0] + * [ 0, 0, 0, 0, 0] => [ 0, 0, 0, 0, 0] * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55 ] */ function getMovingSum(arr) { - throw new Error('Not implemented'); + let sum = 0; + return arr.map((elem) => + { + elem += sum; + sum = elem; + return elem; + }); } /** * Returns every second item from the specified array: - * + * * @param {array} arr * @return {array} - * + * * Example : * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 2, 4, 6, 8, 10 ] * [ 'a', 'b', 'c' , null ] => [ "b", null ] * [ "a" ] => [] */ function getSecondItems(arr) { - throw new Error('Not implemented'); + return arr.filter((elem, index) => index % 2); } /** * Propagates every item in sequence its position times - * Returns an array that consists of: one first item, two second items, tree third items etc. - * - * @param {array} arr + * Returns an array that consists of: one first item, two second items, tree third items etc. + * + * @param {array} arr * @return {array} - * + * * @example : * [] => [] * [ 1 ] => [ 1 ] @@ -268,13 +293,15 @@ function getSecondItems(arr) { * [ 1,2,3,4,5 ] => [ 1, 2,2, 3,3,3, 4,4,4,4, 5,5,5,5,5 ] */ function propagateItemsByPositionIndex(arr) { - throw new Error('Not implemented'); + return arr.reduce((acc, elem, index) => acc.concat( + Array.from({length: index + 1}, () => elem)), [] + ); } -/** +/** * Returns the 3 largest numbers from the specified array - * + * * @param {array} arr * @return {array} * @@ -286,16 +313,16 @@ function propagateItemsByPositionIndex(arr) { * [ 10, 10, 10, 10 ] => [ 10, 10, 10 ] */ function get3TopItems(arr) { - throw new Error('Not implemented'); + return arr.sort((a, b) => b - a).slice(0, 3); } - - -/** + + +/** * Returns the number of positive numbers from specified array - * + * * @param {array} arr * @return {number} - * + * * @example * [ ] => 0 * [ -1, 0, 1 ] => 1 @@ -306,13 +333,13 @@ function get3TopItems(arr) { function getPositivesCount(arr) { throw new Error('Not implemented'); } - -/** + +/** * Sorts digit names - * + * * @param {array} arr * @return {array} - * + * * @example * [] => [] * [ 'nine','one' ] => [ 'one', 'nine' ] @@ -321,15 +348,17 @@ function getPositivesCount(arr) { * [ 'one','one','one','zero' ] => [ 'zero','one','one','one' ] */ function sortDigitNamesByNumericOrder(arr) { - throw new Error('Not implemented'); + const mapped1 = { 0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six', 7: 'seven', 8: 'eight', 9: 'nine' }; + const mapped2 = { 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9 }; + return arr.map(x => mapped2[x]).sort().map(x => mapped1[x]); } -/** +/** * Returns the sum of all items in the specified array of numbers - * + * * @param {array} arr * @return {number} - * + * * @example * [] => 0 * [ 1, 2, 3 ] => 6 @@ -337,15 +366,15 @@ function sortDigitNamesByNumericOrder(arr) { * [ 1, 10, 100, 1000 ] => 1111 */ function getItemsSum(arr) { - throw new Error('Not implemented'); + return arr.reduce((acc, elem) => acc + elem, 0); } - -/** + +/** * Returns the number of all falsy value in the specified array - * + * * @param {array} arr * @return {array} - * + * * @example * [] => 0 * [ 1, '', 3 ] => 1 @@ -353,49 +382,61 @@ function getItemsSum(arr) { * [ null, undefined, NaN, false, 0, '' ] => 6 */ function getFalsyValuesCount(arr) { - throw new Error('Not implemented'); + let sum = 0; + arr.map(elem => + { + if (Boolean(elem) == 0) + return sum++; + }); + return sum; } /** - * Returns a number of all occurences of the specified item in an array - * + * Returns a number of all occurences of the specified item in an array + * * @param {array} arr - * @param {any} item + * @param {any} item * @return {number} - * + * * @example * [ 0, 0, 1, 1, 1, 2 ], 1 => 3 * [ 1, 2, 3, 4, 5 ], 0 => 0 * [ 'a','b','c','c' ], 'c'=> 2 - * [ null, undefined, null ], null => 2 + * [ null, undefined, null ], null => 2 * [ true, 0, 1, 'true' ], true => 1 */ function findAllOccurences(arr, item) { - throw new Error('Not implemented'); + let num = 0; + arr.map(elem => + { + if (elem === item) + return num++; + }) + return num; } /** - * Concatenates all elements from specified array into single string with ',' delimeter - * - * @param {array} arr + * Concatenates all elements from specified array into single string with ',' delimeter + * + * @param {array} arr * @return {string} - * + * * @example * [0, false, 'cat', NaN, true, ''] => '0,false,cat,NaN,true,' * [1, 2, 3, 4, 5] => '1,2,3,4,5' * ['rock', 'paper', 'scissors'] => 'rock,paper,scissors' */ function toStringList(arr) { - throw new Error('Not implemented'); + return arr.join(); } /** * Sorts the specified array by country name first and city name (if countries are equal) in ascending order. - * + * * @param {array} arr * @return {array} - * + * * @example * [ * { country: 'Russia', city: 'Moscow' }, @@ -404,7 +445,7 @@ function toStringList(arr) { * { country: 'Russia', city: 'Saint Petersburg' }, * { country: 'Poland', city: 'Krakow' }, * { country: 'Belarus', city: 'Brest' } - * ] + * ] * => * [ * { country: 'Belarus', city: 'Brest' }, @@ -415,38 +456,61 @@ function toStringList(arr) { * { country: 'Russia', city: 'Saint Petersburg' } */ function sortCitiesArray(arr) { - throw new Error('Not implemented'); + function compareLocation(a, b) { + if (a.country < b.country) { + return -1; + } + if (a.country > b.country) { + return 1; + } + if (a.country === b.country) { + if (a.city < b.city) { + return -1; + } + if (a.city > b.city) { + return 1; + } + if (a.city === b.city) { + return 0; + } + } + } + return arr.sort(compareLocation); } /** * Creates an indentity matrix of the specified size - * + * * @param {number} n * @return {array} - * + * * @example * 1 => [[1]] - * + * * 2 => [[1,0], * [0,1]] - * + * * [[1,0,0,0,0], * [0,1,0,0,0], * 5 => [0,0,1,0,0], * [0,0,0,1,0], - * [0,0,0,0,1]] + * [0,0,0,0,1]] */ function getIdentityMatrix(n) { - throw new Error('Not implemented'); + return Array.from({length: n}, function (elem, index) { + let row = new Array(n).fill(0, 0, n); + row[index] = 1; + return row; + }); } /** * Creates an array of integers from the specified start to end (inclusive) - * + * * @param {number} start * @param {number} end * @return {array} - * + * * @example * 1, 5 => [ 1, 2, 3, 4, 5 ] * -2, 2 => [ -2, -1, 0, 1, 2 ] @@ -454,7 +518,7 @@ function getIdentityMatrix(n) { * 3, 3 => [ 3 ] */ function getIntervalArray(start, end) { - throw new Error('Not implemented'); + return Array.from({length: end - start + 1}, (elem, index) => index + start); } /** @@ -462,14 +526,21 @@ function getIntervalArray(start, end) { * * @param {array} arr * @return {array} - * + * * @example * [ 1, 2, 3, 3, 2, 1 ] => [ 1, 2, 3 ] * [ 'a', 'a', 'a', 'a' ] => [ 'a' ] * [ 1, 1, 2, 2, 3, 3, 4, 4] => [ 1, 2, 3, 4] */ function distinct(arr) { - throw new Error('Not implemented'); + let resArr = []; + arr.map(elem => + { + if (resArr.indexOf(elem) == -1) + resArr.push(elem); + return resArr; + }); + return resArr; } /** @@ -491,19 +562,27 @@ function distinct(arr) { * { country: 'Belarus', city: 'Grodno' }, * { country: 'Belarus', city: 'Minsk' }, * { country: 'Poland', city: 'Lodz' } - * ], - * item => item.country, + * ], + * item => item.country, * item => item.city * ) - * => + * => * Map { * "Belarus" => ["Brest", "Grodno", "Minsk"], - * "Russia" => ["Omsk", "Samara"], + * "Russia" => ["Omsk", "Samara"], * "Poland" => ["Lodz"] * } */ function group(array, keySelector, valueSelector) { - throw new Error('Not implemented'); + let _map = new Map(); + array.map((x, ind) => + { + if( _map.has(keySelector(x)) ) + _map.get(keySelector(x)).push(valueSelector(x)); + else + _map.set(keySelector(x), [valueSelector(x)]); + }); + return _map; } @@ -513,13 +592,18 @@ function group(array, keySelector, valueSelector) { * @param {array} arr * @param {Function} childrenSelector, a transform function to apply to each element that returns an array of children * @return {array} - * + * * @example * [[1, 2], [3, 4], [5, 6]], (x) => x => [ 1, 2, 3, 4, 5, 6 ] * ['one','two','three'], x=>x.split('') => ['o','n','e','t','w','o','t','h','r','e','e'] */ function selectMany(arr, childrenSelector) { - throw new Error('Not implemented'); + let myArr = []; + arr.map(elem => + { + myArr.push(...childrenSelector(elem)); + }); + return myArr; } @@ -529,70 +613,80 @@ function selectMany(arr, childrenSelector) { * @param {array} arr * @param {array} indexes * @return {any} element from array - * + * * @example - * [[1, 2], [3, 4], [5, 6]], [0,0] => 1 (arr[0][0]) - * ['one','two','three'], [2] => 'three' (arr[2]) + * [[1, 2], [3, 4], [5, 6]], [0,0] => 1 (arr[0][0]) + * ['one','two','three'], [2] => 'three' (arr[2]) * [[[ 1, 2, 3]]], [ 0, 0, 1 ] => 2 (arr[0][0][1]) */ function getElementByIndexes(arr, indexes) { - throw new Error('Not implemented'); + return indexes.map(elem => arr = arr[elem])[indexes.length - 1]; } /** * Swaps the head and tail of the specified array: - * the head (first half) of array move to the end, the tail (last half) move to the start. + * the head (first half) of array move to the end, the tail (last half) move to the start. * The middle element (if exists) leave on the same position. - * - * + * + * * @param {array} arr * @return {array} - * + * * @example * [ 1, 2, 3, 4, 5 ] => [ 4, 5, 3, 1, 2 ] - * \----/ \----/ - * head tail + * \----/ \----/ + * head tail + * + * [ 1, 2 ] => [ 2, 1 ] + * [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 5, 6, 7, 8, 1, 2, 3, 4 ] * - * [ 1, 2 ] => [ 2, 1 ] - * [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 5, 6, 7, 8, 1, 2, 3, 4 ] - * */ function swapHeadAndTail(arr) { - throw new Error('Not implemented'); + let len = Math.floor(arr.length / 2); + let head = arr.splice(0, len); + let tail = arr.splice(arr.length-len, len); + let array = []; + array.push(...tail); + if(arr.length % 2) { + let middle = arr.splice(0, 1); + array.push(...middle) + } + array.push(...head); + return array; } module.exports = { - findElement: findElement, - generateOdds: generateOdds, - doubleArray: doubleArray, - getArrayOfPositives: getArrayOfPositives, - getArrayOfStrings: getArrayOfStrings, - removeFalsyValues: removeFalsyValues, - getUpperCaseStrings: getUpperCaseStrings, - getStringsLength: getStringsLength, - insertItem: insertItem, - getHead: getHead, - getTail: getTail, - toCsvText: toCsvText, - toStringList: toStringList, - toArrayOfSquares: toArrayOfSquares, - getMovingSum: getMovingSum, - getSecondItems: getSecondItems, - propagateItemsByPositionIndex: propagateItemsByPositionIndex, - get3TopItems: get3TopItems, - getPositivesCount: getPositivesCount, - sortDigitNamesByNumericOrder: sortDigitNamesByNumericOrder, - getItemsSum: getItemsSum, - getFalsyValuesCount: getFalsyValuesCount, - findAllOccurences: findAllOccurences, - sortCitiesArray: sortCitiesArray, - getIdentityMatrix: getIdentityMatrix, - getIntervalArray: getIntervalArray, - distinct: distinct, - group: group, - selectMany: selectMany, - getElementByIndexes: getElementByIndexes, - swapHeadAndTail: swapHeadAndTail + findElement: findElement, + generateOdds: generateOdds, + doubleArray: doubleArray, + getArrayOfPositives: getArrayOfPositives, + getArrayOfStrings: getArrayOfStrings, + removeFalsyValues: removeFalsyValues, + getUpperCaseStrings: getUpperCaseStrings, + getStringsLength: getStringsLength, + insertItem: insertItem, + getHead: getHead, + getTail: getTail, + toCsvText: toCsvText, + toStringList: toStringList, + toArrayOfSquares: toArrayOfSquares, + getMovingSum: getMovingSum, + getSecondItems: getSecondItems, + propagateItemsByPositionIndex: propagateItemsByPositionIndex, + get3TopItems: get3TopItems, + getPositivesCount: getPositivesCount, + sortDigitNamesByNumericOrder: sortDigitNamesByNumericOrder, + getItemsSum: getItemsSum, + getFalsyValuesCount: getFalsyValuesCount, + findAllOccurences: findAllOccurences, + sortCitiesArray: sortCitiesArray, + getIdentityMatrix: getIdentityMatrix, + getIntervalArray: getIntervalArray, + distinct: distinct, + group: group, + selectMany: selectMany, + getElementByIndexes: getElementByIndexes, + swapHeadAndTail: swapHeadAndTail }; From 207f049cda28056439e37a80bcb6e764915ab36b Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:29:53 +0300 Subject: [PATCH 23/40] Task7 --- task/07-yield-tasks.js | 69 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/task/07-yield-tasks.js b/task/07-yield-tasks.js index a2369790a..f975baff5 100644 --- a/task/07-yield-tasks.js +++ b/task/07-yield-tasks.js @@ -33,7 +33,19 @@ * */ function* get99BottlesOfBeer() { - throw new Error('Not implemented'); + for (let bottles = 99; bottles > 2; bottles--) { + yield `${bottles} bottles of beer on the wall, ${bottles} bottles of beer.`; + yield `Take one down and pass it around, ${bottles - 1} bottles of beer on the wall.`; + } + + yield `2 bottles of beer on the wall, 2 bottles of beer.`; + yield `Take one down and pass it around, 1 bottle of beer on the wall.`; + + yield `1 bottle of beer on the wall, 1 bottle of beer.`; + yield `Take one down and pass it around, no more bottles of beer on the wall.`; + + yield 'No more bottles of beer on the wall, no more bottles of beer.'; + yield 'Go to the store and buy some more, 99 bottles of beer on the wall.'; } @@ -47,7 +59,19 @@ function* get99BottlesOfBeer() { * */ function* getFibonacciSequence() { - throw new Error('Not implemented'); + yield 0; + yield 1; + + let last = 0; + let current = 1; + let new_val = 1; + + while (true){ + yield new_val; + last = current; + current = new_val; + new_val = last + current; + } } @@ -82,7 +106,14 @@ function* getFibonacciSequence() { * */ function* depthTraversalTree(root) { - throw new Error('Not implemented'); + let q1 = [root]; + while (q1.length) { + let node = q1.pop(); + yield node; + if (node.children) { + q1.push(...node.children.reverse()); + } + } } @@ -108,7 +139,24 @@ function* depthTraversalTree(root) { * */ function* breadthTraversalTree(root) { - throw new Error('Not implemented'); + let queue = [root]; + + while (queue.length != 0) { + let node = queue.shift(); + yield node; + + if (node.children !== undefined) { + + for (let child of node.children) { + + if (queue.length == 0 && child.children === undefined) { + yield child; + } else { + queue.push(child); + } + } + } + } } @@ -126,7 +174,18 @@ function* breadthTraversalTree(root) { * [ 1, 3, 5, ... ], [ -1 ] => [ -1, 1, 3, 5, ...] */ function* mergeSortedSequences(source1, source2) { - throw new Error('Not implemented'); + const sources = [source1(), source2()]; + let it = [sources[0].next(), sources[1].next()]; + + while (true) { + if (it[0].value >= it[1].value || it[0].value === undefined) { + yield it[1].value; + it[1] = sources[1].next(); + } else { + yield it[0].value; + it[0] = sources[0].next(); + } + } } From 6d1ecc32d527f4698aef56800ce02f47601535e4 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:32:40 +0300 Subject: [PATCH 24/40] Task8 --- task/08-objects-tasks.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/task/08-objects-tasks.js b/task/08-objects-tasks.js index 610b1e7b2..c06c91c2f 100644 --- a/task/08-objects-tasks.js +++ b/task/08-objects-tasks.js @@ -23,7 +23,12 @@ * console.log(r.getArea()); // => 200 */ function Rectangle(width, height) { - throw new Error('Not implemented'); + this.width = width; + this.height = height; + + this.getArea = () => { + return this.width * this.height + } } @@ -38,7 +43,7 @@ function Rectangle(width, height) { * { width: 10, height : 20 } => '{"height":10,"width":20}' */ function getJSON(obj) { - throw new Error('Not implemented'); + return JSON.stringify(obj); } @@ -55,6 +60,9 @@ function getJSON(obj) { */ function fromJSON(proto, json) { throw new Error('Not implemented'); + /*let obj = Object.create(proto); + obj = JSON.parse(json); + return obj;*/ } From 4dd70083d5e636e34053278cb41a391ff798a936 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:36:21 +0300 Subject: [PATCH 25/40] Task9 --- task/09-functions-n-closures-tasks.js | 50 ++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/task/09-functions-n-closures-tasks.js b/task/09-functions-n-closures-tasks.js index 6ba9dcedd..aaafd9401 100644 --- a/task/09-functions-n-closures-tasks.js +++ b/task/09-functions-n-closures-tasks.js @@ -26,7 +26,9 @@ * */ function getComposition(f,g) { - throw new Error('Not implemented'); + return function (x) { + return f(g(x)); + } } @@ -47,7 +49,9 @@ function getComposition(f,g) { * */ function getPowerFunction(exponent) { - throw new Error('Not implemented'); + return function (number) { + return Math.pow(number, exponent); + }; } @@ -65,7 +69,10 @@ function getPowerFunction(exponent) { * getPolynom() => null */ function getPolynom() { - throw new Error('Not implemented'); + var tmp = Array.from(arguments).reverse(); + return (x) => { + return tmp.reduce((prev, curr, index) => prev+curr*Math.pow(x,index),0); + }; } @@ -84,7 +91,10 @@ function getPolynom() { * memoizer() => the same random number (next run, returns the previous cached result) */ function memoize(func) { - throw new Error('Not implemented'); + let cashed = func(); + return function() { + return cashed + } } @@ -104,7 +114,16 @@ function memoize(func) { * retryer() => 2 */ function retry(func, attempts) { - throw new Error('Not implemented'); + return()=>{ + for (var i = 0; i < attempts;){ + try{ + return func(); + } catch(err){ + i += 1; + } + } + return i; + }; } @@ -132,7 +151,16 @@ function retry(func, attempts) { * */ function logger(func, logFunc) { - throw new Error('Not implemented'); + return function(){ + let tmp = Array.from(arguments); + let callStr = JSON.stringify(tmp); + callStr = callStr.substr(1, callStr.length - 2); + callStr = `${func.name}(${callStr})`; + logFunc(callStr + " starts"); + let res = func.apply(null, tmp); + logFunc(callStr + " ends"); + return res; + } } @@ -150,7 +178,10 @@ function logger(func, logFunc) { * partialUsingArguments(fn, 'a','b','c','d')() => 'abcd' */ function partialUsingArguments(fn) { - throw new Error('Not implemented'); + var applyArgs = Array.prototype.slice.call(arguments, 1); + return function() { + return fn.apply(null, Array.prototype.concat(applyArgs, Array.prototype.slice.call(arguments))); + }; } @@ -171,7 +202,10 @@ function partialUsingArguments(fn) { * getId10() => 11 */ function getIdGeneratorFunction(startFrom) { - throw new Error('Not implemented'); + var curr = startFrom; + return function() { + return startFrom++; + } } From 8dea60cefdb287867207d33cb446effc40276c42 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:38:17 +0300 Subject: [PATCH 26/40] Task10 --- task/10-katas-1-tasks.js | 136 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 8 deletions(-) diff --git a/task/10-katas-1-tasks.js b/task/10-katas-1-tasks.js index 5128b0695..74722ca2a 100644 --- a/task/10-katas-1-tasks.js +++ b/task/10-katas-1-tasks.js @@ -17,10 +17,58 @@ * ] */ function createCompassPoints() { - throw new Error('Not implemented'); - var sides = ['N','E','S','W']; // use array of cardinal directions only! -} + var sides = ['N', 'E', 'S', 'W'], // use array of cardinal directions only! + res = []; + + function getDoubleSide(side1, side2) { + switch (side1) { + case 'E': + return 'SE'; + case 'W': + return 'NW'; + default: + return side1 + side2; + } + } + for (var i = 0, currIndex, curr, next, newObj; i < 32; i++) { + newObj = {}; + currIndex = Math.trunc(i / 8); + curr = sides[currIndex]; + // prev = sides[curr > 0 ? curr - 1 : sides.length - 1]; + next = sides[currIndex < sides.length - 1 ? currIndex + 1 : 0]; + // console.log(curr,next); + switch (i % 8) { + case 0: + newObj['abbreviation'] = curr; + break; + case 1: + newObj['abbreviation'] = `${curr}b${next}`; + break; + case 2: + newObj['abbreviation'] = `${curr}${getDoubleSide(curr, next)}`; + break; + case 3: + newObj['abbreviation'] = `${getDoubleSide(curr, next)}b${curr}`; + break; + case 4: + newObj['abbreviation'] = getDoubleSide(curr, next); + break; + case 5: + newObj['abbreviation'] = `${getDoubleSide(curr, next)}b${next}`; + break; + case 6: + newObj['abbreviation'] = `${next}${getDoubleSide(curr, next)}`; + break; + case 7: + newObj['abbreviation'] = `${next}b${curr}`; + break; + } + newObj['azimuth'] = 11.25 * i; + res.push(newObj); + } + return res; +} /** * Expand the braces of the specified string. @@ -56,10 +104,54 @@ function createCompassPoints() { * 'nothing to do' => 'nothing to do' */ function* expandBraces(str) { - throw new Error('Not implemented'); + var search = '', + startIndex = -1, endIndex = -1, stack = [], tmpstr = []; + startIndex = str.indexOf('{'); + function outerSplit (string, symbol) { + var res = [], currStart = 0, stack = []; + for (var i = 0; i < string.length; i++) { + if (string.charAt(i) == '{') { + stack.push(0); + } else { + if (string.charAt(i) == '}') { + stack.pop(); + } else { + if (string.charAt(i) == symbol && stack.length == 0) { + res.push(string.slice(currStart, i)); + currStart = i + 1; + } + } + } + } + res.push(string.slice(currStart)) + return res; + } + if(startIndex != -1){ + for (endIndex = startIndex + 1; endIndex < str.length; endIndex++){ + if (str.charAt(endIndex) === '}') { + if (stack.length === 0) { + break; + } else { + stack.pop(); + } + } + if(str.charAt(endIndex) === '{') { + stack.push(0); + } + } + search = str.slice(startIndex, endIndex + 1); + } + if (search == '') { + yield str; + } else { + // tmpstr = search.slice(1,-1).split(','); + tmpstr = outerSplit(search.slice(1,-1), ','); + for (var i = 0; i < tmpstr.length; i++) { + yield * expandBraces(str.replace(search, tmpstr[i])); + } + } } - /** * Returns the ZigZag matrix * @@ -88,7 +180,26 @@ function* expandBraces(str) { * */ function getZigZagMatrix(n) { - throw new Error('Not implemented'); + let res = new Array(n).fill().map(()=> new Array(n).fill()); + let i = 0, j = 0; + let d = -1; + let start = 0, end = n*n - 1; + do { + res[i][j] = start++; + res[n - i - 1][n - j - 1] = end--; + i += d; + j -= d; + if (i < 0){ + i++; + d = -d; + } else if (j < 0){ + j++; + d = -d; + } + } while (start < end); + if (start === end) + res[i][j] = start; + return res; } @@ -113,7 +224,7 @@ function getZigZagMatrix(n) { * */ function canDominoesMakeRow(dominoes) { - throw new Error('Not implemented'); + return dominoes.map(x => x[0] + x[1]).reduce((prev, cur) => prev + cur) %2 != 0; } @@ -137,7 +248,16 @@ function canDominoesMakeRow(dominoes) { * [ 1, 2, 4, 5] => '1,2,4,5' */ function extractRanges(nums) { - throw new Error('Not implemented'); + for(var i = 0; i < nums.length; i++){ + var j = i; + while(nums[j] - nums[j + 1] == -1) { + j++; + } + if(j != i && j - i > 1) { + nums.splice(i, j - i + 1, nums[i] + '-' + nums[j]); + } + } + return nums.join(); } module.exports = { From 873577bb7acdf6df1755cd0716f360accb5d84d9 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:39:51 +0300 Subject: [PATCH 27/40] Task11 --- task/11-katas-2-tasks.js | 171 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 163 insertions(+), 8 deletions(-) diff --git a/task/11-katas-2-tasks.js b/task/11-katas-2-tasks.js index 2a1a745cd..e31289281 100644 --- a/task/11-katas-2-tasks.js +++ b/task/11-katas-2-tasks.js @@ -34,7 +34,44 @@ * */ function parseBankAccount(bankAccount) { - throw new Error('Not implemented'); + let Arrtmp = bankAccount.split('\n'); + let accNum = 0; + let numIfDig = 9; + for (let i = 0; i < numIfDig; i++){ + let tmpd; + let k = i*3; + if (Arrtmp[2][k+2] === ' '){ + tmpd = 2; + } else if (Arrtmp[1][k+2] === ' '){ + if (Arrtmp[2][k] === ' '){ + tmpd = 5; + } else { + tmpd = 6; + } + } else if (Arrtmp[2][k] === '|'){ + if (Arrtmp[1][k+1] === ' '){ + tmpd = 0; + } else { + tmpd = 8; + } + } else if (Arrtmp[0][k+1] === ' '){ + if (Arrtmp[1][k+1] === ' '){ + tmpd = 1; + } else { + tmpd = 4; + } + } else if (Arrtmp[1][k+1] === '_'){ + if (Arrtmp[1][k] === '|'){ + tmpd = 9; + } else { + tmpd = 3; + } + } else{ + tmpd = 7; + } + accNum += tmpd * Math.pow(10, numIfDig - i - 1); + } + return accNum; } @@ -63,7 +100,18 @@ function parseBankAccount(bankAccount) { * 'characters.' */ function* wrapText(text, columns) { - throw new Error('Not implemented'); + var words = text.split(' '), curr = ''; + for (var i = 0; i < words.length; i++) { + if (curr.length + words[i].length <= columns) { + curr += words[i] + " "; + } else { + yield curr.trim(); + curr = words[i] + " "; + } + } + if (curr != '') { + yield curr.trim(); + } } @@ -100,20 +148,38 @@ const PokerRank = { } function getPokerHandRank(hand) { - throw new Error('Not implemented'); + const suites = '♥♠♦♣', + numbers = 'A234567891JQK'; + let suitArr = Array.from(suites, () => 0), + numArr = Array.from(numbers, () => 0); + for (let card of hand) { + suitArr[suites.indexOf(card.slice(-1))]++; + numArr[numbers.indexOf(card[0])]++; + } + numArr.push(numArr[0]); // Ace card + let suitStr = suitArr.join(''), + numStr = numArr.join(''); + return (numStr.indexOf('11111') !== -1) && + (suitStr.indexOf('5') !== -1) ? PokerRank.StraightFlush : + (numStr.indexOf('4') !== -1) ? PokerRank.FourOfKind : + (numStr.indexOf('2') !== -1) && (numStr.indexOf('3') !== -1) ? PokerRank.FullHouse : + (suitStr.indexOf('5') !== -1) ? PokerRank.Flush : + (numStr.indexOf('11111') !== -1) ? PokerRank.Straight : + (numStr.indexOf('3') !== -1) ? PokerRank.ThreeOfKind : + (numStr.match(/2.*2.+/)) ? PokerRank.TwoPairs : + (numStr.indexOf('2') !== -1) ? PokerRank.OnePair : + PokerRank.HighCard; } - - /** * Returns the rectangles sequence of specified figure. * The figure is ASCII multiline string comprised of minus signs -, plus signs +, vertical bars | and whitespaces. * The task is to break the figure in the rectangles it is made of. * * NOTE: The order of rectanles does not matter. - * + * * @param {string} figure * @return {Iterable.} decomposition to basic parts - * + * * @example * * '+------------+\n'+ @@ -135,7 +201,96 @@ function getPokerHandRank(hand) { * '+-------------+\n' */ function* getFigureRectangles(figure) { - throw new Error('Not implemented'); + const Arrtmp = figure.split('\n'); + const pluses = []; + const horizontalLines = []; + const rectangles = []; + + for (let i = 0; i < Arrtmp.length; i++) + for (let j = 0; j < Arrtmp[0].length; j++) + if (Arrtmp[i][j] === '+') { + pluses.push({x: j, y: i}); + } + + for (let i = 0; i < pluses.length; i++) + for (let j = i + 1; j < pluses.length; j++) + if (pluses[i].y === pluses[j].y) { + if (checkHorizontalLine(Arrtmp, pluses[i], pluses[j])) + horizontalLines.push([pluses[i], pluses[j]]); + } + + for (let i = 0; i < horizontalLines.length; i++) + for (let j = i + 1; j < horizontalLines.length; j++) + if (checkRectangle(Arrtmp, horizontalLines[i], horizontalLines[j])) { + rectangles.push([horizontalLines[i], horizontalLines[j]]); + } + + for (let i = 0; i < rectangles.length; i++) { + let rectangle = drawRectangle(rectangles[i]); + + yield rectangle; + } +} + +function checkHorizontalLine(Arrtmp, s, f) { + for (let i = s.x; i <= f.y; i++) + if (Arrtmp[s.y][i] !== '-' && Arrtmp[s.y][i] !== '+') + return false; + + return true; +} + +function checkRectangle(Arrtmp, top, bottom) { + if (top[0].x !== bottom[0].x) + return false; + + if (top[1].x !== bottom[1].x) + return false; + + const leftX = top[0].x, + rightX = top[1].x, + topY = top[0].y, + bottomY = bottom[0].y; + + for (let j = leftX + 1; j < rightX; j++) + if (Arrtmp[topY][j] === '+' && Arrtmp[bottomY][j] === '+') { + let hasWhiteSpace = false; + + for (let i = topY + 1; i < bottomY; i++) + if (Arrtmp[i][j] === ' ') + hasWhiteSpace = true; + + + if (!hasWhiteSpace) + return false; + } + + for (let i = topY + 1; i < bottomY; i++) { + if (Arrtmp[i][leftX] !== '|' && Arrtmp[i][leftX] !== '+') + return false; + + if (Arrtmp[i][rightX] !== '|' && Arrtmp[i][rightX] !== '+') + return false; + + for (let j = leftX + 1; j < rightX; j++) + if (Arrtmp[i][j] !== ' ') + return false; + } + + return true; +} + +function drawRectangle(item) { + let width = item[0][1].x - item[0][0].x + 1, + height = item[1][0].y - item[0][0].y + 1, + result = '', + topLine = '+' + ('-').repeat(width - 2) + '+' + '\n'; + + result += topLine; + result += ( '|' + (' ').repeat(width - 2) + '|' + '\n' ).repeat(height - 2); + result += topLine; + + return result; } From f9253204b4eaffd72d7608e7e477c0c23c4af50c Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:41:18 +0300 Subject: [PATCH 28/40] Task12 --- task/12-katas-3-tasks.js | 110 ++++++++++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 18 deletions(-) diff --git a/task/12-katas-3-tasks.js b/task/12-katas-3-tasks.js index 5299be87d..ae4cd33af 100644 --- a/task/12-katas-3-tasks.js +++ b/task/12-katas-3-tasks.js @@ -10,13 +10,13 @@ * @return {bool} * * @example - * var puzzle = [ + * var puzzle = [ * 'ANGULAR', * 'REDNCAE', * 'RFIDTCL', * 'AGNEGSA', * 'YTIRTSP', - * ]; + * ]; * 'ANGULAR' => true (first row) * 'REACT' => true (starting from the top-right R adn follow the ↓ ← ← ↓ ) * 'UNDEFINED' => true @@ -25,10 +25,42 @@ * 'CLASS' => true * 'ARRAY' => true (first column) * 'FUNCTION' => false - * 'NULL' => false + * 'NULL' => false */ function findStringInSnakingPuzzle(puzzle, searchStr) { - throw new Error('Not implemented'); + function dfs(current, step) { + let save = data[current.y][current.x]; + data[current.y][current.x] = ""; + if (step == search.length) + return true; + let result = false, + steps = [[1, 0], [-1, 0], [0, -1], [0, 1]]; + for (let i = 0; i < 4; i++) { + let newX = current.x + steps[i][0], + newY = current.y + steps[i][1]; + if (data[newY][newX] == search[step]) { + result = result || dfs({x: newX, y: newY}, step + 1); + } + } + data[current.y][current.x] = save; + return result; + } + let data = Array.from(puzzle); + let Arrtmp = Array.from({length: data[0].length + 2}, () => ''); + data = data.map(item => [''].concat(item.split(''), [''])); + data = [Arrtmp].concat(data, [Arrtmp]); + let search = searchStr.split(''), + n = data[0].length - 1, + m = data.length - 1; + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) + if (data[i][j] == search[0]) { + if (dfs({y: i, x: j}, 1)) + return true; + } + } + + return false; } @@ -36,7 +68,7 @@ function findStringInSnakingPuzzle(puzzle, searchStr) { * Returns all permutations of the specified string. * Assume all chars in the specified string are different. * The order of permutations does not matter. - * + * * @param {string} chars * @return {Iterable.} all posible strings constructed with the chars from the specfied string * @@ -45,7 +77,20 @@ function findStringInSnakingPuzzle(puzzle, searchStr) { * 'abc' => 'abc','acb','bac','bca','cab','cba' */ function* getPermutations(chars) { - throw new Error('Not implemented'); + function *permute(a, len){ + if (len < 2) + yield a.join(''); + else{ + for (let i = 0; i < len; i++){ + yield *permute(a,len - 1); + const toSwap = len % 2 ? 0 : i; + let tmp = a[len - 1]; + a[len - 1] = a[toSwap]; + a[toSwap] = tmp; + } + } + } + yield *permute(chars.split(''), chars.length); } @@ -53,9 +98,9 @@ function* getPermutations(chars) { * Returns the most profit from stock quotes. * Stock quotes are stores in an array in order of date. * The stock profit is the difference in prices in buying and selling stock. - * Each day, you can either buy one unit of stock, sell any number of stock units you have already bought, or do nothing. + * Each day, you can either buy one unit of stock, sell any number of stock units you have already bought, or do nothing. * Therefore, the most profit is the maximum difference of all pairs in a sequence of stock prices. - * + * * @param {array} quotes * @return {number} max profit * @@ -65,7 +110,18 @@ function* getPermutations(chars) { * [ 1, 6, 5, 10, 8, 7 ] => 18 (buy at 1,6,5 and sell all at 10) */ function getMostProfitFromStockQuotes(quotes) { - throw new Error('Not implemented'); + let tmpMax = quotes + .reduceRight((prev, curr)=>{ + if(prev.length == 0) + return [curr]; + prev.push(Math.max(prev[prev.length - 1],curr)); + return prev; + }, []) + .reverse(); + tmpMax.push(0); + return quotes.reduce((prev, curr, index)=>{ + return prev + Math.max(0, tmpMax[index + 1] - curr); + }, 0); } @@ -73,31 +129,49 @@ function getMostProfitFromStockQuotes(quotes) { * Class representing the url shorting helper. * Feel free to implement any algorithm, but do not store link in the key\value stores. * The short link can be at least 1.5 times shorter than the original url. - * + * * @class * * @example - * + * * var urlShortener = new UrlShortener(); * var shortLink = urlShortener.encode('https://en.wikipedia.org/wiki/URL_shortening'); * var original = urlShortener.decode(shortLink); // => 'https://en.wikipedia.org/wiki/URL_shortening' - * + * */ function UrlShortener() { this.urlAllowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ - "abcdefghijklmnopqrstuvwxyz"+ - "0123456789-_.~!*'();:@&=+$,/?#[]"; + "abcdefghijklmnopqrstuvwxyz"+ + "0123456789-_.~!*'();:@&=+$,/?#[]"; } UrlShortener.prototype = { encode: function(url) { - throw new Error('Not implemented'); + let res = ""; + for (let i = 0; i < url.length; i += 2) { + let tmp1 = url.charCodeAt(i); + let tmp2 = url.charCodeAt(i + 1); + let code = (tmp1 << 8) |tmp2; + res += String.fromCharCode(code); + } + return res; }, - + decode: function(code) { - throw new Error('Not implemented'); - } + let res = ""; + for (let i = 0; i < code.length; i++) { + let char = parseInt(code.charCodeAt(i), 10); + let tmp1 = char & 255; + let tmp2 = (char >> 8) & 255; + if (tmp1 === 0) { + res += String.fromCharCode(tmp2) + } else { + res += String.fromCharCode(tmp2) + String.fromCharCode(tmp1); + } + } + return res; + } } From 6e59569263be6f34fac62ae3545a982285f0daa3 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:44:28 +0300 Subject: [PATCH 29/40] Tests1 --- test/01-strings-tests.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/01-strings-tests.js b/test/01-strings-tests.js index 6716f76ac..cfb8cf3cc 100644 --- a/test/01-strings-tests.js +++ b/test/01-strings-tests.js @@ -1,4 +1,4 @@ -'use strict' ; +'use strict'; var assert = require('assert'); var tasks = require('../task/01-strings-tasks'); @@ -73,21 +73,21 @@ describe('01-strings-tasks', function() { it.optional('getRectangleString should return the string reprentation of rectangle with specified size', function() { assert.equal( - '┌────┐\n'+ - '│ │\n'+ - '│ │\n'+ - '└────┘\n', + '┌────┐\n'+ + '│ │\n'+ + '│ │\n'+ + '└────┘\n', tasks.getRectangleString(6, 4) ); assert.deepEqual( - '┌┐\n'+ - '└┘\n', + '┌┐\n'+ + '└┘\n', tasks.getRectangleString(2, 2) ); assert.deepEqual( - '┌──────────┐\n'+ - '│ │\n'+ - '└──────────┘\n', + '┌──────────┐\n'+ + '│ │\n'+ + '└──────────┘\n', tasks.getRectangleString(12, 3) ); }); @@ -109,13 +109,13 @@ describe('01-strings-tasks', function() { assert.equal(true, tasks.isString('test'), "test"); assert.equal(true, tasks.isString(new String('test')), "new String('test')"); }); - + it.optional('getCardId should return the index of card in the initial deck', function() { [ - 'A♣','2♣','3♣','4♣','5♣','6♣','7♣','8♣','9♣','10♣','J♣','Q♣','K♣', - 'A♦','2♦','3♦','4♦','5♦','6♦','7♦','8♦','9♦','10♦','J♦','Q♦','K♦', - 'A♥','2♥','3♥','4♥','5♥','6♥','7♥','8♥','9♥','10♥','J♥','Q♥','K♥', - 'A♠','2♠','3♠','4♠','5♠','6♠','7♠','8♠','9♠','10♠','J♠','Q♠','K♠' + 'A♣','2♣','3♣','4♣','5♣','6♣','7♣','8♣','9♣','10♣','J♣','Q♣','K♣', + 'A♦','2♦','3♦','4♦','5♦','6♦','7♦','8♦','9♦','10♦','J♦','Q♦','K♦', + 'A♥','2♥','3♥','4♥','5♥','6♥','7♥','8♥','9♥','10♥','J♥','Q♥','K♥', + 'A♠','2♠','3♠','4♠','5♠','6♠','7♠','8♠','9♠','10♠','J♠','Q♠','K♠' ].forEach((val, index) => { assert.equal( index, @@ -123,6 +123,6 @@ describe('01-strings-tasks', function() { `Invalid id for card '${val}':` ) }); - + }); }); From 6bd80a9fe597bc0ca888424435659cd158c70e8c Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:46:46 +0300 Subject: [PATCH 30/40] Tests2 --- test/02-numbers-tests.js | 356 +++------ test/04-arrays-tests.js | 1548 +++++++++++++++++++++----------------- 2 files changed, 962 insertions(+), 942 deletions(-) diff --git a/test/02-numbers-tests.js b/test/02-numbers-tests.js index dfbb68462..f58bad8bd 100644 --- a/test/02-numbers-tests.js +++ b/test/02-numbers-tests.js @@ -1,257 +1,103 @@ 'use strict'; -/******************************************************************************************** - * * - * Plese read the following tutorial before implementing tasks: * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Numbers_and_dates * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math * - * * - ********************************************************************************************/ - - -/** - * Returns an area of a rectangle given by width and heigth. - * - * @param {numder} width - * @param {number} height - * @return {number} - * - * @example: - * 5, 10 => 50 - * 5, 5 => 25 - */ -function getRectangleArea(width, height) { - return width * height; -} - - -/** - * Returns a circumference of circle given by radius. - * - * @param {number} radius - * @return {number} - * - * @example: - * 5 => 31.41592653589793 - * 3.14 => 19.729201864543903 - * 0 => 0 - */ -function getCicleCircumference(radius) { - return 2 * Math.PI * radius; -} - -/** - * Returns an average of two given numbers. - * - * @param {numder} value1 - * @param {number} value2 - * @return {number} - * - * @example: - * 5, 5 => 5 - * 10, 0 => 5 - * -3, 3 => 0 - */ -function getAverage(value1, value2) { - return value1 / 2 + value2 / 2; -} - -/** - * Returns a distance beetween two points by cartesian coordinates. - * - * @param {number} x1 - * @param {number} y1 - * @param {number} x2 - * @param {number} y2 - * - * @return {number} - * - * @example: - * (0,0) (0,1) => 1 - * (0,0) (1,0) => 1 - * (-5,0) (10,-10) => 18.027756377319946 - */ -function getDistanceBetweenPoints(x1, y1, x2, y2) { - return Math.hypot((x2 - x1), (y2 - y1)); -} - -/** - * Returns a root of linear equation a*x + b = 0 given by coefficients a and b. - * - * @param {number} a - * @param {number} b - * @return {number} - * - * @example: - * 5*x - 10 = 0 => 2 - * x + 8 = 0 => -8 - * 5*x = 0 => 0 - */ -function getLinearEquationRoot(a, b) { - return -b / a; -} - - -/** - * Returns an angle (in radians) between two vectors given by xi and yi, coordinates in Cartesian plane - * See details https://en.wikipedia.org/wiki/Euclidean_vector#Representations - * - * @param {number} x1 - * @param {number} y1 - * @param {number} x2 - * @param {number} y2 - * @return {number} - * - * @example: - * (1,0) (0,1) => π/2 - * (0,1) (0,-1) => π - * (0,-1) (1,0) => π/2 - * (0,1) (0,1) => 0 - * (0,1) (1,2) => 0 - */ -function getAngleBetweenVectors(x1, y1, x2, y2) { - return Math.acos( - (x1 * x2 + y1 * y2) / (Math.sqrt(x1 * x1 + y1 * y1) * Math.sqrt(x2 * x2 + y2 * y2)) - ); -} - -/** - * Returns a last digit of a integer number. - * - * @param {number} value - * @return {number} - * - * @example: - * 100 => 0 - * 37 => 7 - * 5 => 5 - * 0 => 0 - */ -function getLastDigit(value) { - return value % 10; -} - - -/** - * Returns a number by given string representation. - * - * @param {string} value - * @return {number} - * - * @example: - * '100' => 100 - * '37' => 37 - * '-525.5' => -525.5 - */ -function parseNumberFromString(value) { - return Number.parseFloat(value); -} - -/** - * Returns a diagonal length of the rectangular parallelepiped given by its sides a,b,c. - * - * @param {number} a - * @param {number} b - * @param {number} c - * @return {number} - * - * @example: - * 1,1,1 => 1.7320508075688772 - * 3,3,3 => 5.196152422706632 - * 1,2,3 => 3.741657386773941 - */ -function getParallelipidedDiagonal(a,b,c) { - return Math.sqrt(a * a + b * b + c * c); -} - -/** - * Returns the number rounded to specified power of 10. - * - * @param {number} num - * @param {number} pow - * @return {number} - * - * @example: - * 1234, 0 => 1234 - * 1234, 1 => 1230 - * 1234, 2 => 1200 - * 1234, 3 => 1000 - * 1678, 0 => 1678 - * 1678, 1 => 1680 - * 1678, 2 => 1700 - * 1678, 3 => 2000 - */ -function roundToPowerOfTen(num, pow) { - return Math.round(num / Math.pow(10, pow)) * Math.pow(10, pow); -} - -/** - * Returns true is the number is prime; otherwise false. - * See: https://en.wikipedia.org/wiki/Primality_test - * - * @param {number} n - * @return {bool} - * - * @example: - * 4 => false - * 5 => true - * 6 => false - * 7 => true - * 11 => true - * 12 => false - * 16 => false - * 17 => true - */ -function isPrime(n) { - if (n <= 1) { - return false ; - } else if (n <= 3) { - return true; - } else if (!(n % 2) || !(n % 3)) { - return false; - } - - let i = 5; - while (i * i <= n) { - if (!(n % i) || !(n % (i + 2))) { - return false; - } - i += 6; - } - return true ; -} - -/** - * Tries to convert value to number and returns it if conversion was successfull; - * otherwise returns default value passed as a second argument. - * - * @param {any} value - * @param {any} def - * @return {number} - * - * @example - * toNumber(null, 0) => 0 - * toNumber('test', 0) => 0 - * toNumber('1', 0) => 1 - * toNumber(42, 0) => 42 - * toNumber(new Number(42), 0) => 42 - */ -function toNumber(value,def) { - return +value ? +value : def; -} - -module.exports = { - getRectangleArea: getRectangleArea, - getCicleCircumference: getCicleCircumference, - getAverage: getAverage, - getDistanceBetweenPoints: getDistanceBetweenPoints, - getLinearEquationRoot: getLinearEquationRoot, - getAngleBetweenVectors: getAngleBetweenVectors, - getLastDigit: getLastDigit, - parseNumberFromString: parseNumberFromString, - getParallelipidedDiagonal: getParallelipidedDiagonal, - roundToPowerOfTen: roundToPowerOfTen, - isPrime: isPrime, - toNumber: toNumber -}; +var assert = require('assert'); +var tasks = require('../task/02-numbers-tasks'); +it.optional = require('../extensions/it-optional'); + +describe('02-numbers-tasks', function() { + + it.optional('getRectangleArea should return a square of rectangle', function() { + assert.equal(50, tasks.getRectangleArea(5, 10)); + assert.equal(25, tasks.getRectangleArea(5, 5)); + }); + + it.optional('getCicleCircumference should return a circumference of cicle', function() { + assert.equal(31.41592653589793, tasks.getCicleCircumference(5)); + assert.equal(19.729201864543903, tasks.getCicleCircumference(3.14)); + assert.equal(0, tasks.getCicleCircumference(0)); + }); + + it.optional('getAverage should return an average of two numbers', function() { + assert.equal(5, tasks.getAverage(5, 5)); + assert.equal(5, tasks.getAverage(10, 0)); + assert.equal(0, tasks.getAverage(-3, 3)); + assert.equal(Number.MAX_VALUE-1, tasks.getAverage(Number.MAX_VALUE-2, Number.MAX_VALUE)); + assert.equal(Number.MAX_VALUE / 4, tasks.getAverage(Number.MAX_VALUE, -Number.MAX_VALUE / 2)); + }); + + it.optional('getDistanceBetweenPoints should return a distance between points', function() { + assert.equal(1, tasks.getDistanceBetweenPoints(0, 0, 0, 1)); + assert.equal(1, tasks.getDistanceBetweenPoints(0, 0, 1, 0)); + assert.equal(18.027756377319946, tasks.getDistanceBetweenPoints(-5, 0, 10, -10)); + }); + + it.optional('getLinearEquationRoot should return a root of linear equation', function() { + assert.equal(2, tasks.getLinearEquationRoot(5, -10)); + assert.equal(-8, tasks.getLinearEquationRoot(1, 8)); + assert.equal(0, tasks.getLinearEquationRoot(5, 0)); + }); + + it.optional('getAngleBetweenVectors should return a angle (in radians) between two linear vectors', function() { + assert.equal(Math.PI/2, tasks.getAngleBetweenVectors(1, 0, 0, 1)); + assert.equal(Math.PI, tasks.getAngleBetweenVectors(0, 1, 0, -1)); + assert.equal(Math.PI/2, tasks.getAngleBetweenVectors(0, -1, 1, 0)); + assert.equal(0, tasks.getAngleBetweenVectors(0, 1, 0, 1)); + }); + + it.optional('getLastDigit should return a last digit of the number', function() { + assert.equal(0, tasks.getLastDigit(100)); + assert.equal(7, tasks.getLastDigit(37)); + assert.equal(5, tasks.getLastDigit(5)); + assert.equal(0, tasks.getLastDigit(0)); + }); + + it.optional('parseNumberFromString should return a number from the given string representation', function() { + assert.equal(100, tasks.parseNumberFromString('100')); + assert.equal(37, tasks.parseNumberFromString('37')); + assert.equal(-525.5, tasks.parseNumberFromString('-525.5')); + }); + + it.optional('getParallelipidedDiagonal should return a diagonal length of the rectagular parallepiped', function() { + assert.equal(Math.sqrt(3), tasks.getParallelipidedDiagonal(1,1,1)); + assert.equal(Math.sqrt(27), tasks.getParallelipidedDiagonal(3,3,3)); + //assert.equal(Math.sqrt(14), tasks.getParallelipidedDiagonal(1,2,3)); + }); + + it.optional('roundToPowerOfTen should return an number rounded to specified power of 10', function() { + assert.equal(1234, tasks.roundToPowerOfTen(1234,0)); + assert.equal(1230, tasks.roundToPowerOfTen(1234,1)); + assert.equal(1200, tasks.roundToPowerOfTen(1234,2)); + assert.equal(1000, tasks.roundToPowerOfTen(1234,3)); + + assert.equal(9678, tasks.roundToPowerOfTen(9678,0)); + assert.equal(9680, tasks.roundToPowerOfTen(9678,1)); + assert.equal(9700, tasks.roundToPowerOfTen(9678,2)); + assert.equal(10000, tasks.roundToPowerOfTen(9678,3)); + }); + + it.optional('isPrime should return true if specified number is prime', function() { + assert.equal(true, tasks.isPrime(2), "2"); + assert.equal(true, tasks.isPrime(3), "3"); + assert.equal(false, tasks.isPrime(4), "4"); + assert.equal(true, tasks.isPrime(5), "5"); + assert.equal(false, tasks.isPrime(6), "6"); + assert.equal(true, tasks.isPrime(7), "7"); + assert.equal(false, tasks.isPrime(8), "8"); + assert.equal(false, tasks.isPrime(9), "9"); + assert.equal(false, tasks.isPrime(10), "10"); + assert.equal(true, tasks.isPrime(11), "11"); + assert.equal(false, tasks.isPrime(12), "12"); + assert.equal(true, tasks.isPrime(13), "13"); + assert.equal(true, tasks.isPrime(113), "113"); + assert.equal(false, tasks.isPrime(119), "119"); + }); + + it.optional('toNumber should convert any value to number or return the default', function() { + assert.equal(0, tasks.toNumber(null, 0)); + assert.equal(0, tasks.toNumber('test', 0)); + assert.equal(1, tasks.toNumber('1', 0)); + assert.equal(42, tasks.toNumber(42, 0)); + assert.equal(42, tasks.toNumber(new Number(42), 0)); + assert.equal(-1, tasks.toNumber(undefined, -1)); + }); +}); diff --git a/test/04-arrays-tests.js b/test/04-arrays-tests.js index 4daac09f7..d85352a56 100644 --- a/test/04-arrays-tests.js +++ b/test/04-arrays-tests.js @@ -1,692 +1,866 @@ 'use strict'; -/********************************************************************************************* - * * - * Plese read the following tutorial before implementing tasks: * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array * - * * - * NOTE : Please do not use loops! All tasks can be implmeneted using standard Array methods * - * * - *********************************************************************************************/ - - -/** - * Returns an index of the specified element in array or -1 if element is not found - * - * @param {array} arr - * @param {any} value - * @return {number} - * - * @example - * ['Ace', 10, true], 10 => 1 - * ['Array', 'Number', 'string'], 'Date' => -1 - * [0, 1, 2, 3, 4, 5], 5 => 5 - */ -function findElement(arr, value) { - return arr.indexOf(value); -} - -/** - * Generates an array of odd numbers of the specified length - * - * @param {number} len - * @return {array} - * - * @example - * 1 => [ 1 ] - * 2 => [ 1, 3 ] - * 5 => [ 1, 3, 5, 7, 9 ] - */ -function generateOdds(len) { - let arr = new Array(len); - arr.fill(1); - return arr.map((elem, index) => - { - return elem = index*2 + 1; - }); -} - - -/** - * Returns the doubled array - elements of the specified array are repeated twice using original order - * - * @param {array} arr - * @return {array} - * - * @example - * ['Ace', 10, true] => ['Ace', 10, true, 'Ace', 10, true] - * [0, 1, 2, 3, 4, 5] => [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5] - * [] => [] - */ -function doubleArray(arr) { - arr.map(elem => - { - return arr.push(elem); - }); - return arr; -} - - -/** - * Returns an array of positive numbers from the specified array in original order - * - * @param {array} arr - * @return {array} - * - * @example - * [ 0, 1, 2, 3, 4, 5 ] => [ 1, 2, 3, 4, 5 ] - * [-1, 2, -5, -4, 0] => [ 2 ] - * [] => [] - */ -function getArrayOfPositives(arr) { - return arr.filter((item, i, arr) => item > 0); -} - -/** - * Returns the array with strings only in the specified array (in original order) - * - * @param {array} arr - * @return {array} - * - * @example - * [ 0, 1, 'cat', 3, true, 'dog' ] => [ 'cat', 'dog' ] - * [ 1, 2, 3, 4, 5 ] => [] - * [ 'cat, 'dog', 'raccon' ] => [ 'cat', 'dog', 'racoon' ] - */ -function getArrayOfStrings(arr) { - return arr.filter(elem => typeof elem === 'string' || elem instanceof String); -} - -/** - * Removes falsy values from the specified array - * Falsy values: false, null, 0, "", undefined, and NaN. - * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean#Description) - * - * @param {array} arr - * @return {array} - * - * @example - * [ 0, false, 'cat', NaN, true, '' ] => [ 'cat', true ] - * [ 1, 2, 3, 4, 5, 'false' ] => [ 1, 2, 3, 4, 5, 'false' ] - * [ false, 0, NaN, '', undefined ] => [ ] - */ -function removeFalsyValues(arr) { - return arr.filter((item, i, arr) => !!item); -} - -/** - * Returns the array of useprcase strings from the specified array - * - * @param {array} arr - * @return {array} - * - * @example - * [ 'permanent-internship', 'glutinous-shriek', 'multiplicative-elevation' ] => [ 'PERMANENT-INTERNSHIP', 'GLUTINOUS-SHRIEK', 'MULTIPLICATIVE-ELEVATION' ] - * [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ] => [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ] - */ -function getUpperCaseStrings(arr) { - return arr.map(elem => - { - return elem.toUpperCase(); - }); -} - - -/** - * Returns the array of string lengths from the specified string array. - * - * @param {array} arr - * @return {array} - * - * @example - * [ '', 'a', 'bc', 'def', 'ghij' ] => [ 0, 1, 2, 3, 4 ] - * [ 'angular', 'react', 'ember' ] => [ 7, 5, 5 ] - */ -function getStringsLength(arr) { - return arr.map((item, i, arr) => item.length); -} - -/** - * Inserts the item into specified array at specified index - * - * @param {array} arr - * @param {any} item - * @param {number} index - * - * @example - * [ 1, 3, 4, 5 ], 2, 1 => [ 1, 2, 3, 4, 5 ] - * [ 1, 'b', 'c'], 0, 'x' => [ 'x', 1, 'b', 'c' ] - */ -function insertItem(arr, item, index) { - return arr.splice(index, 0, item); -} - -/** - * Returns the n first items of the specified array - * - * @param {array} arr - * @param {number} n - * - * @example - * [ 1, 3, 4, 5 ], 2 => [ 1, 2 ] - * [ 'a', 'b', 'c', 'd'], 3 => [ 'a', 'b', 'c' ] - */ -function getHead(arr, n) { - return arr.slice(0, n); -} - - -/** - * Returns the n last items of the specified array - * - * @param {array} arr - * @param {number} n - * - * @example - * [ 1, 3, 4, 5 ], 2 => [ 4, 5 ] - * [ 'a', 'b', 'c', 'd'], 3 => [ 'b', 'c', 'd' ] - */ -function getTail(arr, n) { - return arr.splice( -n); -} - - -/** - * Returns CSV represebtation of two-dimentional numeric array. - * https://en.wikipedia.org/wiki/Comma-separated_values - * - * @param {array} arr - * @return {string} - * - * @example - * [ - * [ 0, 1, 2, 3, 4 ], - * [ 10,11,12,13,14 ], - * [ 20,21,22,23,24 ], - * [ 30,31,32,33,34 ] - * ] - * => - * '0,1,2,3,4\n' - * +'10,11,12,13,14\n' - * +'20,21,22,23,24\n' - * +'30,31,32,33,34' - */ -function toCsvText(arr) { - let arrStr = arr.map((elem, index) => - { - if (index != arr.length-1) - return elem.join(',') + '\n'; - else - return elem.join(','); - }).join(''); - return arrStr; -} - -/** - * Transforms the numeric array into the according array of squares: - * f(x) = x * x - * - * @param {array} arr - * @return {array} - * - * @example - * [ 0, 1, 2, 3, 4, 5 ] => [ 0, 1, 4, 9, 16, 25 ] - * [ 10, 100, -1 ] => [ 100, 10000, 1 ] - */ -function toArrayOfSquares(arr) { - return arr.map((item, i, arr) => item ** 2); -} - - -/** - * Transforms the numeric array to the according moving sum array: - * f[n] = x[0] + x[1] + x[2] +...+ x[n] - * or f[n] = f[n-1] + x[n] - * - * @param {array} arr - * @return {array} - * - * Example : - * [ 1, 1, 1, 1, 1 ] => [ 1, 2, 3, 4, 5 ] - * [ 10, -10, 10, -10, 10 ] => [ 10, 0, 10, 0, 10 ] - * [ 0, 0, 0, 0, 0] => [ 0, 0, 0, 0, 0] - * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55 ] - */ -function getMovingSum(arr) { - let sum = 0; - return arr.map((elem) => - { - elem += sum; - sum = elem; - return elem; - }); -} - -/** - * Returns every second item from the specified array: - * - * @param {array} arr - * @return {array} - * - * Example : - * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 2, 4, 6, 8, 10 ] - * [ 'a', 'b', 'c' , null ] => [ "b", null ] - * [ "a" ] => [] - */ -function getSecondItems(arr) { - return arr.filter((elem, index) => index % 2); -} - - -/** - * Propagates every item in sequence its position times - * Returns an array that consists of: one first item, two second items, tree third items etc. - * - * @param {array} arr - * @return {array} - * - * @example : - * [] => [] - * [ 1 ] => [ 1 ] - * [ 'a', 'b' ] => [ 'a', 'b','b' ] - * [ 'a', 'b', 'c', null ] => [ 'a', 'b','b', 'c','c','c', null,null,null,null ] - * [ 1,2,3,4,5 ] => [ 1, 2,2, 3,3,3, 4,4,4,4, 5,5,5,5,5 ] - */ -function propagateItemsByPositionIndex(arr) { - return arr.reduce((acc, elem, index) => acc.concat( - Array.from({length: index + 1}, () => elem)), [] - ); -} - - -/** - * Returns the 3 largest numbers from the specified array - * - * @param {array} arr - * @return {array} - * - * @example - * [] => [] - * [ 1, 2 ] => [ 2, 1 ] - * [ 1, 2, 3 ] => [ 3, 2, 1 ] - * [ 1,2,3,4,5,6,7,8,9,10 ] => [ 10, 9, 8 ] - * [ 10, 10, 10, 10 ] => [ 10, 10, 10 ] - */ -function get3TopItems(arr) { - return arr.sort((a, b) => b - a).slice(0, 3); -} - - -/** - * Returns the number of positive numbers from specified array - * - * @param {array} arr - * @return {number} - * - * @example - * [ ] => 0 - * [ -1, 0, 1 ] => 1 - * [ 1, 2, 3] => 3 - * [ null, 1, 'elephant' ] => 1 - * [ 1, '2' ] => 1 - */ -function getPositivesCount(arr) { - throw new Error('Not implemented'); -} - -/** - * Sorts digit names - * - * @param {array} arr - * @return {array} - * - * @example - * [] => [] - * [ 'nine','one' ] => [ 'one', 'nine' ] - * [ 'one','two','three' ] => [ 'one','two', 'three' ] - * [ 'nine','eight','nine','eight'] => [ 'eight','eight','nine','nine'] - * [ 'one','one','one','zero' ] => [ 'zero','one','one','one' ] - */ -function sortDigitNamesByNumericOrder(arr) { - const mapped1 = { 0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six', 7: 'seven', 8: 'eight', 9: 'nine' }; - const mapped2 = { 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9 }; - return arr.map(x => mapped2[x]).sort().map(x => mapped1[x]); -} - -/** - * Returns the sum of all items in the specified array of numbers - * - * @param {array} arr - * @return {number} - * - * @example - * [] => 0 - * [ 1, 2, 3 ] => 6 - * [ -1, 1, -1, 1 ] => 0 - * [ 1, 10, 100, 1000 ] => 1111 - */ -function getItemsSum(arr) { - return arr.reduce((acc, elem) => acc + elem, 0); -} - -/** - * Returns the number of all falsy value in the specified array - * - * @param {array} arr - * @return {array} - * - * @example - * [] => 0 - * [ 1, '', 3 ] => 1 - * [ -1, 'false', null, 0 ] => 2 - * [ null, undefined, NaN, false, 0, '' ] => 6 - */ -function getFalsyValuesCount(arr) { - let sum = 0; - arr.map(elem => - { - if (Boolean(elem) == 0) - return sum++; - }); - return sum; -} - -/** - * Returns a number of all occurences of the specified item in an array - * - * @param {array} arr - * @param {any} item - * @return {number} - * - * @example - * [ 0, 0, 1, 1, 1, 2 ], 1 => 3 - * [ 1, 2, 3, 4, 5 ], 0 => 0 - * [ 'a','b','c','c' ], 'c'=> 2 - * [ null, undefined, null ], null => 2 - * [ true, 0, 1, 'true' ], true => 1 - */ -function findAllOccurences(arr, item) { - let num = 0; - arr.map(elem => - { - if (elem === item) - return num++; - }) - return num; -} - -/** - * Concatenates all elements from specified array into single string with ',' delimeter - * - * @param {array} arr - * @return {string} - * - * @example - * [0, false, 'cat', NaN, true, ''] => '0,false,cat,NaN,true,' - * [1, 2, 3, 4, 5] => '1,2,3,4,5' - * ['rock', 'paper', 'scissors'] => 'rock,paper,scissors' - */ -function toStringList(arr) { - return arr.join(); -} - - -/** - * Sorts the specified array by country name first and city name (if countries are equal) in ascending order. - * - * @param {array} arr - * @return {array} - * - * @example - * [ - * { country: 'Russia', city: 'Moscow' }, - * { country: 'Belarus', city: 'Minsk' }, - * { country: 'Poland', city: 'Warsaw' }, - * { country: 'Russia', city: 'Saint Petersburg' }, - * { country: 'Poland', city: 'Krakow' }, - * { country: 'Belarus', city: 'Brest' } - * ] - * => - * [ - * { country: 'Belarus', city: 'Brest' }, - * { country: 'Belarus', city: 'Minsk' }, - * { country: 'Poland', city: 'Krakow' }, - * { country: 'Poland', city: 'Warsaw' }, - * { country: 'Russia', city: 'Moscow' }, - * { country: 'Russia', city: 'Saint Petersburg' } - */ -function sortCitiesArray(arr) { - function compareLocation(a, b) { - if (a.country < b.country) { - return -1; +var assert = require('assert'); +var tasks = require('../task/04-arrays-tasks'); +it.optional = require('../extensions/it-optional'); + +describe('04-arrays-tasks', function() { + + it.optional('findElement should return the index of specified value if exists', function () { + [ + { + arr: ['Ace', 10, true], + value: 10, + expected: 1 + },{ + arr: ['Array', 'Number', 'string'], + value: 'Date', + expected: -1 + },{ + arr: [0, 1, 2, 3, 4, 5], + value: 5, + expected: 5 } - if (a.country > b.country) { - return 1; + ].forEach(data => { + var actual = tasks.findElement(data.arr, data.value); + assert.equal( + data.expected, + actual, + `Index of '${data.value}' inside of [${data.arr}] = ${data.expected}, but actually ${actual}` + ); + }); + }); + + + it.optional('generateOdds should return the array of odd numbers of specified size', function () { + [ + { + len: 1, + expected: [ 1 ] + },{ + len: 2, + expected: [ 1, 3 ] + },{ + len: 5, + expected: [ 1, 3, 5, 7, 9 ] + },{ + len: 16, + expected: [ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 ] + } + ].forEach(data => { + assert.deepEqual( + data.expected, + tasks.generateOdds(data.len) + ); + }); + }); + + + it.optional('doubleArray should return the specified array twice', function () { + [ + { + arr: ['Ace', 10, true], + expected: ['Ace', 10, true, 'Ace', 10, true] + },{ + arr: [0, 1, 2, 3, 4, 5], + expected: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5] + },{ + arr: [], + expected: [] + } + ].forEach(data => { + var actual = tasks.doubleArray(data.arr); + assert.deepEqual( + data.expected, + actual, + `The result of doubling [${data.arr}] is not correct` + ); + }); + }); + + + it.optional('getArrayOfPositives should return the array of positive values from specified array', function () { + [ + { + arr: [ 0, 1, 2, 3, 4, 5 ], + expected: [ 1, 2, 3, 4, 5 ] + },{ + arr: [-1, 2, -5, -4, 0], + expected: [ 2 ] + },{ + arr: [], + expected: [] + } + ].forEach(data => { + var actual = tasks.getArrayOfPositives(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('getArrayOfStrings should return the array of string values from specified array', function () { + [ + { + arr: [ 0, 1, 'cat', 3, true, 'dog' ], + expected: [ 'cat', 'dog' ] + },{ + arr: [ 1, 2, 3, 4, 5 ], + expected: [ ] + },{ + arr: [ 'cat', 'dog', 'raccon' ], + expected: [ 'cat', 'dog', 'raccon' ] + } + ].forEach(data => { + var actual = tasks.getArrayOfStrings(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('removeFalsyValues should return the specified array without falsy values', function () { + [ + { + arr: [ 0, false, 'cat', NaN, true, '' ], + expected: [ 'cat', true ] + },{ + arr: [ 1, 2, 3, 4, 5, 'false' ], + expected: [ 1, 2, 3, 4, 5, 'false' ] + },{ + arr: [ false, 0, NaN, '', undefined ], + expected: [ ] + } + ].forEach(data => { + var actual = tasks.removeFalsyValues(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('findAllOccurences should return the number of all occurences of specified item in an array', function () { + [ + { + arr: [ 0, 0, 1, 1, 1, 2 ], + item: 1, + expected: 3 + },{ + arr: [ 1, 2, 3, 4, 5 ], + item: 0, + expected: 0 + },{ + arr: [ 'a','b','c','c' ], + item: 'c', + expected: 2 + },{ + arr: [ null, undefined, null ], + item: null, + expected: 2 + },{ + arr: [ true, 0, 1, 'true' ], + item: true, + expected: 1 + } + ].forEach(data => { + var actual = tasks.findAllOccurences(data.arr, data.item); + assert.equal( + data.expected, + actual, + `Number of occurences of ${JSON.stringify(data.item)} in ${JSON.stringify(data.arr)} is ${data.expected}, but actually ${actual})` + ); + }); + }); + + + it.optional('getUpperCaseStrings should convert strings from specified array to uppercase', function () { + [ + { + arr: [ 'permanent-internship', 'glutinous-shriek', 'multiplicative-elevation' ], + expected: [ 'PERMANENT-INTERNSHIP', 'GLUTINOUS-SHRIEK', 'MULTIPLICATIVE-ELEVATION' ] + },{ + arr: [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ], + expected: [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ] + } + ].forEach(data => { + var actual = tasks.getUpperCaseStrings(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('getStringsLength should convert strings from specified array to uppercase', function () { + [ + { + arr: [ '', 'a', 'bc', 'def', 'ghij' ], + expected: [ 0, 1, 2, 3, 4 ] + },{ + arr: [ 'angular', 'react', 'ember' ], + expected: [ 7, 5, 5 ] + } + ].forEach(data => { + var actual = tasks.getStringsLength(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('insertItem should insert an item at specified position', function () { + [ + { + arr: [ 1, 3, 4, 5 ], + item: 2, + index: 1, + expected: [ 1, 2, 3, 4, 5 ] + },{ + arr: [ 1, 'b', 'c' ], + item: 'x', + index: 0, + expected: [ 'x', 1, 'b', 'c' ] + } + ].forEach(data => { + tasks.insertItem(data.arr, data.item, data.index); + assert.deepEqual( + data.expected, + data.arr + ); + }); + }); + + + it.optional('getHead should return the n first items from the specified array', function () { + [ + { + arr: [ 1, 2, 3, 4, 5 ], + n: 2, + expected: [ 1, 2 ] + },{ + arr: [ 'a', 'b', 'c', 'd' ], + n: 3, + expected: [ 'a', 'b', 'c' ] + } + ].forEach(data => { + assert.deepEqual( + data.expected, + tasks.getHead(data.arr, data.n) + ); + }); + }); + + + it.optional('getTail should return the n last items from the specified array', function () { + [ + { + arr: [ 1, 2, 3, 4, 5 ], + n: 2, + expected: [ 4, 5 ] + },{ + arr: [ 'a', 'b', 'c', 'd' ], + n: 3, + expected: [ 'b', 'c', 'd' ] + } + ].forEach(data => { + assert.deepEqual( + data.expected, + tasks.getTail(data.arr, data.n) + ); + }); + }); + + + it.optional('toCsvText should convert two-dimentional numeric array to CSV format', function () { + [ + { + arr: [ + [ 0, 1, 2, 3, 4 ], + [ 10,11,12,13,14 ], + [ 20,21,22,23,24 ], + [ 30,31,32,33,34 ] + ], + expected: + '0,1,2,3,4\n' + +'10,11,12,13,14\n' + +'20,21,22,23,24\n' + +'30,31,32,33,34' + }, { + arr: [[]], + expected: '' + } + ].forEach(data => { + var actual = tasks.toCsvText(data.arr); + assert.equal( + data.expected, + actual + ); + }); + }); + + + it.optional('toArrayOfSquares should convert numeric array to the array of squares', function () { + [ + { + arr: [ 0, 1, 2, 3, 4, 5 ], + expected: [ 0, 1, 4, 9, 16, 25 ] + }, { + arr: [ 10, 100, -1 ], + expected: [ 100, 10000, 1 ] + } + ].forEach(data => { + var actual = tasks.toArrayOfSquares(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('getMovingSum should convert numeric array to the according array of moving sum', function () { + [ + { + arr: [ 1, 1, 1, 1, 1 ], + expected: [ 1, 2, 3, 4, 5 ] + }, { + arr: [ 10, -10, 10, -10, 10 ], + expected: [ 10, 0, 10, 0, 10 ] + }, { + arr: [ 0, 0, 0, 0, 0], + expected: [ 0, 0, 0, 0, 0] + }, { + arr: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], + expected: [ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55 ] + } + ].forEach(data => { + var actual = tasks.getMovingSum(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('getSecondItems should return every second item from the specified array', function () { + [ + { + arr: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], + expected: [ 2, 4, 6, 8, 10 ] + }, { + arr: [ 'a', 'b', 'c' , null ], + expected: [ "b", null ] + }, { + arr: [ "a" ], + expected: [ ] + } + ].forEach(data => { + var actual = tasks.getSecondItems(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('propagateItemsByPositionIndex should propagate every item its position time', function () { + [ + { + arr: [], + expected: [] + }, { + arr: [ 1 ], + expected: [ 1 ] + }, { + arr: [ 'a', 'b' ], + expected: [ 'a', 'b','b' ] + }, { + arr: [ 'a', 'b', 'c', null ], + expected: [ 'a', 'b','b', 'c','c','c', null,null,null,null ] + }, { + arr: [ 1, 2, 3, 4, 5 ], + expected: [ 1, 2,2, 3,3,3, 4,4,4,4, 5,5,5,5,5 ] + } + ].forEach(data => { + var actual = tasks.propagateItemsByPositionIndex(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('get3TopItems should return the 3 largest items from integer array', function () { + [ + { + arr: [], + expected: [] + }, { + arr: [ 1,2 ], + expected: [ 2,1 ] + }, { + arr: [ 1, 2, 3 ], + expected: [ 3, 2, 1 ] + }, { + arr: [ 1,2,3,4,5,6,7,8,9,10 ], + expected: [ 10,9,8 ] + }, { + arr: [ 10, 10, 10, 10], + expected: [ 10, 10, 10 ] + } + ].forEach(data => { + var actual = tasks.get3TopItems(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('getPositivesCount should return the number of positive integers in the specified array', function () { + [ + { + arr: [], + expected: 0 + }, { + arr: [ -1, 0, 1 ], + expected: 1 + }, { + arr: [ 1, 2, 3 ], + expected: 3 + }, { + arr: [ null, 1, 'elephant' ], + expected: 1 + }, { + arr: [ 1, '2' ], + expected: 1 + } + ].forEach(data => { + var actual = tasks.getPositivesCount(data.arr); + assert.equal( + data.expected, + actual, + `Test failed for argument [${data.arr}]` + ); + }); + }); + + + it.optional('sortDigitNamesByNumericOrder should sort digit names by its numeric value', function () { + [ + { + arr: [], + expected: [] + }, { + arr: [ 'nine','one' ], + expected: [ 'one', 'nine' ] + }, { + arr: [ 'one','two','three' ], + expected: [ 'one','two', 'three' ] + }, { + arr: [ 'nine','eight','nine','eight' ], + expected: [ 'eight','eight','nine','nine' ] + }, { + arr: [ 'one','one','one','zero' ], + expected: [ 'zero','one','one','one' ] + }, { + arr: [ 'nine','eight','seven','six','five','four','three','two','one','zero' ], + expected: [ 'zero','one','two','three','four','five','six','seven','eight','nine' ] + } + ].forEach(data => { + var actual = tasks.sortDigitNamesByNumericOrder(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('getItemsSum should return the sum of all items of numbers array', function () { + [ + { + arr: [ ], + expected: 0 + },{ + arr: [ 1, 2, 3 ], + expected: 6 + },{ + arr: [ 1, 10, 100, 1000 ], + expected: 1111 + } + ].forEach(data => { + var actual = tasks.getItemsSum(data.arr); + assert.deepEqual( + data.expected, + actual, + `Test failed for [${data.arr}]` + ); + }); + }); + + + it.optional('getFalsyValuesCount should return the number of falsy value in the specified array', function () { + [ + { + arr: [ ], + expected: 0 + },{ + arr: [ 1, '', 3 ], + expected: 1 + },{ + arr: [ -1, 'false', null, 0 ], + expected: 2 + },{ + arr: [ null, undefined, NaN, false, 0, '' ], + expected: 6 + } + ].forEach(data => { + var actual = tasks.getFalsyValuesCount(data.arr); + assert.deepEqual( + data.expected, + actual, + `Test failed for [${data.arr}]` + ); + }); + }); + + + it.optional('toStringList should return the string list of passed arguments', function () { + [ + { + arr: [ 0, false, 'cat', NaN, true, '' ], + expected: '0,false,cat,NaN,true,' + },{ + arr: [ 1, 2, 3, 4, 5 ], + expected: '1,2,3,4,5' + },{ + arr: [ 'rock', 'paper', 'scissors' ], + expected: 'rock,paper,scissors' + } + ].forEach(data => { + var actual = tasks.toStringList(data.arr); + assert.equal( + data.expected, + actual + ); + }); + }); + + + it.optional('sortCitiesArray should sort the array of objects using two keys', function () { + [ + { + arr: [ + { country: 'Russia', city: 'Moscow' }, + { country: 'Belarus', city: 'Minsk' }, + { country: 'Poland', city: 'Warsaw' }, + { country: 'Russia', city: 'Saint Petersburg' }, + { country: 'Poland', city: 'Krakow' }, + { country: 'Belarus', city: 'Brest' } + ], + expected: [ + { country: 'Belarus', city: 'Brest' }, + { country: 'Belarus', city: 'Minsk' }, + { country: 'Poland', city: 'Krakow' }, + { country: 'Poland', city: 'Warsaw' }, + { country: 'Russia', city: 'Moscow' }, + { country: 'Russia', city: 'Saint Petersburg' } + ] + }, { + arr: [ + { country: 'D', city: '1' }, + { country: 'E', city: '1' }, + { country: 'A', city: '2' }, + { country: 'B', city: '1' }, + { country: 'B', city: '2' }, + { country: 'A', city: '1' } + ], + expected: [ + { country: 'A', city: '1' }, + { country: 'A', city: '2' }, + { country: 'B', city: '1' }, + { country: 'B', city: '2' }, + { country: 'D', city: '1' }, + { country: 'E', city: '1' } + ] + },{ + arr: [ + { country: '5', city: '1' }, + { country: '1', city: '1' }, + { country: '1', city: '2' }, + { country: '1', city: '3' }, + { country: '2', city: '2' }, + { country: '1', city: '1' }, + { country: '1', city: '1' }, + { country: '2', city: '1' }, + { country: '3', city: '1' }, + { country: '3', city: '3' }, + { country: '2', city: '5' }, + { country: '5', city: '2' } + ], + expected: [ + { country: '1', city: '1' }, + { country: '1', city: '1' }, + { country: '1', city: '1' }, + { country: '1', city: '2' }, + { country: '1', city: '3' }, + { country: '2', city: '1' }, + { country: '2', city: '2' }, + { country: '2', city: '5' }, + { country: '3', city: '1' }, + { country: '3', city: '3' }, + { country: '5', city: '1' }, + { country: '5', city: '2' } + ] + } + ].forEach(data => { + var actual = tasks.sortCitiesArray(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('getIdentityMatrix should return the identity matrix of the specified size', function () { + [ + { + n: 1, + expected: [[1]] + }, { + n: 2, + expected: [[1,0], + [0,1]] + }, { + n: 5, + expected: [[1,0,0,0,0], + [0,1,0,0,0], + [0,0,1,0,0], + [0,0,0,1,0], + [0,0,0,0,1]] + } + ].forEach(data => { + var actual = tasks.getIdentityMatrix(data.n); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('getIntervalArray should return the array of integers from start to end (inclusive)', function () { + [ + { + start: 1, + end: 5, + expected: [ 1, 2, 3, 4, 5 ] + }, { + start: -2, + end: 2, + expected: [ -2, -1, 0, 1, 2 ] + }, { + start: 0, + end: 100, + expected: [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100 + ] + }, { + start: 3, + end: 3, + expected: [ 3 ] + } + ].forEach(data => { + var actual = tasks.getIntervalArray(data.start, data.end); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('distinct should return an array of unique items from the specified array', function () { + [ + { + arr: [ 1, 2, 3, 3, 2, 1 ], + expected: [ 1, 2, 3 ] + }, { + arr: [ 'a', 'a', 'a', 'a', 'a' ], + expected: [ 'a' ] + }, { + arr: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], + expected: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] + }, { + arr: [ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6 ], + expected: [ 1, 2, 3, 4, 5, 6 ] } - if (a.country === b.country) { - if (a.city < b.city) { - return -1; - } - if (a.city > b.city) { - return 1; - } - if (a.city === b.city) { - return 0; - } + ].forEach(data => { + var actual = tasks.distinct(data.arr); + assert.deepEqual( + data.expected, + actual + ); + }); + }); + + + + it.optional('group should return a map of grouped data by key and value selector', function () { + [ + { + arr: [ + { country: 'Belarus', city: 'Brest' }, + { country: 'Russia', city: 'Omsk' }, + { country: 'Russia', city: 'Samara' }, + { country: 'Belarus', city: 'Grodno' }, + { country: 'Belarus', city: 'Minsk' }, + { country: 'Poland', city: 'Lodz' } + ], + keySelector: item => item.country, + valueSelector: item => item.city, + expected: new Map([ + ['Belarus', ['Brest', 'Grodno', 'Minsk']], + ['Russia', ['Omsk', 'Samara']], + ['Poland', ['Lodz']] + ]) + }, { + arr: [ + { artist: 'ACDC', album: 'Highway to Hell' }, + { artist: 'Metallica', album: "Kill'em All" }, + { artist: 'Deep Purple', album: 'Machine Head' }, + { artist: 'Metallica', album: 'And Justice for All' }, + { artist: 'ACDC', album: 'Back in Black' }, + { artist: 'Manowar', album: 'Kings of Metal' } + ], + keySelector: item => item.artist, + valueSelector: item => item.album, + expected: new Map([ + ['ACDC', ['Highway to Hell', 'Back in Black']], + ['Metallica', ["Kill'em All", 'And Justice for All']], + ['Deep Purple', ['Machine Head']], + ['Manowar',['Kings of Metal']] + ]) } - } - return arr.sort(compareLocation); -} - -/** - * Creates an indentity matrix of the specified size - * - * @param {number} n - * @return {array} - * - * @example - * 1 => [[1]] - * - * 2 => [[1,0], - * [0,1]] - * - * [[1,0,0,0,0], - * [0,1,0,0,0], - * 5 => [0,0,1,0,0], - * [0,0,0,1,0], - * [0,0,0,0,1]] - */ -function getIdentityMatrix(n) { - return Array.from({length: n}, function (elem, index) { - let row = new Array(n).fill(0, 0, n); - row[index] = 1; - return row; - }); -} - -/** - * Creates an array of integers from the specified start to end (inclusive) - * - * @param {number} start - * @param {number} end - * @return {array} - * - * @example - * 1, 5 => [ 1, 2, 3, 4, 5 ] - * -2, 2 => [ -2, -1, 0, 1, 2 ] - * 0, 100 => [ 0, 1, 2, ..., 100 ] - * 3, 3 => [ 3 ] - */ -function getIntervalArray(start, end) { - return Array.from({length: end - start + 1}, (elem, index) => index + start); -} - -/** - * Returns array containing only unique values from the specified array. - * - * @param {array} arr - * @return {array} - * - * @example - * [ 1, 2, 3, 3, 2, 1 ] => [ 1, 2, 3 ] - * [ 'a', 'a', 'a', 'a' ] => [ 'a' ] - * [ 1, 1, 2, 2, 3, 3, 4, 4] => [ 1, 2, 3, 4] - */ -function distinct(arr) { - let resArr = []; - arr.map(elem => - { - if (resArr.indexOf(elem) == -1) - resArr.push(elem); - return resArr; - }); - return resArr; -} - -/** - * Groups elements of the specified array by key. - * Returns multimap of keys extracted from array elements via keySelector callback - * and values extracted via valueSelector callback. - * See: https://en.wikipedia.org/wiki/Multimap - * - * @param {array} array - * @param {Function} keySelector - * @param {Function} valueSelector - * @return {Map} - * - * @example - * group([ - * { country: 'Belarus', city: 'Brest' }, - * { country: 'Russia', city: 'Omsk' }, - * { country: 'Russia', city: 'Samara' }, - * { country: 'Belarus', city: 'Grodno' }, - * { country: 'Belarus', city: 'Minsk' }, - * { country: 'Poland', city: 'Lodz' } - * ], - * item => item.country, - * item => item.city - * ) - * => - * Map { - * "Belarus" => ["Brest", "Grodno", "Minsk"], - * "Russia" => ["Omsk", "Samara"], - * "Poland" => ["Lodz"] - * } - */ -function group(array, keySelector, valueSelector) { - let _map = new Map(); - array.map((x, ind) => - { - if( _map.has(keySelector(x)) ) - _map.get(keySelector(x)).push(valueSelector(x)); - else - _map.set(keySelector(x), [valueSelector(x)]); - }); - return _map ; -} - - -/** - * Projects each element of the specified array to a sequence and flattens the resulting sequences into one array. - * - * @param {array} arr - * @param {Function} childrenSelector, a transform function to apply to each element that returns an array of children - * @return {array} - * - * @example - * [[1, 2], [3, 4], [5, 6]], (x) => x => [ 1, 2, 3, 4, 5, 6 ] - * ['one','two','three'], x=>x.split('') => ['o','n','e','t','w','o','t','h','r','e','e'] - */ -function selectMany(arr, childrenSelector) { - let myArr = []; - arr.map(elem => - { - myArr.push(...childrenSelector(elem)); - }); - return myArr ; -} - - -/** - * Returns an element from the multidimentional array by the specified indexes. - * - * @param {array} arr - * @param {array} indexes - * @return {any} element from array - * - * @example - * [[1, 2], [3, 4], [5, 6]], [0,0] => 1 (arr[0][0]) - * ['one','two','three'], [2] => 'three' (arr[2]) - * [[[ 1, 2, 3]]], [ 0, 0, 1 ] => 2 (arr[0][0][1]) - */ -function getElementByIndexes(arr, indexes) { - return indexes.map(elem => arr = arr[elem])[indexes.length - 1]; -} - - -/** - * Swaps the head and tail of the specified array: - * the head (first half) of array move to the end, the tail (last half) move to the start. - * The middle element (if exists) leave on the same position. - * - * - * @param {array} arr - * @return {array} - * - * @example - * [ 1, 2, 3, 4, 5 ] => [ 4, 5, 3, 1, 2 ] - * \----/ \----/ - * head tail - * - * [ 1, 2 ] => [ 2, 1 ] - * [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 5, 6, 7, 8, 1, 2, 3, 4 ] - * - */ -function swapHeadAndTail(arr) { - let len = Math.floor(arr.length / 2); - let head = arr.splice(0, len); - let tail = arr.splice(arr.length-len, len) ; - let array = []; - array.push(...tail) ; - if(arr.length % 2) { - let middle = arr.splice(0, 1); - array.push(...middle) - } - array.push(...head); - return array; -} - - -module.exports = { - findElement: findElement, - generateOdds: generateOdds, - doubleArray: doubleArray, - getArrayOfPositives: getArrayOfPositives, - getArrayOfStrings: getArrayOfStrings, - removeFalsyValues: removeFalsyValues, - getUpperCaseStrings: getUpperCaseStrings, - getStringsLength: getStringsLength, - insertItem: insertItem, - getHead: getHead, - getTail: getTail, - toCsvText: toCsvText, - toStringList: toStringList, - toArrayOfSquares: toArrayOfSquares, - getMovingSum: getMovingSum, - getSecondItems: getSecondItems, - propagateItemsByPositionIndex: propagateItemsByPositionIndex, - get3TopItems: get3TopItems, - getPositivesCount: getPositivesCount, - sortDigitNamesByNumericOrder: sortDigitNamesByNumericOrder, - getItemsSum: getItemsSum, - getFalsyValuesCount: getFalsyValuesCount, - findAllOccurences: findAllOccurences, - sortCitiesArray: sortCitiesArray, - getIdentityMatrix: getIdentityMatrix, - getIntervalArray: getIntervalArray, - distinct: distinct, - group: group, - selectMany: selectMany, - getElementByIndexes: getElementByIndexes, - swapHeadAndTail: swapHeadAndTail -}; + ].forEach(data => { + var actual = tasks.group(data.arr, data.keySelector, data.valueSelector); + assert.deepEqual( + Array.from(data.expected), + Array.from(actual) + ); + }); + }); + + + it.optional('selectMany should return an array of child items from the specified array', function () { + [ + { + arr: [[1, 2], [3, 4], [5, 6]], + childrenSelector : x => x, + expected: [ 1, 2, 3, 4, 5, 6 ] + }, { + arr: [[11, 12, 13, 14, 15], [21, 22, ,23, 24, 25], [31, 32, 34, 35]], + childrenSelector : x => x.slice(0,2), + expected: [ 11, 12, 21, 22, 31, 32 ] + }, { + arr: ['one','two','three'], + childrenSelector: x=>x.split(''), + expected: ['o','n','e','t','w','o','t','h','r','e','e'] + } + ].forEach(data => { + var actual = tasks.selectMany(data.arr, data.childrenSelector); + assert.deepStrictEqual( + data.expected, + actual + ); + }); + }); + + + it.optional('getElementByIndexes should return an element from array by specified indexes', function () { + [ + { + arr: [ [1, 2], [3, 4], [5, 6] ], + indexes: [ 0, 0 ], + expected: 1 + }, { + arr: ['one','two','three'], + indexes: [ 2 ], + expected: 'three' + }, { + arr: [[[1,2,3]]], + indexes: [ 0, 0, 1 ], + expected: 2 + } + ].forEach(data => { + var actual = tasks.getElementByIndexes(data.arr, data.indexes); + assert.equal( + data.expected, + actual, + `getElementByIndexes(${JSON.stringify(data.arr)}, ${JSON.stringify(data.indexes)}) returns an incorrect result. Expected ${data.expected}, but actual ${actual}` + ); + }); + }); + + + it.optional('swapHeadAndTail should swap the head and tail of the array', function () { + [ + { + arr: [ 1 ], + expected: [ 1 ] + },{ + arr: [ 1, 2 ], + expected: [ 2, 1 ] + },{ + arr: [ 1, 2, 3 ], + expected: [ 3, 2, 1 ] + },{ + arr: [ 1, 2, 3, 4 ], + expected: [ 3, 4, 1, 2 ] + },{ + arr: [ 1, 2, 3, 4, 5 ], + expected: [ 4, 5, 3, 1, 2 ] + } + ].forEach(data => { + var actual = tasks.swapHeadAndTail(Array.from(data.arr)); + assert.deepEqual( + data.expected, + actual, + `The result of swaping head and tail [${data.arr}] is not correct` + ); + }); + }); + + + it.optional('Functions from 04-array-test.js should not use basic loops statements', function () { + Object.getOwnPropertyNames(tasks) + .filter(x => tasks[x] instanceof Function) + .forEach(f => { + assert( + !/([;{]\s*(for|while)\s*\()|(\.forEach\s*\()/.test(tasks[f].toString()), + `Function "${f}" should not use basic loop statements (for, while or Array.forEach)! Please use specialized array methods (Array.map, Array.reduce etc).` + ); + }); + }); + +}); From 59acfa89abef03d92ad6fef36049d61ae1360031 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:49:54 +0300 Subject: [PATCH 31/40] Tests3 --- test/03-date-tests.js | 88 +++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/test/03-date-tests.js b/test/03-date-tests.js index 8b4696763..995809b61 100644 --- a/test/03-date-tests.js +++ b/test/03-date-tests.js @@ -1,6 +1,6 @@ 'use strict'; -var assert = require('assert') ; +var assert = require('assert'); var tasks = require('../task/03-date-tasks'); it.optional = require('../extensions/it-optional'); @@ -68,22 +68,22 @@ describe('03-date-tasks', function() { endDate: new Date(2000,1,1,11,0,0), expected: '01:00:00.000' }, { - startDate: new Date(2000,1,1,10,0,0), - endDate: new Date(2000,1,1,10,30,0), - expected: '00:30:00.000' - }, { - startDate: new Date(2000,1,1,10,0,0), - endDate: new Date(2000,1,1,10,0,20), - expected: '00:00:20.000' - }, { - startDate: new Date(2000,1,1,10,0,0), - endDate: new Date(2000,1,1,10,0,0,250), - expected: '00:00:00.250' - }, { - startDate: new Date(2000,1,1,10,0,0), - endDate: new Date(2000,1,1,15,20,10,453), - expected: '05:20:10.453' - } + startDate: new Date(2000,1,1,10,0,0), + endDate: new Date(2000,1,1,10,30,0), + expected: '00:30:00.000' + }, { + startDate: new Date(2000,1,1,10,0,0), + endDate: new Date(2000,1,1,10,0,20), + expected: '00:00:20.000' + }, { + startDate: new Date(2000,1,1,10,0,0), + endDate: new Date(2000,1,1,10,0,0,250), + expected: '00:00:00.250' + }, { + startDate: new Date(2000,1,1,10,0,0), + endDate: new Date(2000,1,1,15,20,10,453), + expected: '05:20:10.453' + } ].forEach(data => { assert.equal( data.expected, @@ -92,43 +92,43 @@ describe('03-date-tasks', function() { }); }); - - + + it.optional('angleBetweenClockHands should returns the angle bettween clock hands for specified Greenwich datetime', function () { [ { date: Date.UTC(2016,3,5, 0, 0), expected: 0 }, { - date: Date.UTC(2016,3,5, 3, 0), - expected: Math.PI/2 - }, { - date: Date.UTC(2016,3,5,15, 0), - expected: Math.PI/2 - }, { - date: Date.UTC(2016,3,5, 6, 0), - expected: Math.PI - }, { - date: Date.UTC(2016,3,5,18, 0), - expected: Math.PI - }, { - date: Date.UTC(2016,3,5, 9, 0), - expected: Math.PI/2 - }, { - date: Date.UTC(2016,3,5,21, 0), - expected: Math.PI/2 - }, { - date: Date.UTC(2016,3,5,14,20), - expected: 0.8726646259971648 - }, { - date: Date.UTC(2016,3,5,23,55), - expected: 0.4799655442984406 - } + date: Date.UTC(2016,3,5, 3, 0), + expected: Math.PI/2 + }, { + date: Date.UTC(2016,3,5,15, 0), + expected: Math.PI/2 + }, { + date: Date.UTC(2016,3,5, 6, 0), + expected: Math.PI + }, { + date: Date.UTC(2016,3,5,18, 0), + expected: Math.PI + }, { + date: Date.UTC(2016,3,5, 9, 0), + expected: Math.PI/2 + }, { + date: Date.UTC(2016,3,5,21, 0), + expected: Math.PI/2 + }, { + date: Date.UTC(2016,3,5,14,20), + expected: 0.8726646259971648 + }, { + date: Date.UTC(2016,3,5,23,55), + expected: 0.4799655442984406 + } ].forEach(data => { assert.equal( tasks.angleBetweenClockHands(new Date(data.date)), data.expected, - `Incorrect result for angleBetweenClockHands(${new Date(data.date).toUTCString()}):` + `Incorrect result for angleBetweenClockHands(${new Date(data.date).toUTCString()}):` ); }); }); From 1e614f305dc479a23f665a5784b914adf1687a8b Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:51:56 +0300 Subject: [PATCH 32/40] Tests4 --- test/04-arrays-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/04-arrays-tests.js b/test/04-arrays-tests.js index d85352a56..d29e71854 100644 --- a/test/04-arrays-tests.js +++ b/test/04-arrays-tests.js @@ -847,7 +847,7 @@ describe('04-arrays-tasks', function() { data.expected, actual, `The result of swaping head and tail [${data.arr}] is not correct` - ); + ) ; }); }); From b441c942a291af5304af1bd1356bc583da251ab6 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:53:47 +0300 Subject: [PATCH 33/40] Tests5 --- test/05-regex-tests.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/05-regex-tests.js b/test/05-regex-tests.js index 2012821fc..5a95b24b0 100644 --- a/test/05-regex-tests.js +++ b/test/05-regex-tests.js @@ -1,6 +1,6 @@ 'use strict'; -var assert = require('assert') ; +var assert = require('assert'); var tasks = require('../task/05-regex-tasks'); it.optional = require('../extensions/it-optional'); @@ -28,10 +28,10 @@ describe('05-regex-tasks', function() { '0c74f13f-fa83-4c48-9b33-68921dd72463', 'The roof, the roof, the roof is on fire' ].forEach((str) => { - assert( - result.test(str) == false, + assert( + result.test(str) == false, `regex matches '${str}'` - ); + ); }); }); @@ -154,8 +154,8 @@ describe('05-regex-tasks', function() { }); assert( - !'abcdABCD1234'.match(tasks.getPasswordValidator(20)), - 'Password validator do not validate minLength restriction' + !'abcdABCD1234'.match(tasks.getPasswordValidator(20)), + 'Password validator do not validate minLength restriction' ); }); }); From a53fc106968f2cad76c5fbf71ebc7454b26784ca Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:55:28 +0300 Subject: [PATCH 34/40] Tests6 --- test/06-conditions-n-loops-tests.js | 496 ++++++++++++++-------------- 1 file changed, 248 insertions(+), 248 deletions(-) diff --git a/test/06-conditions-n-loops-tests.js b/test/06-conditions-n-loops-tests.js index eec404ef9..e8bcb2ca9 100644 --- a/test/06-conditions-n-loops-tests.js +++ b/test/06-conditions-n-loops-tests.js @@ -1,4 +1,4 @@ -'use strict' ; +'use strict'; var assert = require('assert'); var tasks = require('../task/06-conditions-n-loops-tasks'); @@ -9,8 +9,8 @@ describe('06-conditions-n-loops-tasks', function() { it.optional('getFizzBuzz should return the output value according specification', () => { [ 1, 2, 4, 7, 8, 11, 13, 14, 16, 17, 19, 22, 23, 26, 28, 29, 31, 32, 34, - 37, 38, 41, 43, 44, 47, 49, 52, 53, 56, 58, 59, 61, 62, 64, 67, 68, 71, - 73, 74, 76, 77, 79, 82, 83, 86, 88, 89, 91, 92, 94, 97, 98 + 37, 38, 41, 43, 44, 47, 49, 52, 53, 56, 58, 59, 61, 62, 64, 67, 68, 71, + 73, 74, 76, 77, 79, 82, 83, 86, 88, 89, 91, 92, 94, 97, 98 ].forEach(num => { var actual = tasks.getFizzBuzz(num); assert.equal( @@ -22,9 +22,9 @@ describe('06-conditions-n-loops-tasks', function() { [ 3, 6, 9, 12, 18, 21, 24, 27, - 33, 36, 39, 42, 48, 51, 54, 57, - 63, 66, 69, 72, 78, 81, 84, 87, - 93, 96, 99 + 33, 36, 39, 42, 48, 51, 54, 57, + 63, 66, 69, 72, 78, 81, 84, 87, + 93, 96, 99 ].forEach(num => { var actual = tasks.getFizzBuzz(num); assert.equal( @@ -97,7 +97,7 @@ describe('06-conditions-n-loops-tasks', function() { { sides: [ 3, 4, 5] , expected: true }, { sides: [ 10, 1, 1] , expected: false }, { sides: [ 10, 10, 10] , expected: true }, - ].forEach(data => { + ].forEach(data => { [[0,1,2], [0,2,1], [1,2,0], [1,0,2], [2,0,1], [2,1,0]].forEach(idx => { var actual = tasks.isTriangle( data.sides[idx[0]], @@ -116,36 +116,36 @@ describe('06-conditions-n-loops-tasks', function() { it.optional('doRectanglesOverlap should return true if rectangles overlap', () => { [ - { + { rect1: { top: 0, left: 0, width: 10, height: 10 }, rect2: { top: 5, left: 5, width: 10, height: 10 }, expected: true },{ - rect1: { top: 10, left: 10, width: 10, height: 10 }, - rect2: { top: 5, left: 5, width: 15, height: 15 }, - expected: true - },{ - rect1: { top: 10, left: 10, width: 50, height: 5 }, - rect2: { top: 5, left: 5, width: 10, height: 50 }, - expected: true - },{ - rect1: { top: 0, left: 0, width: 90, height: 90 }, - rect2: { top: 25, left: 25, width: 10, height: 10 }, - expected: true - },{ - rect1: { top: 5, left: 5, width: 20, height: 20 }, - rect2: { top: 5, left: 5, width: 40, height: 10 }, - expected: true - },{ - rect1: { top: 5, left: 5, width: 20, height: 20 }, - rect2: { top: 30, left: 5, width: 40, height: 10 }, - expected: false - },{ - rect1: { top: 0, left: 0, width: 90, height: 90 }, - rect2: { top: 25, left:100, width: 10, height: 10 }, - expected: false - } - ].forEach(data => { + rect1: { top: 10, left: 10, width: 10, height: 10 }, + rect2: { top: 5, left: 5, width: 15, height: 15 }, + expected: true + },{ + rect1: { top: 10, left: 10, width: 50, height: 5 }, + rect2: { top: 5, left: 5, width: 10, height: 50 }, + expected: true + },{ + rect1: { top: 0, left: 0, width: 90, height: 90 }, + rect2: { top: 25, left: 25, width: 10, height: 10 }, + expected: true + },{ + rect1: { top: 5, left: 5, width: 20, height: 20 }, + rect2: { top: 5, left: 5, width: 40, height: 10 }, + expected: true + },{ + rect1: { top: 5, left: 5, width: 20, height: 20 }, + rect2: { top: 30, left: 5, width: 40, height: 10 }, + expected: false + },{ + rect1: { top: 0, left: 0, width: 90, height: 90 }, + rect2: { top: 25, left:100, width: 10, height: 10 }, + expected: false + } + ].forEach(data => { assert.equal( tasks.doRectanglesOverlap(data.rect1, data.rect2), data.expected, @@ -157,36 +157,36 @@ describe('06-conditions-n-loops-tasks', function() { it.optional('isInsideCircle should return true if point lies inside of the specified circle', () => { [ - { + { circle: { center: { x: 0, y: 0 }, radius: 10 }, point: { x: 0, y: 0 }, expected: true - },{ - circle: { center: { x: 5, y: 5 }, radius: 6 }, - point: { x: 5, y: 10.99 }, - expected: true - },{ - circle: { center: { x: 0, y: 0 }, radius: 10 }, - point: { x: 0, y: 10 }, - expected: false - },{ - circle: { center: { x: 5, y: 5 }, radius: 6 }, - point: { x: 0, y: 0 }, - expected: false - },{ - circle: { center: { x: 2, y: 2 }, radius: 1 }, - point: { x: 2.8, y: 2.8 }, - expected: false - },{ - circle: { center: { x: 2, y: 2 }, radius: 4 }, - point: { x: -1, y: -1 }, - expected: false },{ - circle: { center: { x: 2, y: 2 }, radius: 4 }, - point: { x: 2, y: 6.1 }, - expected: false - } - ].forEach(data => { + circle: { center: { x: 5, y: 5 }, radius: 6 }, + point: { x: 5, y: 10.99 }, + expected: true + },{ + circle: { center: { x: 0, y: 0 }, radius: 10 }, + point: { x: 0, y: 10 }, + expected: false + },{ + circle: { center: { x: 5, y: 5 }, radius: 6 }, + point: { x: 0, y: 0 }, + expected: false + },{ + circle: { center: { x: 2, y: 2 }, radius: 1 }, + point: { x: 2.8, y: 2.8 }, + expected: false + },{ + circle: { center: { x: 2, y: 2 }, radius: 4 }, + point: { x: -1, y: -1 }, + expected: false + },{ + circle: { center: { x: 2, y: 2 }, radius: 4 }, + point: { x: 2, y: 6.1 }, + expected: false + } + ].forEach(data => { assert.equal( tasks.isInsideCircle(data.circle, data.point), data.expected, @@ -221,30 +221,30 @@ describe('06-conditions-n-loops-tasks', function() { isEndIncluded: true, expected: '[0, 1]' },{ - a: 0, - b: 1, - isStartIncluded: true, - isEndIncluded: false, - expected: '[0, 1)' - },{ - a: 0, - b: 1, - isStartIncluded: false, - isEndIncluded: true, - expected: '(0, 1]' - },{ - a: 0, - b: 1, - isStartIncluded: false, - isEndIncluded: false, - expected: '(0, 1)' - },{ - a: 5, - b: 3, - isStartIncluded: true, - isEndIncluded: true, - expected: '[3, 5]' - } + a: 0, + b: 1, + isStartIncluded: true, + isEndIncluded: false, + expected: '[0, 1)' + },{ + a: 0, + b: 1, + isStartIncluded: false, + isEndIncluded: true, + expected: '(0, 1]' + },{ + a: 0, + b: 1, + isStartIncluded: false, + isEndIncluded: false, + expected: '(0, 1)' + },{ + a: 5, + b: 3, + isStartIncluded: true, + isEndIncluded: true, + expected: '[3, 5]' + } ].forEach(data => { var actual = tasks.getIntervalString(data.a, data.b, data.isStartIncluded, data.isEndIncluded); assert.equal( @@ -354,7 +354,7 @@ describe('06-conditions-n-loops-tasks', function() { }); - it.optional('isBracketsBalanced should check the balanced brackets', () => { + it.optional('isBracketsBalanced should check the balanced brackets', () => { [ '[]', '[[][][[]]]', '[[][]]', '', '<>', '{}', '()', '<()>', '{<>}', '[{}]', '[{(<()[]{}<>>)}]', '{}<>()[]','{<>}{()}[[]](())' @@ -408,15 +408,15 @@ describe('06-conditions-n-loops-tasks', function() { pathes: ['/web/images/image1.png', '/web/images/image2.png'], expected: '/web/images/' },{ - pathes: ['/web/assets/style.css', '/web/scripts/app.js', 'home/setting.conf'], - expected: '' - },{ - pathes: ['/web/assets/style.css', '/.bin/mocha', '/read.me'], - expected: '/' - },{ - pathes: ['/web/favicon.ico', '/web-scripts/dump', '/webalizer/logs'], - expected: '/' - } + pathes: ['/web/assets/style.css', '/web/scripts/app.js', 'home/setting.conf'], + expected: '' + },{ + pathes: ['/web/assets/style.css', '/.bin/mocha', '/read.me'], + expected: '/' + },{ + pathes: ['/web/favicon.ico', '/web-scripts/dump', '/webalizer/logs'], + expected: '/' + } ].forEach(data => { var actual = tasks.getCommonDirectoryPath(data.pathes, data.n); assert.equal( @@ -447,16 +447,16 @@ describe('06-conditions-n-loops-tasks', function() { [ 7, 8, 9 ] ] },{ - m1: [ - [ 1, 2, 3] - ], - m2: [ - [ 4 ], - [ 5 ], - [ 6 ] - ], - expected : [[ 32 ]] - } + m1: [ + [ 1, 2, 3] + ], + m2: [ + [ 4 ], + [ 5 ], + [ 6 ] + ], + expected : [[ 32 ]] + } ].forEach(data => { var actual = tasks.getMatrixProduct(data.m1, data.m2); assert.deepEqual( @@ -475,130 +475,130 @@ describe('06-conditions-n-loops-tasks', function() { endDate: '2000-01-01 01:00:00.200', expected: 'a few seconds ago' }, { - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 01:00:30.000', - expected: 'a few seconds ago' - }, { - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 01:00:45.000', - expected: 'a few seconds ago' - }, { - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 01:00:45.001', - expected: 'a minute ago' - }, { - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 01:01:00.000', - expected: 'a minute ago' - }, { - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 01:01:30.000', - expected: 'a minute ago' - }, { - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 01:01:30.001', - expected: '2 minutes ago' - }, { - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 01:05:30.000', - expected: '5 minutes ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 01:45:00.000', - expected: '45 minutes ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 01:45:00.001', - expected: 'an hour ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 02:00:00.000', - expected: 'an hour ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 02:30:00.000', - expected: 'an hour ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 02:30:00.001', - expected: '2 hours ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 05:30:00.000', - expected: '4 hours ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 05:30:00.001', - expected: '5 hours ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 23:00:00.000', - expected: '22 hours ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-01 23:00:00.001', - expected: 'a day ago' - },{ - startDate: '2000-01-01 01:00:00.000', - endDate: '2000-01-02 01:00:00.000', - expected: 'a day ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-01-02 12:00:00.000', - expected: 'a day ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-01-02 12:00:00.001', - expected: '2 days ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-01-05 12:00:00.000', - expected: '4 days ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-01-26 00:00:00.000', - expected: '25 days ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-01-26 00:00:00.001', - expected: 'a month ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-02-01 00:00:00.000', - expected: 'a month ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-02-15 00:00:00.000', - expected: 'a month ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-02-16 00:00:00.000', - expected: '2 months ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-05-20 00:00:00.000', - expected: '5 months ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-12-10 00:00:00.000', - expected: '11 months ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2000-12-12 00:00:00.000', - expected: 'a year ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2001-02-15 00:00:00.001', - expected: 'a year ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2001-06-01 00:00:00.001', - expected: 'a year ago' - },{ - startDate: '2000-01-01 00:00:00.000', - endDate: '2015-02-15 00:00:00.001', - expected: '15 years ago' - } + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 01:00:30.000', + expected: 'a few seconds ago' + }, { + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 01:00:45.000', + expected: 'a few seconds ago' + }, { + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 01:00:45.001', + expected: 'a minute ago' + }, { + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 01:01:00.000', + expected: 'a minute ago' + }, { + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 01:01:30.000', + expected: 'a minute ago' + }, { + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 01:01:30.001', + expected: '2 minutes ago' + }, { + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 01:05:30.000', + expected: '5 minutes ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 01:45:00.000', + expected: '45 minutes ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 01:45:00.001', + expected: 'an hour ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 02:00:00.000', + expected: 'an hour ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 02:30:00.000', + expected: 'an hour ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 02:30:00.001', + expected: '2 hours ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 05:30:00.000', + expected: '4 hours ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 05:30:00.001', + expected: '5 hours ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 23:00:00.000', + expected: '22 hours ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-01 23:00:00.001', + expected: 'a day ago' + },{ + startDate: '2000-01-01 01:00:00.000', + endDate: '2000-01-02 01:00:00.000', + expected: 'a day ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-01-02 12:00:00.000', + expected: 'a day ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-01-02 12:00:00.001', + expected: '2 days ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-01-05 12:00:00.000', + expected: '4 days ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-01-26 00:00:00.000', + expected: '25 days ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-01-26 00:00:00.001', + expected: 'a month ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-02-01 00:00:00.000', + expected: 'a month ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-02-15 00:00:00.000', + expected: 'a month ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-02-16 00:00:00.000', + expected: '2 months ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-05-20 00:00:00.000', + expected: '5 months ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-12-10 00:00:00.000', + expected: '11 months ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2000-12-12 00:00:00.000', + expected: 'a year ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2001-02-15 00:00:00.001', + expected: 'a year ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2001-06-01 00:00:00.001', + expected: 'a year ago' + },{ + startDate: '2000-01-01 00:00:00.000', + endDate: '2015-02-15 00:00:00.001', + expected: '15 years ago' + } ].forEach(data => { var actual = tasks.timespanToHumanString(new Date(data.startDate), new Date(data.endDate)); assert.equal( @@ -630,35 +630,35 @@ describe('06-conditions-n-loops-tasks', function() { [ X, X, X ], [ O, O, ], [ O, , ] - ],[ + ],[ [ , O, O ], [ X, X, X ], [ O, , O ] - ],[ + ],[ [ , , O ], [ O, , O ], [ X, X, X ] - ],[ + ],[ [ X, , O ], [ X, , O ], [ X, O, ] - ],[ + ],[ [ O, X, O ], [ X, X, O ], [ O, X, ] - ],[ + ],[ [ O, O, X ], [ X, O, X ], [ O, X, X ] - ],[ + ],[ [ X, O, O ], [ X, X, O ], [ O, X, X ] - ],[ + ],[ [ O, O, X ], [ X, X, O ], [ X, , O ] - ] + ] ].forEach(data => { var actual = tasks.evaluateTicTacToePosition(data); assert.equal( @@ -672,35 +672,35 @@ describe('06-conditions-n-loops-tasks', function() { [ O, O, O ], [ , X, X ], [ X, , ] - ],[ + ],[ [ X, X, ], [ O, O, O ], [ X, , X ] - ],[ + ],[ [ , , ], [ X, , X ], [ O, O, O ] - ],[ + ],[ [ O, , X ], [ O, X, X ], [ O, X, ] - ],[ + ],[ [ X, O, X ], [ X, O, O ], [ O, O, X ] - ],[ + ],[ [ X, X, O ], [ X, O, O ], [ , X, O ] - ],[ + ],[ [ O, X, X ], [ X, O, X ], [ O, X, O ] - ],[ + ],[ [ X, X, O ], [ X, O, X ], [ O, , X ] - ] + ] ].forEach(data => { var actual = tasks.evaluateTicTacToePosition(data); assert.equal( @@ -714,23 +714,23 @@ describe('06-conditions-n-loops-tasks', function() { [ , , ], [ , , ], [ , , ] - ],[ + ],[ [ X, , ], [ O, O, ], [ , , X ] - ],[ + ],[ [ X, O, X ], [ X, O, X ], [ O, X, O ] - ],[ + ],[ [ X, O, X ], [ O, X, X ], [ O, X, O ] - ],[ + ],[ [ X, O, X ], [ O, , O ], [ X, O, X ] - ] + ] ].forEach(data => { var actual = tasks.evaluateTicTacToePosition(data); assert.equal( From 8cfc4d2054df12f8fe3ff8e0374e9f40cfe53eea Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 22:59:06 +0300 Subject: [PATCH 35/40] Tests7 --- test/07-yield-tests.js | 627 +++++++++++++++++++++++++++++------------ 1 file changed, 439 insertions(+), 188 deletions(-) diff --git a/test/07-yield-tests.js b/test/07-yield-tests.js index f975baff5..82210f98e 100644 --- a/test/07-yield-tests.js +++ b/test/07-yield-tests.js @@ -1,198 +1,449 @@ 'use strict'; -/******************************************************************************************** - * * - * Plese read the following tutorial before implementing tasks: * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield * - * * - ********************************************************************************************/ - - -/** - * Returns the lines sequence of "99 Bottles of Beer" song: - * - * '99 bottles of beer on the wall, 99 bottles of beer.' - * 'Take one down and pass it around, 98 bottles of beer on the wall.' - * '98 bottles of beer on the wall, 98 bottles of beer.' - * 'Take one down and pass it around, 97 bottles of beer on the wall.' - * ... - * '1 bottle of beer on the wall, 1 bottle of beer.' - * 'Take one down and pass it around, no more bottles of beer on the wall.' - * 'No more bottles of beer on the wall, no more bottles of beer.' - * 'Go to the store and buy some more, 99 bottles of beer on the wall.' - * - * See the full text at - * http://99-bottles-of-beer.net/lyrics.html - * - * NOTE: Please try to complete this task faster then original song finished: - * https://www.youtube.com/watch?v=Z7bmyjxJuVY :) - * - * - * @return {Iterable.} - * - */ -function* get99BottlesOfBeer() { - for (let bottles = 99; bottles > 2; bottles--) { - yield `${bottles} bottles of beer on the wall, ${bottles} bottles of beer.`; - yield `Take one down and pass it around, ${bottles - 1} bottles of beer on the wall.`; - } +var assert = require('assert'); +var tasks = require('../task/07-yield-tasks'); +it.optional = require('../extensions/it-optional'); - yield `2 bottles of beer on the wall, 2 bottles of beer.`; - yield `Take one down and pass it around, 1 bottle of beer on the wall.`; - - yield `1 bottle of beer on the wall, 1 bottle of beer.`; - yield `Take one down and pass it around, no more bottles of beer on the wall.`; - - yield 'No more bottles of beer on the wall, no more bottles of beer.'; - yield 'Go to the store and buy some more, 99 bottles of beer on the wall.'; -} - - -/** - * Returns the Fibonacci sequence: - * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, ... - * - * See more at: https://en.wikipedia.org/wiki/Fibonacci_number - * - * @return {Iterable.} - * - */ -function* getFibonacciSequence() { - yield 0; - yield 1; - - let last = 0; - let current = 1; - let new_val = 1; - - while (true){ - yield new_val; - last = current; - current = new_val; - new_val = last + current; - } -} - - -/** - * Traverses a tree using the depth-first strategy - * See details: https://en.wikipedia.org/wiki/Depth-first_search - * - * Each node have child nodes in node.children array. - * The leaf nodes do not have 'children' property. - * - * @params {object} root the tree root - * @return {Iterable.} the sequence of all tree nodes in depth-first order - * @example - * - * var node1 = { n:1 }, node2 = { n:2 }, node3 = { n:3 }, node4 = { n:4 }, - * node5 = { n:5 }, node6 = { n:6 }, node7 = { n:7 }, node8 = { n:8 }; - * node1.children = [ node2, node6, node7 ]; - * node2.children = [ node3, node4 ]; - * node4.children = [ node5 ]; - * node7.children = [ node8 ]; - * - * source tree (root = 1): - * 1 - * / | \ - * 2 6 7 - * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } - * 3 4 8 - * | - * 5 - * - * depthTraversalTree(node1) => node1, node2, node3, node4, node5, node6, node7, node8 - * - */ -function* depthTraversalTree(root) { - let q1 = [root]; - while (q1.length) { - let node = q1.pop(); - yield node; - if (node.children) { - q1.push(...node.children.reverse()); +describe('07-yield-tasks', function() { + + it.optional('get99BottlesOfBeer should return the sequence of song lyric lines', () => { + + var expected = [ + '99 bottles of beer on the wall, 99 bottles of beer.', + 'Take one down and pass it around, 98 bottles of beer on the wall.', + '98 bottles of beer on the wall, 98 bottles of beer.', + 'Take one down and pass it around, 97 bottles of beer on the wall.', + '97 bottles of beer on the wall, 97 bottles of beer.', + 'Take one down and pass it around, 96 bottles of beer on the wall.', + '96 bottles of beer on the wall, 96 bottles of beer.', + 'Take one down and pass it around, 95 bottles of beer on the wall.', + '95 bottles of beer on the wall, 95 bottles of beer.', + 'Take one down and pass it around, 94 bottles of beer on the wall.', + '94 bottles of beer on the wall, 94 bottles of beer.', + 'Take one down and pass it around, 93 bottles of beer on the wall.', + '93 bottles of beer on the wall, 93 bottles of beer.', + 'Take one down and pass it around, 92 bottles of beer on the wall.', + '92 bottles of beer on the wall, 92 bottles of beer.', + 'Take one down and pass it around, 91 bottles of beer on the wall.', + '91 bottles of beer on the wall, 91 bottles of beer.', + 'Take one down and pass it around, 90 bottles of beer on the wall.', + '90 bottles of beer on the wall, 90 bottles of beer.', + 'Take one down and pass it around, 89 bottles of beer on the wall.', + '89 bottles of beer on the wall, 89 bottles of beer.', + 'Take one down and pass it around, 88 bottles of beer on the wall.', + '88 bottles of beer on the wall, 88 bottles of beer.', + 'Take one down and pass it around, 87 bottles of beer on the wall.', + '87 bottles of beer on the wall, 87 bottles of beer.', + 'Take one down and pass it around, 86 bottles of beer on the wall.', + '86 bottles of beer on the wall, 86 bottles of beer.', + 'Take one down and pass it around, 85 bottles of beer on the wall.', + '85 bottles of beer on the wall, 85 bottles of beer.', + 'Take one down and pass it around, 84 bottles of beer on the wall.', + '84 bottles of beer on the wall, 84 bottles of beer.', + 'Take one down and pass it around, 83 bottles of beer on the wall.', + '83 bottles of beer on the wall, 83 bottles of beer.', + 'Take one down and pass it around, 82 bottles of beer on the wall.', + '82 bottles of beer on the wall, 82 bottles of beer.', + 'Take one down and pass it around, 81 bottles of beer on the wall.', + '81 bottles of beer on the wall, 81 bottles of beer.', + 'Take one down and pass it around, 80 bottles of beer on the wall.', + '80 bottles of beer on the wall, 80 bottles of beer.', + 'Take one down and pass it around, 79 bottles of beer on the wall.', + '79 bottles of beer on the wall, 79 bottles of beer.', + 'Take one down and pass it around, 78 bottles of beer on the wall.', + '78 bottles of beer on the wall, 78 bottles of beer.', + 'Take one down and pass it around, 77 bottles of beer on the wall.', + '77 bottles of beer on the wall, 77 bottles of beer.', + 'Take one down and pass it around, 76 bottles of beer on the wall.', + '76 bottles of beer on the wall, 76 bottles of beer.', + 'Take one down and pass it around, 75 bottles of beer on the wall.', + '75 bottles of beer on the wall, 75 bottles of beer.', + 'Take one down and pass it around, 74 bottles of beer on the wall.', + '74 bottles of beer on the wall, 74 bottles of beer.', + 'Take one down and pass it around, 73 bottles of beer on the wall.', + '73 bottles of beer on the wall, 73 bottles of beer.', + 'Take one down and pass it around, 72 bottles of beer on the wall.', + '72 bottles of beer on the wall, 72 bottles of beer.', + 'Take one down and pass it around, 71 bottles of beer on the wall.', + '71 bottles of beer on the wall, 71 bottles of beer.', + 'Take one down and pass it around, 70 bottles of beer on the wall.', + '70 bottles of beer on the wall, 70 bottles of beer.', + 'Take one down and pass it around, 69 bottles of beer on the wall.', + '69 bottles of beer on the wall, 69 bottles of beer.', + 'Take one down and pass it around, 68 bottles of beer on the wall.', + '68 bottles of beer on the wall, 68 bottles of beer.', + 'Take one down and pass it around, 67 bottles of beer on the wall.', + '67 bottles of beer on the wall, 67 bottles of beer.', + 'Take one down and pass it around, 66 bottles of beer on the wall.', + '66 bottles of beer on the wall, 66 bottles of beer.', + 'Take one down and pass it around, 65 bottles of beer on the wall.', + '65 bottles of beer on the wall, 65 bottles of beer.', + 'Take one down and pass it around, 64 bottles of beer on the wall.', + '64 bottles of beer on the wall, 64 bottles of beer.', + 'Take one down and pass it around, 63 bottles of beer on the wall.', + '63 bottles of beer on the wall, 63 bottles of beer.', + 'Take one down and pass it around, 62 bottles of beer on the wall.', + '62 bottles of beer on the wall, 62 bottles of beer.', + 'Take one down and pass it around, 61 bottles of beer on the wall.', + '61 bottles of beer on the wall, 61 bottles of beer.', + 'Take one down and pass it around, 60 bottles of beer on the wall.', + '60 bottles of beer on the wall, 60 bottles of beer.', + 'Take one down and pass it around, 59 bottles of beer on the wall.', + '59 bottles of beer on the wall, 59 bottles of beer.', + 'Take one down and pass it around, 58 bottles of beer on the wall.', + '58 bottles of beer on the wall, 58 bottles of beer.', + 'Take one down and pass it around, 57 bottles of beer on the wall.', + '57 bottles of beer on the wall, 57 bottles of beer.', + 'Take one down and pass it around, 56 bottles of beer on the wall.', + '56 bottles of beer on the wall, 56 bottles of beer.', + 'Take one down and pass it around, 55 bottles of beer on the wall.', + '55 bottles of beer on the wall, 55 bottles of beer.', + 'Take one down and pass it around, 54 bottles of beer on the wall.', + '54 bottles of beer on the wall, 54 bottles of beer.', + 'Take one down and pass it around, 53 bottles of beer on the wall.', + '53 bottles of beer on the wall, 53 bottles of beer.', + 'Take one down and pass it around, 52 bottles of beer on the wall.', + '52 bottles of beer on the wall, 52 bottles of beer.', + 'Take one down and pass it around, 51 bottles of beer on the wall.', + '51 bottles of beer on the wall, 51 bottles of beer.', + 'Take one down and pass it around, 50 bottles of beer on the wall.', + '50 bottles of beer on the wall, 50 bottles of beer.', + 'Take one down and pass it around, 49 bottles of beer on the wall.', + '49 bottles of beer on the wall, 49 bottles of beer.', + 'Take one down and pass it around, 48 bottles of beer on the wall.', + '48 bottles of beer on the wall, 48 bottles of beer.', + 'Take one down and pass it around, 47 bottles of beer on the wall.', + '47 bottles of beer on the wall, 47 bottles of beer.', + 'Take one down and pass it around, 46 bottles of beer on the wall.', + '46 bottles of beer on the wall, 46 bottles of beer.', + 'Take one down and pass it around, 45 bottles of beer on the wall.', + '45 bottles of beer on the wall, 45 bottles of beer.', + 'Take one down and pass it around, 44 bottles of beer on the wall.', + '44 bottles of beer on the wall, 44 bottles of beer.', + 'Take one down and pass it around, 43 bottles of beer on the wall.', + '43 bottles of beer on the wall, 43 bottles of beer.', + 'Take one down and pass it around, 42 bottles of beer on the wall.', + '42 bottles of beer on the wall, 42 bottles of beer.', + 'Take one down and pass it around, 41 bottles of beer on the wall.', + '41 bottles of beer on the wall, 41 bottles of beer.', + 'Take one down and pass it around, 40 bottles of beer on the wall.', + '40 bottles of beer on the wall, 40 bottles of beer.', + 'Take one down and pass it around, 39 bottles of beer on the wall.', + '39 bottles of beer on the wall, 39 bottles of beer.', + 'Take one down and pass it around, 38 bottles of beer on the wall.', + '38 bottles of beer on the wall, 38 bottles of beer.', + 'Take one down and pass it around, 37 bottles of beer on the wall.', + '37 bottles of beer on the wall, 37 bottles of beer.', + 'Take one down and pass it around, 36 bottles of beer on the wall.', + '36 bottles of beer on the wall, 36 bottles of beer.', + 'Take one down and pass it around, 35 bottles of beer on the wall.', + '35 bottles of beer on the wall, 35 bottles of beer.', + 'Take one down and pass it around, 34 bottles of beer on the wall.', + '34 bottles of beer on the wall, 34 bottles of beer.', + 'Take one down and pass it around, 33 bottles of beer on the wall.', + '33 bottles of beer on the wall, 33 bottles of beer.', + 'Take one down and pass it around, 32 bottles of beer on the wall.', + '32 bottles of beer on the wall, 32 bottles of beer.', + 'Take one down and pass it around, 31 bottles of beer on the wall.', + '31 bottles of beer on the wall, 31 bottles of beer.', + 'Take one down and pass it around, 30 bottles of beer on the wall.', + '30 bottles of beer on the wall, 30 bottles of beer.', + 'Take one down and pass it around, 29 bottles of beer on the wall.', + '29 bottles of beer on the wall, 29 bottles of beer.', + 'Take one down and pass it around, 28 bottles of beer on the wall.', + '28 bottles of beer on the wall, 28 bottles of beer.', + 'Take one down and pass it around, 27 bottles of beer on the wall.', + '27 bottles of beer on the wall, 27 bottles of beer.', + 'Take one down and pass it around, 26 bottles of beer on the wall.', + '26 bottles of beer on the wall, 26 bottles of beer.', + 'Take one down and pass it around, 25 bottles of beer on the wall.', + '25 bottles of beer on the wall, 25 bottles of beer.', + 'Take one down and pass it around, 24 bottles of beer on the wall.', + '24 bottles of beer on the wall, 24 bottles of beer.', + 'Take one down and pass it around, 23 bottles of beer on the wall.', + '23 bottles of beer on the wall, 23 bottles of beer.', + 'Take one down and pass it around, 22 bottles of beer on the wall.', + '22 bottles of beer on the wall, 22 bottles of beer.', + 'Take one down and pass it around, 21 bottles of beer on the wall.', + '21 bottles of beer on the wall, 21 bottles of beer.', + 'Take one down and pass it around, 20 bottles of beer on the wall.', + '20 bottles of beer on the wall, 20 bottles of beer.', + 'Take one down and pass it around, 19 bottles of beer on the wall.', + '19 bottles of beer on the wall, 19 bottles of beer.', + 'Take one down and pass it around, 18 bottles of beer on the wall.', + '18 bottles of beer on the wall, 18 bottles of beer.', + 'Take one down and pass it around, 17 bottles of beer on the wall.', + '17 bottles of beer on the wall, 17 bottles of beer.', + 'Take one down and pass it around, 16 bottles of beer on the wall.', + '16 bottles of beer on the wall, 16 bottles of beer.', + 'Take one down and pass it around, 15 bottles of beer on the wall.', + '15 bottles of beer on the wall, 15 bottles of beer.', + 'Take one down and pass it around, 14 bottles of beer on the wall.', + '14 bottles of beer on the wall, 14 bottles of beer.', + 'Take one down and pass it around, 13 bottles of beer on the wall.', + '13 bottles of beer on the wall, 13 bottles of beer.', + 'Take one down and pass it around, 12 bottles of beer on the wall.', + '12 bottles of beer on the wall, 12 bottles of beer.', + 'Take one down and pass it around, 11 bottles of beer on the wall.', + '11 bottles of beer on the wall, 11 bottles of beer.', + 'Take one down and pass it around, 10 bottles of beer on the wall.', + '10 bottles of beer on the wall, 10 bottles of beer.', + 'Take one down and pass it around, 9 bottles of beer on the wall.', + '9 bottles of beer on the wall, 9 bottles of beer.', + 'Take one down and pass it around, 8 bottles of beer on the wall.', + '8 bottles of beer on the wall, 8 bottles of beer.', + 'Take one down and pass it around, 7 bottles of beer on the wall.', + '7 bottles of beer on the wall, 7 bottles of beer.', + 'Take one down and pass it around, 6 bottles of beer on the wall.', + '6 bottles of beer on the wall, 6 bottles of beer.', + 'Take one down and pass it around, 5 bottles of beer on the wall.', + '5 bottles of beer on the wall, 5 bottles of beer.', + 'Take one down and pass it around, 4 bottles of beer on the wall.', + '4 bottles of beer on the wall, 4 bottles of beer.', + 'Take one down and pass it around, 3 bottles of beer on the wall.', + '3 bottles of beer on the wall, 3 bottles of beer.', + 'Take one down and pass it around, 2 bottles of beer on the wall.', + '2 bottles of beer on the wall, 2 bottles of beer.', + 'Take one down and pass it around, 1 bottle of beer on the wall.', + '1 bottle of beer on the wall, 1 bottle of beer.', + 'Take one down and pass it around, no more bottles of beer on the wall.', + 'No more bottles of beer on the wall, no more bottles of beer.', + 'Go to the store and buy some more, 99 bottles of beer on the wall.' + ]; + + var lineNo = 0; + for(let line of tasks.get99BottlesOfBeer()) { + assert.equal( + line, + expected[lineNo++], + `Text mismatch at line no ${lineNo}: ` + ); } - } -} - - -/** - * Traverses a tree using the breadth-first strategy - * See details: https://en.wikipedia.org/wiki/Breadth-first_search - * - * Each node have child nodes in node.children array. - * The leaf nodes do not have 'children' property. - * - * @params {object} root the tree root - * @return {Iterable.} the sequence of all tree nodes in breadth-first order - * @example - * source tree (root = 1): - * - * 1 - * / | \ - * 2 3 4 - * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } - * 5 6 7 - * | - * 8 - * - */ -function* breadthTraversalTree(root) { - let queue = [root]; - - while (queue.length != 0) { - let node = queue.shift(); - yield node; - - if (node.children !== undefined) { - - for (let child of node.children) { - - if (queue.length == 0 && child.children === undefined) { - yield child; - } else { - queue.push(child); - } - } + + assert.equal( + expected.length, + lineNo, + 'Lines count is incorrect:' + ); + }); + + + it.optional('getFibonacciSequence should return the Fibonacci sequence', () => { + + var expected = [ + 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, + 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, + 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169 + ]; + + var index = 0; + for(let num of tasks.getFibonacciSequence()) { + assert.equal( + num, + expected[index++], + `Sequence mismatch at index no ${index}: ` + ); + if (index>=expected.length) break; } + if (index { + + /* + * source tree (root = 1): + * + * 1 + * / | \ + * 2 6 7 + * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } + * 3 4 8 + * | + * 5 + */ + + var node1 = { n:1 }, node2 = { n:2 }, node3 = { n:3 }, node4 = { n:4 }, node5 = { n:5 }, node6 = { n:6 }, node7 = { n:7 }, node8 = { n:8 }; + node1.children = [ node2, node6, node7 ]; + node2.children = [ node3, node4 ]; + node4.children = [ node5 ]; + node7.children = [ node8 ]; + var expected = [ node1, node2, node3, node4, node5, node6, node7, node8 ]; + var index = 0; + for(let num of tasks.depthTraversalTree(node1)) { + if (index>=expected.length) assert.fail(index, expected.length,`sequence length should be equal to ${expected.length}`); + assert.equal( + num.n, + expected[index++].n, + `Sequence mismatch at index no ${index}: ` + ); + } + if (index0; i--) { + root = { n : i, children : [ root ] }; + } + return root; } -} - - -/** - * Merges two yield-style sorted sequences into the one sorted sequence. - * The result sequence consists of sorted items from source iterators. - * - * @params {Iterable.} source1 - * @params {Iterable.} source2 - * @return {Iterable.} the merged sorted sequence - * - * @example - * [ 1, 3, 5, ... ], [2, 4, 6, ... ] => [ 1, 2, 3, 4, 5, 6, ... ] - * [ 0 ], [ 2, 4, 6, ... ] => [ 0, 2, 4, 6, ... ] - * [ 1, 3, 5, ... ], [ -1 ] => [ -1, 1, 3, 5, ...] - */ -function* mergeSortedSequences(source1, source2) { - const sources = [source1(), source2()]; - let it = [sources[0].next(), sources[1].next()]; - - while (true) { - if (it[0].value >= it[1].value || it[0].value === undefined) { - yield it[1].value; - it[1] = sources[1].next(); - } else { - yield it[0].value; - it[0] = sources[0].next(); + + function createWideTree() { + var root = { n: 1, children: [] }; + for(var i=2; i<=MAX_NODE_COUNT; i++) { + root.children.push({ n: i }); } + return root; } -} + it.optional('depthTraversalTree should process a deep tree', () => { + var root = createDeepTree(); + var index = 1; + for(let node of tasks.depthTraversalTree(root)) { + if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); + assert.equal( + node.n, + index, + `Sequence mismatch at index no ${index}: ` + ); + index++; + } + if (index-1 { + var root = createWideTree(); + var index = 1; + for(let node of tasks.depthTraversalTree(root)) { + if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); + assert.equal( + node.n, + index, + `Sequence mismatch at index no ${index}: ` + ); + index++; + } + if (index-1 { + + /* + * source tree (root = 1): + * + * 1 + * / | \ + * 2 3 4 + * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } + * 5 6 7 + * | + * 8 + */ + + var node1 = { n:1 }, node2 = { n:2 }, node3 = { n:3 }, node4 = { n:4 }, node5 = { n:5 }, node6 = { n:6 }, node7 = { n:7 }, node8 = { n:8 }; + node1.children = [ node2, node3, node4 ]; + node2.children = [ node5, node6 ]; + node4.children = [ node7 ]; + node6.children = [ node8 ]; + var expected = [ node1, node2, node3, node4, node5, node6, node7, node8 ]; + var index = 0; + for(let num of tasks.breadthTraversalTree(node1)) { + if (index>=expected.length) assert.fail(null,null,`sequence length should be equal to ${expected.length}`); + assert.equal( + num.n, + expected[index++].n, + `Sequence mismatch at index no ${index}: ` + ); + } + if (index { + var root = createDeepTree(); + var index = 1; + for(let node of tasks.breadthTraversalTree(root)) { + if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); + assert.equal( + node.n, + index, + `Sequence mismatch at index no ${index}: ` + ); + index++; + } + if (index-1 { + var root = createWideTree(); + var index = 1; + for(let node of tasks.breadthTraversalTree(root)) { + if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); + assert.equal( + node.n, + index, + `Sequence mismatch at index no ${index}: ` + ); + index++; + } + if (index-1 { + const ITEMS_COUNT = 500; + + var odds = function* () { + for(var i=1; true; i+=2) yield i; + }; + var evens = function* () { + for(var i=2; true; i+=2) yield i; + }; + var expected = 1; + var count = 0; + for(let value of tasks.mergeSortedSequences(odds, evens)) { + assert.equal( + value, + expected++ + ); + count++; + if (count==ITEMS_COUNT) break; + } + assert.equal(count, ITEMS_COUNT); + + var zero = function* () { yield 0; } + expected = 0; + count = 0; + for(let value of tasks.mergeSortedSequences(zero, evens)) { + assert.equal( + value, + expected + ); + expected +=2; + count++; + if (count == ITEMS_COUNT) break; + } + assert.equal(count, ITEMS_COUNT); + + + var minus1 = function* () { yield -1; } + expected = -1; + count = 0; + for(let value of tasks.mergeSortedSequences(odds, minus1)) { + assert.equal( + value, + expected + ); + expected +=2; + count++; + if (count == ITEMS_COUNT) break; + } + assert.equal(count, ITEMS_COUNT); -module.exports = { - get99BottlesOfBeer: get99BottlesOfBeer, - getFibonacciSequence: getFibonacciSequence, - depthTraversalTree: depthTraversalTree, - breadthTraversalTree: breadthTraversalTree, - mergeSortedSequences: mergeSortedSequences -}; + }); +}); From 2b55b7e13361c4f7d7ceaf16c565eda8aa17082f Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 23:00:44 +0300 Subject: [PATCH 36/40] Tests8 --- test/08-objects-tests.js | 420 +++++++++++++++++++++++++-------------- 1 file changed, 268 insertions(+), 152 deletions(-) diff --git a/test/08-objects-tests.js b/test/08-objects-tests.js index c06c91c2f..fe7bcdac2 100644 --- a/test/08-objects-tests.js +++ b/test/08-objects-tests.js @@ -1,154 +1,270 @@ 'use strict'; -/************************************************************************************************** - * * - * Plese read the following tutorial before implementing tasks: * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object * - * * - **************************************************************************************************/ - - -/** - * Returns the rectagle object with width and height parameters and getArea() method - * - * @param {number} width - * @param {number} height - * @return {Object} - * - * @example - * var r = new Rectangle(10,20); - * console.log(r.width); // => 10 - * console.log(r.height); // => 20 - * console.log(r.getArea()); // => 200 - */ -function Rectangle(width, height) { - this.width = width; - this.height = height; - - this.getArea = () => { - return this.width * this.height - } -} - - -/** - * Returns the JSON representation of specified object - * - * @param {object} obj - * @return {string} - * - * @example - * [1,2,3] => '[1,2,3]' - * { width: 10, height : 20 } => '{"height":10,"width":20}' - */ -function getJSON(obj) { - return JSON.stringify(obj); -} - - -/** - * Returns the object of specified type from JSON representation - * - * @param {Object} proto - * @param {string} json - * @return {object} - * - * @example - * var r = fromJSON(Rectangle.prototype, '{"width":10, "height":20}'); - * - */ -function fromJSON(proto, json) { - throw new Error('Not implemented'); - /*let obj = Object.create(proto); - obj = JSON.parse(json); - return obj;*/ -} - - -/** - * Css selectors builder - * - * Each complex selector can consists of type, id, class, attribute, pseudo-class and pseudo-element selectors: - * - * element#id.class[attr]:pseudoClass::pseudoElement - * \----/\----/\----------/ - * Can be several occurences - * - * All types of selectors can be combined using the combinators ' ','+','~','>' . - * - * The task is to design a single class, independent classes or classes hierarchy and implement the functionality - * to build the css selectors using the provided cssSelectorBuilder. - * Each selector should have the stringify() method to output the string repsentation according to css specification. - * - * Provided cssSelectorBuilder should be used as facade only to create your own classes, - * for example the first method of cssSelectorBuilder can be like this: - * element: function(value) { - * return new MySuperBaseElementSelector(...)... - * }, - * - * The design of class(es) is totally up to you, but try to make it as simple, clear and readable as possible. - * - * @example - * - * var builder = cssSelectorBuilder; - * - * builder.id('main').class('container').class('editable').stringify() => '#main.container.editable' - * - * builder.element('a').attr('href$=".png"').pseudoClass('focus').stringify() => 'a[href$=".png"]:focus' - * - * builder.combine( - * builder.element('div').id('main').class('container').class('draggable'), - * '+', - * builder.combine( - * builder.element('table').id('data'), - * '~', - * builder.combine( - * builder.element('tr').pseudoClass('nth-of-type(even)'), - * ' ', - * builder.element('td').pseudoClass('nth-of-type(even)') - * ) - * ) - * ).stringify() => 'div#main.container.draggable + table#data ~ tr:nth-of-type(even) td:nth-of-type(even)' - * - * For more examples see unit tests. - */ - -const cssSelectorBuilder = { - - element: function(value) { - throw new Error('Not implemented'); - }, - - id: function(value) { - throw new Error('Not implemented'); - }, - - class: function(value) { - throw new Error('Not implemented'); - }, - - attr: function(value) { - throw new Error('Not implemented'); - }, - - pseudoClass: function(value) { - throw new Error('Not implemented'); - }, - - pseudoElement: function(value) { - throw new Error('Not implemented'); - }, - - combine: function(selector1, combinator, selector2) { - throw new Error('Not implemented'); - }, -}; - - -module.exports = { - Rectangle: Rectangle, - getJSON: getJSON, - fromJSON: fromJSON, - cssSelectorBuilder: cssSelectorBuilder -}; +var assert = require('assert'); +var tasks = require('../task/08-objects-tasks'); +it.optional = require('../extensions/it-optional'); + +describe('08-objects-tasks', function() { + + it.optional('Rectangle constructor should return the rectangle object', function () { + var rect = new tasks.Rectangle(10,20); + + assert.equal( + typeof rect, + 'object', + 'Result of Rectangle constructor should be an object' + ); + assert( + rect.hasOwnProperty('width'), + 'Result of Rectangle constructor should be an object with "width" property' + ); + assert.equal( + rect.width, + 10, + 'Result of new Rectangle(10,20) should be an object with "width" property equals to 10' + ); + assert( + rect.hasOwnProperty('height'), + 'Result of new Rectangle(10,20) should be an object with "height" property' + ); + assert.equal( + rect.width, + 10, + 'Result of new Rectangle(10,20) should be an object with "height" property equals to 20' + ); + assert.equal( + typeof rect.getArea, + 'function', + 'Result of Rectangle constructor should be an object with "getArea" method' + ); + assert.equal( + rect.getArea(), + 200, + 'Result of (new Rectangle(10,20)).getArea() should return the correct area of specified rectangle' + ); + assert.equal( + (new tasks.Rectangle(3,8)).getArea(), + 24, + 'Result of (new Rectangle(3,8)).getArea() should return the correct area of specified rectangle' + ); + }); + + + it.optional('getJSON should return the JSON representation of specified object', function () { + [ + { + obj: [ 1, 2, 3], + expected: '[1,2,3]' + },{ + obj: { height: 10, width: 20 }, + expected: '{"height":10,"width":20}' + } + ].forEach(data => { + assert.equal( + tasks.getJSON(data.obj), + data.expected + ); + }); + }); + + + it.optional('fromJSON should return the object of specified type from JSON representation', function () { + var MockType = function(a,b,c) { + this.a = a; + this.b = b; + this.c = c; + }; + + [ + { + proto: tasks.Rectangle.prototype, + json: '{ "width":10, "height":20 }', + expected: new tasks.Rectangle(10, 20) + },{ + proto: MockType.prototype, + json: '{ "a":10, "b":20, "c":30 }', + expected: new MockType(10,20,30) + } + ].forEach(data => { + var actual = tasks.fromJSON(data.proto, data.json); + assert.deepEqual( + actual, + data.expected, + 'fromJson method shoud restore all properties from json' + ); + assert.equal( + actual.__proto__, + data.expected.__proto__, + 'fromJson method shoud restore type from prototype argument' + ); + }); + }); + + + it.optional('cssSelectorBuilder should creates css selector object with stringify() method', function () { + const builder = tasks.cssSelectorBuilder; + + // Test simple selectors + assert.equal( + builder.element('div').stringify(), + 'div' + ); + assert.equal( + builder.id('nav-bar').stringify(), + '#nav-bar' + ); + assert.equal( + builder.class('warning').stringify(), + '.warning' + ); + assert.equal( + builder.attr('href$=".png"').stringify(), + '[href$=".png"]' + ); + assert.equal( + builder.pseudoClass('invalid').stringify(), + ':invalid' + ); + assert.equal( + builder.pseudoElement('first-letter').stringify(), + '::first-letter' + ); + + // Test complex selectors + assert.equal( + builder.element('li').id('main').stringify(), + 'li#main' + ); + assert.equal( + builder.element('div').class('container').stringify(), + 'div.container' + ); + assert.equal( + builder.element('div').class('container').class('clickable').stringify(), + 'div.container.clickable' + ); + assert.equal( + builder.id('main').class('container').class('editable').stringify(), + '#main.container.editable' + ); + assert.equal( + builder.element('li').id('home-menu').class('active').stringify(), + 'li#home-menu.active' + ); + assert.equal( + builder.class('container').class('nav-bar').class('navbar-inverted').stringify(), + '.container.nav-bar.navbar-inverted' + ); + assert.equal( + builder.element('a').attr('href$=".png"').pseudoClass('focus').stringify(), + 'a[href$=".png"]:focus' + ); + assert.equal( + builder.element('p').pseudoClass('first-of-type').pseudoElement('first-letter').stringify(), + 'p:first-of-type::first-letter' + ); + assert.equal( + builder.element('input').pseudoClass('focus').pseudoClass('invalid').stringify(), + 'input:focus:invalid' + ); + + // Test combined selectors + assert.equal( + builder.combine( + builder.element('p').pseudoClass('focus'), + '>', + builder.element('a').attr('href$=".png"') + ).stringify(), + 'p:focus > a[href$=".png"]' + ); + + assert.equal( + builder.combine( + builder.element('p').id('introduction'), + '~', + builder.element('img').attr('href$=".png"') + ).stringify(), + 'p#introduction ~ img[href$=".png"]' + ); + + assert.equal( + builder.combine( + builder.id('charter1').class('touch'), + '+', + builder.element('table') + ).stringify(), + '#charter1.touch + table' + ); + + assert.equal( + builder.combine( + builder.element('ul').class('animable'), + ' ', + builder.element('li').pseudoClass('nth-of-type(1)') + ).stringify(), + 'ul.animable li:nth-of-type(1)' + ); + + assert.equal( + builder.combine( + builder.element('div').id('main').class('container').class('draggable'), + '+', + builder.combine( + builder.element('table').id('data'), + '~', + builder.combine( + builder.element('tr').pseudoClass('nth-of-type(even)'), + ' ', + builder.element('td').pseudoClass('nth-of-type(even)') + ) + ) + ).stringify(), + 'div#main.container.draggable + table#data ~ tr:nth-of-type(even) td:nth-of-type(even)' + ); + + // Test validation + [ + () => builder.element('table').element('div'), + () => builder.id('id1').id('id2'), + () => builder.pseudoElement('after').pseudoElement('before'), + ].forEach(fn => { + assert.throws( + fn, + /Element, id and pseudo-element should not occur more then one time inside the selector/, + + '\nPlease throw an exception "Element, id and pseudo-element should not occur more then one time inside the selector" '+ + 'if element, id or pseudo-element occurs twice or more times' + ); + }); + + [ + () => builder.class('draggable').class('animated'), + () => builder.attr('href').attr('title'), + () => builder.pseudoClass('invalid').pseudoClass('focus'), + ].forEach(fn => { + assert.doesNotThrow( + fn, + /Element, id and pseudo-element should not occur more then one time inside the selector/ + ); + }); + + [ + () => builder.id('id').element('div'), + () => builder.class('main').id('id'), + () => builder.attr('href').class('download-link'), + () => builder.pseudoClass('hover').attr('title'), + () => builder.pseudoElement('after').pseudoClass('valid'), + () => builder.pseudoElement('after').id('id'), + ].forEach(fn => { + assert.throws( + fn, + /Selector parts should be arranged in the following order: element, id, class, attribute, pseudo-class, pseudo-element/, + + '\nPlease throw an exception "Selector parts should be arranged in the following order: element, id, class, attribute, pseudo-class, pseudo-element" '+ + 'if selector parts arranged in an invalid order.' + ); + }); + + }); + +}); From 28186ff6e0b0492dd9f5475542864fa60273b0b4 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 23:02:13 +0300 Subject: [PATCH 37/40] Tests9 --- test/09-functions-n-closures-tests.js | 392 ++++++++++++-------------- 1 file changed, 174 insertions(+), 218 deletions(-) diff --git a/test/09-functions-n-closures-tests.js b/test/09-functions-n-closures-tests.js index aaafd9401..14ee1d59a 100644 --- a/test/09-functions-n-closures-tests.js +++ b/test/09-functions-n-closures-tests.js @@ -1,221 +1,177 @@ 'use strict'; -/********************************************************************************************** - * * - * Plese read the following tutorial before implementing tasks: * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures * - * * - **********************************************************************************************/ - - -/** - * Returns the functions composition of two specified functions f(x) and g(x). - * The result of compose is to be a function of one argument, (lets call the argument x), - * which works like applying function f to the result of applying function g to x, i.e. - * getComposition(f,g)(x) = f(g(x)) - * - * @param {Function} f - * @param {Function} g - * @return {Function} - * - * @example - * getComposition(Math.sin, Math.asin)(x) => Math.sin(Math.acos(x)) - * - */ -function getComposition(f,g) { - return function (x) { - return f(g(x)); - } -} - - -/** - * Returns the math power function with the specified exponent - * - * @param {number} exponent - * @return {Function} - * - * @example - * var power2 = getPowerFunction(2); // => x^2 - * power2(2) => 4 - * power2(4) => 16 - * - * var power05 = getPowerFunction(0.5); // => x^0.5 - * power05(4) => 2 - * power05(16) => 4 - * - */ -function getPowerFunction(exponent) { - return function (number) { - return Math.pow(number, exponent); - }; -} - - -/** - * Returns the polynom function of one argument based on specified coefficients. - * See: https://en.wikipedia.org/wiki/Polynomial#Definition - * - * @params {integer} - * @return {Function} - * - * @example - * getPolynom(2,3,5) => y = 2*x^2 + 3*x + 5 - * getPolynom(1,-3) => y = x - 3 - * getPolynom(8) => y = 8 - * getPolynom() => null - */ -function getPolynom() { - var tmp = Array.from(arguments).reverse(); - return (x) => { - return tmp.reduce((prev, curr, index) => prev+curr*Math.pow(x,index),0); - }; -} - - -/** - * Memoizes passed function and returns function - * which invoked first time calls the passed function and then always returns cached result. - * - * @params {Function} func - function to memoize - * @return {Function} memoized function - * - * @example - * var memoizer = memoize(() => Math.random()); - * memoizer() => some random number (first run, evaluates the result of Math.random()) - * memoizer() => the same random number (second run, returns the previous cached result) - * ... - * memoizer() => the same random number (next run, returns the previous cached result) - */ -function memoize(func) { - let cashed = func(); - return function() { - return cashed - } -} - - -/** - * Returns the function trying to call the passed function and if it throws, - * retrying it specified number of attempts. - * - * @param {Function} func - * @param {number} attempts - * @return {Function} - * - * @example - * var attempt = 0, retryer = retry(() => { - * if (++attempt % 2) throw new Error('test'); - * else return attempt; - * }, 2); - * retryer() => 2 - */ -function retry(func, attempts) { - return()=>{ - for (var i = 0; i < attempts;){ - try{ - return func(); - } catch(err){ - i += 1; - } +var assert = require('assert'); +var tasks = require('../task/09-functions-n-closures-tasks'); +it.optional = require('../extensions/it-optional'); + +describe('09-functions-n-closures-tasks', function() { + + it.optional('getComposition should return the composition of two functions', () => { + [ + { f: Math.sin, g: Math.asin, arg: 0, result: 0 }, + { f: x=>x+1, g: x=>x+1, arg: 1, result: 3 }, + { f: x=>x*x, g: x=>x+2, arg: 5, result: 49 }, + ].forEach(data => { + var actual = tasks.getComposition(data.f, data.g); + assert( + actual(data.arg)==data.result + ) + }); + }); + + + it.optional('getPowerFunction should return the math power function using the specified exponent', () => { + + var power2 = tasks.getPowerFunction(2); + for(var i=0; i<10; i++) { + assert.equal(power2(i), Math.pow(i,2)); } - return i; - }; -} - - -/** - * Returns the logging wrapper for the specified method, - * Logger has to log the start and end of calling the specified function. - * Logger has to log the arguments of invoked function. - * The fromat of output log is: - * (, ,...,) starts - * (, ,...,) ends - * - * - * @param {Function} func - * @param {Function} logFunc - function to output log with single string argument - * @return {Function} - * - * @example - * - * var cosLogger = logger(Math.cos, console.log); - * var result = cosLogger(Math.PI)); // -1 - * - * log from console.log: - * cos(3.141592653589793) starts - * cos(3.141592653589793) ends - * - */ -function logger(func, logFunc) { - return function(){ - let tmp = Array.from(arguments); - let callStr = JSON.stringify(tmp); - callStr = callStr.substr(1, callStr.length - 2); - callStr = `${func.name}(${callStr})`; - logFunc(callStr + " starts"); - let res = func.apply(null, tmp); - logFunc(callStr + " ends"); - return res; - } -} - - -/** - * Return the function with partial applied arguments - * - * @param {Function} fn - * @return {Function} - * - * @example - * var fn = function(x1,x2,x3,x4) { return x1 + x2 + x3 + x4; }; - * partialUsingArguments(fn, 'a')('b','c','d') => 'abcd' - * partialUsingArguments(fn, 'a','b')('c','d') => 'abcd' - * partialUsingArguments(fn, 'a','b','c')('d') => 'abcd' - * partialUsingArguments(fn, 'a','b','c','d')() => 'abcd' - */ -function partialUsingArguments(fn) { - var applyArgs = Array.prototype.slice.call(arguments, 1); - return function() { - return fn.apply(null, Array.prototype.concat(applyArgs, Array.prototype.slice.call(arguments))); - }; -} - - -/** - * Returns the id generator function that returns next integer starting from specified number every time when invoking. - * - * @param {Number} startFrom - * @return {Function} - * - * @example - * var getId4 = getIdGenerator(4); - * var getId10 = gerIdGenerator(10); - * getId4() => 4 - * getId10() => 10 - * getId4() => 5 - * getId4() => 6 - * getId4() => 7 - * getId10() => 11 - */ -function getIdGeneratorFunction(startFrom) { - var curr = startFrom; - return function() { - return startFrom++; - } -} - - -module.exports = { - getComposition: getComposition, - getPowerFunction: getPowerFunction, - getPolynom: getPolynom, - memoize: memoize, - retry: retry, - logger: logger, - partialUsingArguments: partialUsingArguments, - getIdGeneratorFunction: getIdGeneratorFunction, -}; + + var power05 = tasks.getPowerFunction(0.5); + for(var i=0; i<10; i++) { + assert.equal(power05(i), Math.pow(i, 0.5)); + } + }); + + + it.optional('getPolynom should return the polynom with specified coefficients', () => { + [ + { + polynom: tasks.getPolynom(2,3,5), + results: [ {x: 0, y: 5}, {x: 2, y: 19}, {x: 3, y: 32} ] + },{ + polynom: tasks.getPolynom(1,-3), + results: [ {x:0, y: -3}, {x:2, y: -1}, {x:5, y:2} ] + },{ + polynom: tasks.getPolynom(8), + results: [ {x:0, y:8}, {x:2, y:8}, {x:5, y:8} ] + } + ].forEach(data => { + data.results.forEach(test => { + assert( + test.y == data.polynom(test.x) + ) + }); + }); + }); + + + it.optional('memoize method should cache the result of function', () => { + var numberOfCalls = 0; + var fn = function() { + numberOfCalls++; + return Math.random(); + } + var memoizer = tasks.memoize(fn); + var expected = memoizer(); + assert.equal(numberOfCalls, 1, 'memoize result should evaluate the specified function at first call'); + for(var i=0; i<10; i++) { + let actual = memoizer(); + assert.equal(actual, expected, 'memoize result should return the cached value at second and next calls'); + assert.equal(numberOfCalls, 1, 'memoize result should not evaluate the specified function at second and next calls'); + } + }); + + + it.optional('retry method should try to evaluate the specified function several times', () => { + var maxAttemps = 3; + var attemps = 0; + var expected = 'expected'; + + var fn = function() { + if (++attemps { + var log = ''; + + var logFunc = (text) => ( log += text + '\n'); + var cosLogger = tasks.logger(Math.cos, logFunc); + + var actual = cosLogger(Math.PI); + + assert.equal(actual, -1, 'logger function should return the original result from specified function'); + assert.equal( + log, + 'cos(3.141592653589793) starts\n' + +'cos(3.141592653589793) ends\n', + 'logger function shoud log the start and end of the specified function'); + }); + + + it.optional('logger method should log start and end of call of the specified function', () => { + var isCalling = false; + var log = ''; + + var fn = function testLogger(param, index) { + assert.equal( + log, + 'testLogger(["expected","test",1],0) starts\n', + 'logger function shoud log the start of specified function before calling' + ); + isCalling = true; + return param[index]; + } + + var logFunc = (text) => ( log += text + '\n'); + var logger = tasks.logger(fn, logFunc); + + var actual = logger(["expected", "test", 1], 0); + + assert.equal(isCalling, true, 'logger function should call the specified function'); + assert.equal(actual, 'expected', 'logger function should return the original result from specified function'); + assert.equal( + log, + 'testLogger(["expected","test",1],0) starts\n' + +'testLogger(["expected","test",1],0) ends\n', + 'logger function shoud log the end of specified function after calling'); + }); + + + it.optional('partialUsingArguments should return the function with partial applied arguments', () => { + const fn = (x1,x2,x3,x4) => x1+x2+x3+x4; + assert.equal( + tasks.partialUsingArguments(fn, 'a')('b','c','d'), + 'abcd', + "partialUsingArguments(fn, 'a')('b','c','d')' should return 'abcd'" + ); + assert.equal( + tasks.partialUsingArguments(fn, 'a','b')('c','d'), + 'abcd', + "partialUsingArguments(fn, 'a','b')('c','d')' should return 'abcd'" + ); + assert.equal( + tasks.partialUsingArguments(fn, 'a','b','c')('d'), + 'abcd', + "partialUsingArguments(fn, 'a','b','c')('d') should return 'abcd'" + ); + assert.equal( + tasks.partialUsingArguments(fn, 'a','b','c','d')(), + 'abcd', + "partialUsingArguments(fn, 'a','b','c','d')()' should return 'abcd'" + ); + }); + + + it.optional('getIdGeneratorFunction should return the id generator function', () => { + + var f0 = tasks.getIdGeneratorFunction(0); + for(var i=0; i<1000; i++) { + assert.equal(f0(), i); + } + + var f10 = tasks.getIdGeneratorFunction(10); + var f20 = tasks.getIdGeneratorFunction(20); + for(var i=0; i<1000; i++) { + assert.equal(f10(), 10+i); + assert.equal(f20(), 20+i); + } + }); + +}); From 8f5ff19c2031ac06c198fbd79aabeb3a8dc73fc0 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 23:03:39 +0300 Subject: [PATCH 38/40] Tests10 --- test/10-katas-1-tests.js | 470 ++++++++++++++++++--------------------- 1 file changed, 213 insertions(+), 257 deletions(-) diff --git a/test/10-katas-1-tests.js b/test/10-katas-1-tests.js index 74722ca2a..16ade7b42 100644 --- a/test/10-katas-1-tests.js +++ b/test/10-katas-1-tests.js @@ -1,269 +1,225 @@ 'use strict'; -/** - * Returns the array of 32 compass points and heading. - * See details here: - * https://en.wikipedia.org/wiki/Points_of_the_compass#32_cardinal_points - * - * @return {array} - * - * Example of return : - * [ - * { abbreviation : 'N', azimuth : 0.00 , - * { abbreviation : 'NbE', azimuth : 11.25 }, - * { abbreviation : 'NNE', azimuth : 22.50 }, - * ... - * { abbreviation : 'NbW', azimuth : 348.75 } - * ] - */ -function createCompassPoints() { - var sides = ['N', 'E', 'S', 'W'], // use array of cardinal directions only! - res = []; +var assert = require('assert'); +var tasks = require('../task/10-katas-1-tasks'); +it.optional = require('../extensions/it-optional'); - function getDoubleSide(side1, side2) { - switch (side1) { - case 'E': - return 'SE'; - case 'W': - return 'NW'; - default: - return side1 + side2; - } - } +describe('10-katas-1-tasks', function() { - for (var i = 0, currIndex, curr, next, newObj; i < 32; i++) { - newObj = {}; - currIndex = Math.trunc(i / 8); - curr = sides[currIndex]; - // prev = sides[curr > 0 ? curr - 1 : sides.length - 1]; - next = sides[currIndex < sides.length - 1 ? currIndex + 1 : 0]; - // console.log(curr,next); - switch (i % 8) { - case 0: - newObj['abbreviation'] = curr; - break; - case 1: - newObj['abbreviation'] = `${curr}b${next}`; - break; - case 2: - newObj['abbreviation'] = `${curr}${getDoubleSide(curr, next)}`; - break; - case 3: - newObj['abbreviation'] = `${getDoubleSide(curr, next)}b${curr}`; - break; - case 4: - newObj['abbreviation'] = getDoubleSide(curr, next); - break; - case 5: - newObj['abbreviation'] = `${getDoubleSide(curr, next)}b${next}`; - break; - case 6: - newObj['abbreviation'] = `${next}${getDoubleSide(curr, next)}`; - break; - case 7: - newObj['abbreviation'] = `${next}b${curr}`; - break; - } - newObj['azimuth'] = 11.25 * i; - res.push(newObj); - } - return res; -} + it.optional('createCompassPoints should return the 32 compass points', () => { + var expected = [ + { abbreviation : 'N', azimuth : 0.00 }, + { abbreviation : 'NbE', azimuth : 11.25 }, + { abbreviation : 'NNE', azimuth : 22.50 }, + { abbreviation : 'NEbN', azimuth : 33.75 }, -/** - * Expand the braces of the specified string. - * See https://en.wikipedia.org/wiki/Bash_(Unix_shell)#Brace_expansion - * - * In the input string, balanced pairs of braces containing comma-separated substrings - * represent alternations that specify multiple alternatives which are to appear at that position in the output. - * - * @param {string} str - * @return {Iterable.} - * - * NOTE: The order of output string does not matter. - * - * Example: - * '~/{Downloads,Pictures}/*.{jpg,gif,png}' => '~/Downloads/*.jpg', - * '~/Downloads/*.gif' - * '~/Downloads/*.png', - * '~/Pictures/*.jpg', - * '~/Pictures/*.gif', - * '~/Pictures/*.png' - * - * 'It{{em,alic}iz,erat}e{d,}, please.' => 'Itemized, please.', - * 'Itemize, please.', - * 'Italicized, please.', - * 'Italicize, please.', - * 'Iterated, please.', - * 'Iterate, please.' - * - * 'thumbnail.{png,jp{e,}g}' => 'thumbnail.png' - * 'thumbnail.jpeg' - * 'thumbnail.jpg' - * - * 'nothing to do' => 'nothing to do' - */ -function* expandBraces(str) { - var search = '', - startIndex = -1, endIndex = -1, stack = [], tmpstr = []; - startIndex = str.indexOf('{'); - function outerSplit (string, symbol) { - var res = [], currStart = 0, stack = []; - for (var i = 0; i < string.length; i++) { - if (string.charAt(i) == '{') { - stack.push(0); - } else { - if (string.charAt(i) == '}') { - stack.pop(); - } else { - if (string.charAt(i) == symbol && stack.length == 0) { - res.push(string.slice(currStart, i)); - currStart = i + 1; - } - } - } - } - res.push(string.slice(currStart)) - return res; - } - if(startIndex != -1){ - for (endIndex = startIndex + 1; endIndex < str.length; endIndex++){ - if (str.charAt(endIndex) === '}') { - if (stack.length === 0) { - break; - } else { - stack.pop(); - } - } - if(str.charAt(endIndex) === '{') { - stack.push(0); - } - } - search = str.slice(startIndex, endIndex + 1); - } - if (search == '') { - yield str; - } else { - // tmpstr = search.slice(1,-1).split(','); - tmpstr = outerSplit(search.slice(1,-1), ','); - for (var i = 0; i < tmpstr.length; i++) { - yield * expandBraces(str.replace(search, tmpstr[i])); - } - } -} + { abbreviation : 'NE', azimuth : 45.00 }, + { abbreviation : 'NEbE', azimuth : 56.25 }, + { abbreviation : 'ENE', azimuth : 67.50 }, + { abbreviation : 'EbN', azimuth : 78.75 }, -/** - * Returns the ZigZag matrix - * - * The fundamental idea in the JPEG compression algorithm is to sort coefficient of given image by zigzag path and encode it. - * In this task you are asked to implement a simple method to create a zigzag square matrix. - * See details at https://en.wikipedia.org/wiki/JPEG#Entropy_coding - * and zigzag path here: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/JPEG_ZigZag.svg/220px-JPEG_ZigZag.svg.png - * - * @param {number} n - matrix dimension - * @return {array} n x n array of zigzag path - * - * @example - * 1 => [[0]] - * - * 2 => [[ 0, 1 ], - * [ 2, 3 ]] - * - * [[ 0, 1, 5 ], - * 3 => [ 2, 4, 6 ], - * [ 3, 7, 8 ]] - * - * [[ 0, 1, 5, 6 ], - * 4 => [ 2, 4, 7,12 ], - * [ 3, 8,11,13 ], - * [ 9,10,14,15 ]] - * - */ -function getZigZagMatrix(n) { - let res = new Array(n).fill().map(()=> new Array(n).fill()); - let i = 0, j = 0; - let d = -1; - let start = 0, end = n*n - 1; - do { - res[i][j] = start++; - res[n - i - 1][n - j - 1] = end--; - i += d; - j -= d; - if (i < 0){ - i++; - d = -d; - } else if (j < 0){ - j++; - d = -d; - } - } while (start < end); - if (start === end) - res[i][j] = start; - return res; -} + { abbreviation : 'E', azimuth : 90.00 }, + { abbreviation : 'EbS', azimuth : 101.25 }, + { abbreviation : 'ESE', azimuth : 112.50 }, + { abbreviation : 'SEbE', azimuth : 123.75 }, + { abbreviation : 'SE', azimuth : 135.00 }, + { abbreviation : 'SEbS', azimuth : 146.25 }, + { abbreviation : 'SSE', azimuth : 157.50 }, + { abbreviation : 'SbE', azimuth : 168.75 }, -/** - * Returns true if specified subset of dominoes can be placed in a row accroding to the game rules. - * Dominoes details see at: https://en.wikipedia.org/wiki/Dominoes - * - * Each domino tile presented as an array [x,y] of tile value. - * For example, the subset [1, 1], [2, 2], [1, 2] can be arranged in a row (as [1, 1] followed by [1, 2] followed by [2, 2]), - * while the subset [1, 1], [0, 3], [1, 4] can not be arranged in one row. - * NOTE that as in usual dominoes playing any pair [i, j] can also be treated as [j, i]. - * - * @params {array} dominoes - * @return {bool} - * - * @example - * - * [[0,1], [1,1]] => true - * [[1,1], [2,2], [1,5], [5,6], [6,3]] => false - * [[1,3], [2,3], [1,4], [2,4], [1,5], [2,5]] => true - * [[0,0], [0,1], [1,1], [0,2], [1,2], [2,2], [0,3], [1,3], [2,3], [3,3]] => false - * - */ -function canDominoesMakeRow(dominoes) { - return dominoes.map(x => x[0] + x[1]).reduce((prev, cur) => prev + cur) %2 != 0; -} + { abbreviation : 'S', azimuth : 180.00 }, + { abbreviation : 'SbW', azimuth : 191.25 }, + { abbreviation : 'SSW', azimuth : 202.50 }, + { abbreviation : 'SWbS', azimuth : 213.75 }, + { abbreviation : 'SW', azimuth : 225.00 }, + { abbreviation : 'SWbW', azimuth : 236.25 }, + { abbreviation : 'WSW', azimuth : 247.50 }, + { abbreviation : 'WbS', azimuth : 258.75 }, -/** - * Returns the string expression of the specified ordered list of integers. - * - * A format for expressing an ordered list of integers is to use a comma separated list of either: - * - individual integers - * - or a range of integers denoted by the starting integer separated from the end integer in the range by a dash, '-'. - * (The range includes all integers in the interval including both endpoints) - * The range syntax is to be used only for, and for every range that expands to more than two values. - * - * @params {array} nums - * @return {bool} - * - * @example - * - * [ 0, 1, 2, 3, 4, 5 ] => '0-5' - * [ 1, 4, 5 ] => '1,4,5' - * [ 0, 1, 2, 5, 7, 8, 9] => '0-2,5,7-9' - * [ 1, 2, 4, 5] => '1,2,4,5' - */ -function extractRanges(nums) { - for(var i = 0; i < nums.length; i++){ - var j = i; - while(nums[j] - nums[j + 1] == -1) { - j++; - } - if(j != i && j - i > 1) { - nums.splice(i, j - i + 1, nums[i] + '-' + nums[j]); + { abbreviation : 'W', azimuth : 270.00 }, + { abbreviation : 'WbN', azimuth : 281.25 }, + { abbreviation : 'WNW', azimuth : 292.50 }, + { abbreviation : 'NWbW', azimuth : 303.75 }, + + { abbreviation : 'NW', azimuth : 315.00 }, + { abbreviation : 'NWbN', azimuth : 326.25 }, + { abbreviation : 'NNW', azimuth : 337.50 }, + { abbreviation : 'NbW', azimuth : 348.75 } + + ]; + + assert.deepEqual( + tasks.createCompassPoints(), + expected + ); + + }); + + + it.optional('expandBraces should expand the braces from pattern string', () => { + [ + { + str: '~/{Downloads,Pictures}/*.{jpg,gif,png}', + result : [ + '~/Downloads/*.gif', + '~/Downloads/*.jpg', + '~/Downloads/*.png', + '~/Pictures/*.gif', + '~/Pictures/*.jpg', + '~/Pictures/*.png' + ] + }, { + str: 'It{{em,alic}iz,erat}e{d,}, please.', + result : [ + 'Italicize, please.', + 'Italicized, please.', + 'Itemize, please.', + 'Itemized, please.', + 'Iterate, please.', + 'Iterated, please.' + ] + },{ + str: 'thumbnail.{png,jp{e,}g}', + result : [ + 'thumbnail.jpeg', + 'thumbnail.jpg', + 'thumbnail.png' + ] + },{ + str: 'nothing to do', + result : [ + 'nothing to do' + ] } - } - return nums.join(); -} + ].forEach(data => { + var actual = Array.from(tasks.expandBraces(data.str)); + actual.sort(); + assert.deepEqual( + actual, + data.result, + `'${data.str}' have not expanded correctly:` + ); + }); + }); + + + it.optional('getZigZagMatrix should create a square matrix with zigzag path', () => { + [ + [ + [0] + ],[ + [ 0, 1 ], + [ 2, 3 ] + ],[ + [ 0, 1, 5 ], + [ 2, 4, 6 ], + [ 3, 7, 8 ] + ],[ + [ 0, 1, 5, 6 ], + [ 2, 4, 7, 12 ], + [ 3, 8, 11, 13 ], + [ 9, 10, 14, 15 ] + ],[ + [ 0, 1, 5, 6, 14 ], + [ 2, 4, 7, 13, 15 ], + [ 3, 8, 12, 16, 21 ], + [ 9, 11, 17, 20, 22 ], + [ 10, 18, 19, 23, 24 ], + ],[ + [ 0, 1, 5, 6, 14, 15 ], + [ 2, 4, 7, 13, 16, 25 ], + [ 3, 8, 12, 17, 24, 26 ], + [ 9, 11, 18, 23, 27, 32 ], + [ 10, 19, 22, 28, 31, 33 ], + [ 20, 21, 29, 30, 34, 35 ], + ],[ + [ 0, 1, 5, 6, 14, 15, 27 ], + [ 2, 4, 7, 13, 16, 26, 28 ], + [ 3, 8, 12, 17, 25, 29, 38 ], + [ 9, 11, 18, 24, 30, 37, 39 ], + [ 10, 19, 23, 31, 36, 40, 45 ], + [ 20, 22, 32, 35, 41, 44, 46 ], + [ 21, 33, 34, 42, 43, 47, 48 ], + ] + ].forEach(data => { + var actual = tasks.getZigZagMatrix(data.length); + assert.deepEqual( + actual, + data, + `Zigzag matrix of ${data.length} size has not been produced correctly:` + ); + }); + }); + + + it.optional('canDominoesMakeRow should answer if specified subset of dominoes can be arranged in a row', () => { + [ + [ + [0,1], [1,1] + ],[ + [1,3], [2,3], [1,4], [2,4], [1,5], [2,5] + ],[ + [1,1], [1,2], [2,3], [2,5], [2,6], [3,6], [5,6], [6,6] + ] + ].forEach(data => { + var actual = tasks.canDominoesMakeRow(data); + assert.equal( + actual, + true, + `[${data.join('],[')}] can be arrangement in a row` + ); + }); + + + [ + [ + [0,1], [2,3] + ],[ + [1,1], [2,2], [1,5], [5,6], [6,3] + ],[ + [0,0], [0,1], [0,2], [0,3], [1,1], [1,2], [1,3], [2,2], [2,3], [3,3] + ] + ].forEach(data => { + var actual = tasks.canDominoesMakeRow(data); + assert.equal( + actual, + false, + `[${data.join('],[')}] can't be arrangement in a row` + ); + }); + + }); + + + it.optional('extractRanges should return string expression of ordered list of integers', () => { + [ + { + nums: [ 0, 1, 2, 3, 4, 5 ], + result: '0-5' + },{ + nums: [ 1, 4, 5 ], + result: '1,4,5' + },{ + nums: [ 0, 1, 2, 5, 7, 8, 9], + result: '0-2,5,7-9' + },{ + nums: [ 1, 2, 4, 5], + result: '1,2,4,5' + },{ + nums: [ 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39 ], + result: '0-2,4,6-8,11,12,14-25,27-33,35-39' + }, + ].forEach(data => { + var actual = tasks.extractRanges(data.nums); + assert.equal( + actual, + data.result, + `[${data.nums}] have not expanded correctly:` + ); + }); + }); -module.exports = { - createCompassPoints : createCompassPoints, - expandBraces : expandBraces, - getZigZagMatrix : getZigZagMatrix, - canDominoesMakeRow : canDominoesMakeRow, - extractRanges : extractRanges -}; +}); From f649b613268119e41d4b1a75777cfe8254f6c2ad Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 23:04:54 +0300 Subject: [PATCH 39/40] Tests11 --- test/11-katas-2-tests.js | 602 +++++++++++++++++++++------------------ 1 file changed, 321 insertions(+), 281 deletions(-) diff --git a/test/11-katas-2-tests.js b/test/11-katas-2-tests.js index e31289281..7da2283a4 100644 --- a/test/11-katas-2-tests.js +++ b/test/11-katas-2-tests.js @@ -1,303 +1,343 @@ 'use strict'; -/** - * Returns the bank account number parsed from specified string. - * - * You work for a bank, which has recently purchased an ingenious machine to assist in reading letters and faxes sent in by branch offices. - * The machine scans the paper documents, and produces a string with a bank account that looks like this: - * - * _ _ _ _ _ _ _ - * | _| _||_||_ |_ ||_||_| - * ||_ _| | _||_| ||_| _| - * - * Each string contains an account number written using pipes and underscores. - * Each account number should have 9 digits, all of which should be in the range 0-9. - * - * Your task is to write a function that can take bank account string and parse it into actual account numbers. - * - * @param {string} bankAccount - * @return {number} - * - * Example of return : - * - * ' _ _ _ _ _ _ _ \n'+ - * ' | _| _||_||_ |_ ||_||_|\n'+ => 123456789 - * ' ||_ _| | _||_| ||_| _|\n' - * - * ' _ _ _ _ _ _ _ _ _ \n'+ - * '| | _| _|| ||_ |_ ||_||_|\n'+ => 23056789 - * '|_||_ _||_| _||_| ||_| _|\n', - * - * ' _ _ _ _ _ _ _ _ _ \n'+ - * '|_| _| _||_||_ |_ |_||_||_|\n'+ => 823856989 - * '|_||_ _||_| _||_| _||_| _|\n', - * - */ -function parseBankAccount(bankAccount) { - let Arrtmp = bankAccount.split('\n'); - let accNum = 0; - let numIfDig = 9; - for (let i = 0; i < numIfDig; i++){ - let tmpd; - let k = i*3; - if (Arrtmp[2][k+2] === ' '){ - tmpd = 2; - } else if (Arrtmp[1][k+2] === ' '){ - if (Arrtmp[2][k] === ' '){ - tmpd = 5; - } else { - tmpd = 6; - } - } else if (Arrtmp[2][k] === '|'){ - if (Arrtmp[1][k+1] === ' '){ - tmpd = 0; - } else { - tmpd = 8; - } - } else if (Arrtmp[0][k+1] === ' '){ - if (Arrtmp[1][k+1] === ' '){ - tmpd = 1; - } else { - tmpd = 4; - } - } else if (Arrtmp[1][k+1] === '_'){ - if (Arrtmp[1][k] === '|'){ - tmpd = 9; - } else { - tmpd = 3; - } - } else{ - tmpd = 7; - } - accNum += tmpd * Math.pow(10, numIfDig - i - 1); - } - return accNum; -} - +var assert = require('assert'); +var tasks = require('../task/11-katas-2-tasks'); +it.optional = require('../extensions/it-optional'); -/** - * Returns the string, but with line breaks inserted at just the right places to make sure that no line is longer than the specified column number. - * Lines can be broken at word boundaries only. - * - * @param {string} text - * @param {number} columns - * @return {Iterable.} - * - * @example : - * - * 'The String global object is a constructor for strings, or a sequence of characters.', 26 => 'The String global object', - * 'is a constructor for', - * 'strings, or a sequence of', - * 'characters.' - * - * 'The String global object is a constructor for strings, or a sequence of characters.', 12 => 'The String', - * 'global', - * 'object is a', - * 'constructor', - * 'for strings,', - * 'or a', - * 'sequence of', - * 'characters.' - */ -function* wrapText(text, columns) { - var words = text.split(' '), curr = ''; - for (var i = 0; i < words.length; i++) { - if (curr.length + words[i].length <= columns) { - curr += words[i] + " "; - } else { - yield curr.trim(); - curr = words[i] + " "; - } - } - if (curr != '') { - yield curr.trim(); - } -} +describe('11-katas-2-tasks', function() { + it.optional('parseBankAccount should return the bank account number from the specified string', () => { + [ + { + text: ' _ _ _ _ _ _ _ _ _ \n'+ + '| || || || || || || || || |\n'+ + '|_||_||_||_||_||_||_||_||_|\n', + result: 0 + },{ + text: ' \n'+ + ' | | | | | | | | |\n'+ + ' | | | | | | | | |\n', + result: 111111111, -/** - * Returns the rank of the specified poker hand. - * See the ranking rules here: https://en.wikipedia.org/wiki/List_of_poker_hands. - * - * @param {array} hand - * @return {PokerRank} rank - * - * @example - * [ '4♥','5♥','6♥','7♥','8♥' ] => PokerRank.StraightFlush - * [ 'A♠','4♠','3♠','5♠','2♠' ] => PokerRank.StraightFlush - * [ '4♣','4♦','4♥','4♠','10♥' ] => PokerRank.FourOfKind - * [ '4♣','4♦','5♦','5♠','5♥' ] => PokerRank.FullHouse - * [ '4♣','5♣','6♣','7♣','Q♣' ] => PokerRank.Flush - * [ '2♠','3♥','4♥','5♥','6♥' ] => PokerRank.Straight - * [ '2♥','4♦','5♥','A♦','3♠' ] => PokerRank.Straight - * [ '2♥','2♠','2♦','7♥','A♥' ] => PokerRank.ThreeOfKind - * [ '2♥','4♦','4♥','A♦','A♠' ] => PokerRank.TwoPairs - * [ '3♥','4♥','10♥','3♦','A♠' ] => PokerRank.OnePair - * [ 'A♥','K♥','Q♥','2♦','3♠' ] => PokerRank.HighCard - */ -const PokerRank = { - StraightFlush: 8, - FourOfKind: 7, - FullHouse: 6, - Flush: 5, - Straight: 4, - ThreeOfKind: 3, - TwoPairs: 2, - OnePair: 1, - HighCard: 0 -} + },{ + text: ' _ _ _ _ _ _ _ _ _ \n'+ + ' _| _| _| _| _| _| _| _| _|\n'+ + '|_ |_ |_ |_ |_ |_ |_ |_ |_ \n', + result: 222222222 + },{ + text: ' _ _ _ _ _ _ _ _ _ \n'+ + ' _| _| _| _| _| _| _| _| _|\n'+ + ' _| _| _| _| _| _| _| _| _|\n', + result: 333333333 + },{ + text: ' \n'+ + '|_||_||_||_||_||_||_||_||_|\n'+ + ' | | | | | | | | |\n', + result: 444444444 + },{ + text: ' _ _ _ _ _ _ _ _ _ \n'+ + '|_ |_ |_ |_ |_ |_ |_ |_ |_ \n'+ + ' _| _| _| _| _| _| _| _| _|\n', + result: 555555555 + },{ + text: ' _ _ _ _ _ _ _ _ _ \n'+ + '|_ |_ |_ |_ |_ |_ |_ |_ |_ \n'+ + '|_||_||_||_||_||_||_||_||_|\n', + result: 666666666 + },{ + text: ' _ _ _ _ _ _ _ _ _ \n'+ + ' | | | | | | | | |\n'+ + ' | | | | | | | | |\n', + result: 777777777 + },{ + text: ' _ _ _ _ _ _ _ _ _ \n'+ + '|_||_||_||_||_||_||_||_||_|\n'+ + '|_||_||_||_||_||_||_||_||_|\n', + result: 888888888 + },{ + text: ' _ _ _ _ _ _ _ _ _ \n'+ + '|_||_||_||_||_||_||_||_||_|\n'+ + ' _| _| _| _| _| _| _| _| _|\n', + result: 999999999 + },{ + text: ' _ _ _ _ _ _ _ \n'+ + ' | _| _||_||_ |_ ||_||_|\n'+ + ' ||_ _| | _||_| ||_| _|\n', + result: 123456789 + },{ + text: ' _ _ _ _ _ _ _ _ _ \n'+ + '| | _| _|| ||_ |_ ||_||_|\n'+ + '|_||_ _||_| _||_| ||_| _|\n', + result: 23056789 + },{ + text: ' _ _ _ _ _ _ _ _ _ \n'+ + '|_| _| _||_||_ |_ |_||_||_|\n'+ + '|_||_ _||_| _||_| _||_| _|\n', + result: 823856989 + } + ].forEach(data => { + assert.equal( + tasks.parseBankAccount(data.text), + data.result, + `${data.text} has not parsed correctly:` + ); + }); + }); -function getPokerHandRank(hand) { - const suites = '♥♠♦♣', - numbers = 'A234567891JQK'; - let suitArr = Array.from(suites, () => 0), - numArr = Array.from(numbers, () => 0); - for (let card of hand) { - suitArr[suites.indexOf(card.slice(-1))]++; - numArr[numbers.indexOf(card[0])]++; - } - numArr.push(numArr[0]); // Ace card - let suitStr = suitArr.join(''), - numStr = numArr.join(''); - return (numStr.indexOf('11111') !== -1) && - (suitStr.indexOf('5') !== -1) ? PokerRank.StraightFlush : - (numStr.indexOf('4') !== -1) ? PokerRank.FourOfKind : - (numStr.indexOf('2') !== -1) && (numStr.indexOf('3') !== -1) ? PokerRank.FullHouse : - (suitStr.indexOf('5') !== -1) ? PokerRank.Flush : - (numStr.indexOf('11111') !== -1) ? PokerRank.Straight : - (numStr.indexOf('3') !== -1) ? PokerRank.ThreeOfKind : - (numStr.match(/2.*2.+/)) ? PokerRank.TwoPairs : - (numStr.indexOf('2') !== -1) ? PokerRank.OnePair : - PokerRank.HighCard; -} -/** - * Returns the rectangles sequence of specified figure. - * The figure is ASCII multiline string comprised of minus signs -, plus signs +, vertical bars | and whitespaces. - * The task is to break the figure in the rectangles it is made of. - * - * NOTE: The order of rectanles does not matter. - * - * @param {string} figure - * @return {Iterable.} decomposition to basic parts - * - * @example - * - * '+------------+\n'+ - * '| |\n'+ - * '| |\n'+ '+------------+\n'+ - * '| |\n'+ '| |\n'+ '+------+\n'+ '+-----+\n'+ - * '+------+-----+\n'+ => '| |\n'+ , '| |\n'+ , '| |\n'+ - * '| | |\n'+ '| |\n'+ '| |\n'+ '| |\n'+ - * '| | |\n' '+------------+\n' '+------+\n' '+-----+\n' - * '+------+-----+\n' - * - * - * - * ' +-----+ \n'+ - * ' | | \n'+ '+-------------+\n'+ - * '+--+-----+----+\n'+ '+-----+\n'+ '| |\n'+ - * '| |\n'+ => '| |\n'+ , '| |\n'+ - * '| |\n'+ '+-----+\n' '+-------------+\n' - * '+-------------+\n' - */ -function* getFigureRectangles(figure) { - const Arrtmp = figure.split('\n'); - const pluses = []; - const horizontalLines = []; - const rectangles = []; - for (let i = 0; i < Arrtmp.length; i++) - for (let j = 0; j < Arrtmp[0].length; j++) - if (Arrtmp[i][j] === '+') { - pluses.push({x: j, y: i}); - } + it.optional('wrapText should return the sequence of lines from the specified string', () => { + const text = 'The String global object is a constructor for strings, or a sequence of characters.'; + [ + { + cols: 26, + expected: [ + 'The String global object', + 'is a constructor for', + 'strings, or a sequence of', + 'characters.' + ] + },{ + cols: 12, + expected: [ + 'The String', + 'global', + 'object is a', + 'constructor', + 'for strings,', + 'or a', + 'sequence of', + 'characters.' + ] + },{ + cols: Number.MAX_SAFE_INTEGER, + expected: [ text ] + } + ].forEach(data => { + assert.deepEqual( + Array.from(tasks.wrapText(text, data.cols)), + data.expected, + `'${text}' has not wrapped correctly for ${data.cols} columns:` + ); + }); + }); - for (let i = 0; i < pluses.length; i++) - for (let j = i + 1; j < pluses.length; j++) - if (pluses[i].y === pluses[j].y) { - if (checkHorizontalLine(Arrtmp, pluses[i], pluses[j])) - horizontalLines.push([pluses[i], pluses[j]]); - } - for (let i = 0; i < horizontalLines.length; i++) - for (let j = i + 1; j < horizontalLines.length; j++) - if (checkRectangle(Arrtmp, horizontalLines[i], horizontalLines[j])) { - rectangles.push([horizontalLines[i], horizontalLines[j]]); - } + it.optional('getPokerHandRank should return the rank of the specified poker hand', () => { + var rankNames = []; + var PokerRank = tasks.PokerRank; + rankNames[PokerRank.StraightFlush] = 'StraightFlush'; + rankNames[PokerRank.FourOfKind] = 'FourOfKind'; + rankNames[PokerRank.FullHouse] = 'FullHouse'; + rankNames[PokerRank.Flush] = 'Flush'; + rankNames[PokerRank.Straight] = 'Straight'; + rankNames[PokerRank.ThreeOfKind] = 'ThreeOfKind'; + rankNames[PokerRank.TwoPairs] = 'TwoPairs'; + rankNames[PokerRank.OnePair] = 'OnePair'; + rankNames[PokerRank.HighCard] = 'HighCard'; - for (let i = 0; i < rectangles.length; i++) { - let rectangle = drawRectangle(rectangles[i]); + [ + { + hand: [ '4♥','5♥','6♥','7♥','8♥' ], + expected: PokerRank.StraightFlush + },{ + hand: [ 'A♣','K♣','Q♣','J♣','10♣' ], + expected: PokerRank.StraightFlush + },{ + hand: [ '10♦','9♦','6♦','7♦','8♦' ], + expected: PokerRank.StraightFlush + },{ + hand: [ 'A♠','4♠','3♠','5♠','2♠' ], + expected: PokerRank.StraightFlush + },{ + hand: [ '4♣','4♦','4♥','4♠','10♥' ], + expected: PokerRank.FourOfKind + },{ + hand: [ '2♣','A♦','A♣','A♠','A♥' ], + expected: PokerRank.FourOfKind + },{ + hand: [ '10♣','10♦','6♦','10♠','10♥' ], + expected: PokerRank.FourOfKind + },{ + hand: [ '4♣','4♦','5♦','5♠','5♥' ], + expected: PokerRank.FullHouse + },{ + hand: [ 'A♣','2♦','A♦','2♠','2♥' ], + expected: PokerRank.FullHouse + },{ + hand: [ '4♣','4♦','5♦','5♠','5♥' ], + expected: PokerRank.FullHouse + },{ + hand: [ '4♣','5♣','6♣','7♣','Q♣' ], + expected: PokerRank.Flush + },{ + hand: [ 'A♦','2♦','3♦','4♦','K♦' ], + expected: PokerRank.Flush + },{ + hand: [ 'A♠','Q♠','J♠','10♠','9♠' ], + expected: PokerRank.Flush + },{ + hand: [ '2♥','4♥','5♥','7♥','A♥' ], + expected: PokerRank.Flush + },{ + hand: [ '2♠','3♥','4♥','5♥','6♥' ], + expected: PokerRank.Straight + },{ + hand: [ 'A♠','K♦','Q♦','J♦','10♦' ], + expected: PokerRank.Straight + },{ + hand: [ '10♥','8♥','9♠','7♥','6♦' ], + expected: PokerRank.Straight + },{ + hand: [ '2♥','4♦','5♥','A♦','3♠' ], + expected: PokerRank.Straight + },{ + hand: [ '2♥','2♠','2♦','7♥','A♥' ], + expected: PokerRank.ThreeOfKind + },{ + hand: [ '2♥','4♥','A♥','A♦','A♠' ], + expected: PokerRank.ThreeOfKind + },{ + hand: [ '10♥','9♥','10♦','J♥','10♠' ], + expected: PokerRank.ThreeOfKind + },{ + hand: [ '2♥','4♦','4♥','A♦','A♠' ], + expected: PokerRank.TwoPairs + },{ + hand: [ '3♥','4♥','A♥','3♦','A♠' ], + expected: PokerRank.TwoPairs + },{ + hand: [ '5♥','6♥','A♥','6♦','5♠' ], + expected: PokerRank.TwoPairs + },{ + hand: [ '2♥','4♦','5♥','A♦','A♠' ], + expected: PokerRank.OnePair + },{ + hand: [ '3♥','4♥','10♥','3♦','A♠' ], + expected: PokerRank.OnePair + },{ + hand: [ '5♥','6♥','7♥','8♦','5♠' ], + expected: PokerRank.OnePair + },{ + hand: [ '3♥','4♥','5♥','7♦','8♥' ], + expected: PokerRank.HighCard + },{ + hand: [ 'A♥','K♥','Q♥','J♦','5♠' ], + expected: PokerRank.HighCard + },{ + hand: [ 'A♥','K♥','Q♥','2♦','3♠' ], + expected: PokerRank.HighCard + } + ].forEach(data => { + var actual = tasks.getPokerHandRank(data.hand); + assert( + actual >= PokerRank.HighCard, + 'Invalid return value. The return value should be >= PokerRank.HighCard' + ); + assert( + actual <= PokerRank.StraightFlush, + 'Invalid return value. The return value should be <= PokerRank.StraightFlush' + ); + assert( + actual == data.expected, + `'${data.hand}' is ranked as ${rankNames[data.expected]}, but actually ${rankNames[actual]} ` + ); + }); + }); - yield rectangle; - } -} -function checkHorizontalLine(Arrtmp, s, f) { - for (let i = s.x; i <= f.y; i++) - if (Arrtmp[s.y][i] !== '-' && Arrtmp[s.y][i] !== '+') - return false; + it.optional('getFigureRectangles should return the sequence of rectagles parts of the specified figure', () => { + [ + { + figure: '+------------+\n'+ + '| |\n'+ + '| |\n'+ + '| |\n'+ + '+------+-----+\n'+ + '| | |\n'+ + '| | |\n'+ + '+------+-----+\n', + expected: [ + '+------------+\n'+ + '| |\n'+ + '| |\n'+ + '| |\n'+ + '+------------+\n', - return true; -} + '+------+\n'+ + '| |\n'+ + '| |\n'+ + '+------+\n', -function checkRectangle(Arrtmp, top, bottom) { - if (top[0].x !== bottom[0].x) - return false; + '+-----+\n'+ + '| |\n'+ + '| |\n'+ + '+-----+\n' + ] + },{ + figure: ' +-----+ \n'+ + ' | | \n'+ + '+--+-----+----+\n'+ + '| |\n'+ + '| |\n'+ + '+-------------+\n', + expected: [ + '+-----+\n'+ + '| |\n'+ + '+-----+\n', - if (top[1].x !== bottom[1].x) - return false; + '+-------------+\n'+ + '| |\n'+ + '| |\n'+ + '+-------------+\n' + ] + },{ + figure: ' +--+ \n'+ + ' | | \n'+ + '+--+--+--+\n'+ + '| | |\n'+ + '+--+--+--+\n'+ + ' | | \n'+ + ' +--+ \n', + expected: [ + '+--+\n'+ + '| |\n'+ + '+--+\n', - const leftX = top[0].x, - rightX = top[1].x, - topY = top[0].y, - bottomY = bottom[0].y; + '+--+\n'+ + '| |\n'+ + '+--+\n', - for (let j = leftX + 1; j < rightX; j++) - if (Arrtmp[topY][j] === '+' && Arrtmp[bottomY][j] === '+') { - let hasWhiteSpace = false; + '+--+\n'+ + '| |\n'+ + '+--+\n', - for (let i = topY + 1; i < bottomY; i++) - if (Arrtmp[i][j] === ' ') - hasWhiteSpace = true; + '+-----+\n'+ + '| |\n'+ + '+-----+\n' + ] + },{ + figure: '++++\n'+ + '++++\n', + expected: [ + '++\n'+ + '++\n', + '++\n'+ + '++\n', - if (!hasWhiteSpace) - return false; + '++\n'+ + '++\n', + ] } + ].forEach(data => { + var actual = Array.from(tasks.getFigureRectangles(data.figure)).sort(); + var expected = data.expected.sort(); + assert.deepEqual( + actual, + expected, + `Figure \n${data.figure} has the following parts:\n${expected.join(',\n')} but actually :\n${actual.join(',\n')}` + ); + }); + }); - for (let i = topY + 1; i < bottomY; i++) { - if (Arrtmp[i][leftX] !== '|' && Arrtmp[i][leftX] !== '+') - return false; - - if (Arrtmp[i][rightX] !== '|' && Arrtmp[i][rightX] !== '+') - return false; - - for (let j = leftX + 1; j < rightX; j++) - if (Arrtmp[i][j] !== ' ') - return false; - } - - return true; -} - -function drawRectangle(item) { - let width = item[0][1].x - item[0][0].x + 1, - height = item[1][0].y - item[0][0].y + 1, - result = '', - topLine = '+' + ('-').repeat(width - 2) + '+' + '\n'; - - result += topLine; - result += ( '|' + (' ').repeat(width - 2) + '|' + '\n' ).repeat(height - 2); - result += topLine; - - return result; -} - - -module.exports = { - parseBankAccount : parseBankAccount, - wrapText: wrapText, - PokerRank: PokerRank, - getPokerHandRank: getPokerHandRank, - getFigureRectangles: getFigureRectangles -}; +}); From 0bdcf1103974ed89b3717228cbee27c15d15a9c5 Mon Sep 17 00:00:00 2001 From: Taras Nevostruev Date: Mon, 31 May 2021 23:12:02 +0300 Subject: [PATCH 40/40] Tests12 --- test/12-katas-3-tests.js | 289 ++++++++++++++++----------------------- 1 file changed, 120 insertions(+), 169 deletions(-) diff --git a/test/12-katas-3-tests.js b/test/12-katas-3-tests.js index ae4cd33af..32e1318cc 100644 --- a/test/12-katas-3-tests.js +++ b/test/12-katas-3-tests.js @@ -1,183 +1,134 @@ 'use strict'; -/** - * Returns true if word occurrs in the specified word snaking puzzle. - * Each words can be constructed using "snake" path inside a grid with top, left, right and bottom directions. - * Each char can be used only once ("snake" should not cross itself). - * - * @param {array} puzzle - * @param {array} searchStr - * @return {bool} - * - * @example - * var puzzle = [ - * 'ANGULAR', - * 'REDNCAE', - * 'RFIDTCL', - * 'AGNEGSA', - * 'YTIRTSP', - * ]; - * 'ANGULAR' => true (first row) - * 'REACT' => true (starting from the top-right R adn follow the ↓ ← ← ↓ ) - * 'UNDEFINED' => true - * 'RED' => true - * 'STRING' => true - * 'CLASS' => true - * 'ARRAY' => true (first column) - * 'FUNCTION' => false - * 'NULL' => false - */ -function findStringInSnakingPuzzle(puzzle, searchStr) { - function dfs(current, step) { - let save = data[current.y][current.x]; - data[current.y][current.x] = ""; - if (step == search.length) - return true; - let result = false, - steps = [[1, 0], [-1, 0], [0, -1], [0, 1]]; - for (let i = 0; i < 4; i++) { - let newX = current.x + steps[i][0], - newY = current.y + steps[i][1]; - if (data[newY][newX] == search[step]) { - result = result || dfs({x: newX, y: newY}, step + 1); - } - } - data[current.y][current.x] = save; - return result; - } - let data = Array.from(puzzle); - let Arrtmp = Array.from({length: data[0].length + 2}, () => ''); - data = data.map(item => [''].concat(item.split(''), [''])); - data = [Arrtmp].concat(data, [Arrtmp]); - let search = searchStr.split(''), - n = data[0].length - 1, - m = data.length - 1; - for (let i = 1; i < m; i++) { - for (let j = 1; j < n; j++) - if (data[i][j] == search[0]) { - if (dfs({y: i, x: j}, 1)) - return true; - } - } +var assert = require('assert'); +var tasks = require('../task/12-katas-3-tasks'); +it.optional = require('../extensions/it-optional'); - return false; -} +describe('12-katas-3-tasks', function() { + it.optional('findStringInSnakingPuzzle shoud return true if word occurrs in the specified puzzle', () => { + var puzzle = [ + 'ANGULAR', + 'REDNCAE', + 'RFIDTCL', + 'AGNEGSA', + 'YTIRTSP', + ]; + var puzzleToString = (p) => p.map(x=>' '+x).join('\n'); + [ + 'ANGULAR', 'REACT', 'UNDEFINED', 'RED', 'STRING', 'CLASS', 'ARRAY' + ].forEach(word => { + assert( + tasks.findStringInSnakingPuzzle(puzzle, word), + `Word "${word}" occurrs in puzzle\n${puzzleToString(puzzle)}` + ); + }); -/** - * Returns all permutations of the specified string. - * Assume all chars in the specified string are different. - * The order of permutations does not matter. - * - * @param {string} chars - * @return {Iterable.} all posible strings constructed with the chars from the specfied string - * - * @example - * 'ab' => 'ab','ba' - * 'abc' => 'abc','acb','bac','bca','cab','cba' - */ -function* getPermutations(chars) { - function *permute(a, len){ - if (len < 2) - yield a.join(''); - else{ - for (let i = 0; i < len; i++){ - yield *permute(a,len - 1); - const toSwap = len % 2 ? 0 : i; - let tmp = a[len - 1]; - a[len - 1] = a[toSwap]; - a[toSwap] = tmp; - } - } - } - yield *permute(chars.split(''), chars.length); -} + [ + 'FUNCTION', 'NULL', 'EMBER', 'HOISTING', 'GIT', 'ARENA' + ].forEach(word => { + assert( + !tasks.findStringInSnakingPuzzle(puzzle, word), + `Word "${word}" does not occurr in puzzle\n${puzzleToString(puzzle)}` + ); + }); + }); -/** - * Returns the most profit from stock quotes. - * Stock quotes are stores in an array in order of date. - * The stock profit is the difference in prices in buying and selling stock. - * Each day, you can either buy one unit of stock, sell any number of stock units you have already bought, or do nothing. - * Therefore, the most profit is the maximum difference of all pairs in a sequence of stock prices. - * - * @param {array} quotes - * @return {number} max profit - * - * @example - * [ 1, 2, 3, 4, 5, 6] => 15 (buy at 1,2,3,4,5 and then sell all at 6) - * [ 6, 5, 4, 3, 2, 1] => 0 (nothing to buy) - * [ 1, 6, 5, 10, 8, 7 ] => 18 (buy at 1,6,5 and sell all at 10) - */ -function getMostProfitFromStockQuotes(quotes) { - let tmpMax = quotes - .reduceRight((prev, curr)=>{ - if(prev.length == 0) - return [curr]; - prev.push(Math.max(prev[prev.length - 1],curr)); - return prev; - }, []) - .reverse(); - tmpMax.push(0); - return quotes.reduce((prev, curr, index)=>{ - return prev + Math.max(0, tmpMax[index + 1] - curr); - }, 0); -} + it.optional('getPermutations should return all possible string permutations', () => { + [ + { + chars: 'a', + expected: [ 'a' ] + },{ + chars: 'ab', + expected: [ 'ab', 'ba' ] + },{ + chars: 'abc', + expected: [ 'abc', 'acb', 'bac', 'bca', 'cab', 'cba' ] + },{ + chars: 'abcd', + expected: [ + 'abcd', 'abdc', 'acbd', 'acdb', 'adbc', 'adcb', + 'bacd', 'badc', 'bcad', 'bcda', 'bdac', 'bdca', + 'cabd', 'cadb', 'cbad', 'cbda', 'cdab', 'cdba', + 'dabc', 'dacb', 'dbac', 'dbca', 'dcab', 'dcba' + ] + } + ].forEach(data => { + assert.deepEqual( + Array.from(tasks.getPermutations(data.chars)).sort(), + data.expected, + `Incorrect permutations of "${data.chars}"` + ); + }); + assert.equal( + Array.from(tasks.getPermutations('12345')).length, + 120, + 'Number of 5 chars permutations should be 120.' + ); + }); -/** - * Class representing the url shorting helper. - * Feel free to implement any algorithm, but do not store link in the key\value stores. - * The short link can be at least 1.5 times shorter than the original url. - * - * @class - * - * @example - * - * var urlShortener = new UrlShortener(); - * var shortLink = urlShortener.encode('https://en.wikipedia.org/wiki/URL_shortening'); - * var original = urlShortener.decode(shortLink); // => 'https://en.wikipedia.org/wiki/URL_shortening' - * - */ -function UrlShortener() { - this.urlAllowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ - "abcdefghijklmnopqrstuvwxyz"+ - "0123456789-_.~!*'();:@&=+$,/?#[]"; -} + it.optional('getMostProfitFromStockQuotes should return the max profit from stock trading', () => { + [ + { + quotes: [ 1, 2, 3, 4, 5, 6 ], + expected: 15 + },{ + quotes: [ 6, 5, 4, 3, 2, 1 ], + expected: 0 + },{ + quotes: [ 1, 6, 5, 10, 8, 7 ], + expected: 18 + },{ + quotes: [ 31, 312, 3, 35, 33, 3, 44, 123, 126, 2, 4, 1 ], + expected: 798 + },{ + quotes: [ 1, 20, 1, 30, 1, 40, 1, 50, 1, 40, 1, 30, 1, 20, 1 ], + expected: 343 + } + ].forEach(data => { + var actual = tasks.getMostProfitFromStockQuotes(data.quotes); + assert.equal( + actual, + data.expected, + `Most profit for [${data.quotes}] quotes is ${data.expected} but actually ${actual}` + ); + }); + }); -UrlShortener.prototype = { - encode: function(url) { - let res = ""; - for (let i = 0; i < url.length; i += 2) { - let tmp1 = url.charCodeAt(i); - let tmp2 = url.charCodeAt(i + 1); - let code = (tmp1 << 8) |tmp2; - res += String.fromCharCode(code); - } - return res; - }, + it.optional('urlShortener should return encoded string shorter than original url', () => { + [ + 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul', + 'https://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters', + 'https://en.wikipedia.org/wiki/Binary-to-text_encoding#Encoding_plain_text' + ].forEach(data => { + var urlShortener = new tasks.UrlShortener(); + var actual = urlShortener.encode(data); + assert( + data.length / actual.length > 1.5, + `urlShortener.encode for "${data}" returns "${actual}" that is only ${data.length/actual.length} times less than original url` + ); + }); + }); - decode: function(code) { - let res = ""; - for (let i = 0; i < code.length; i++) { - let char = parseInt(code.charCodeAt(i), 10); - let tmp1 = char & 255; - let tmp2 = (char >> 8) & 255; - if (tmp1 === 0) { - res += String.fromCharCode(tmp2) - } else { - res += String.fromCharCode(tmp2) + String.fromCharCode(tmp1); - } - } - return res; - } -} + it.optional('urlShortener should decode shorten link to to the original url', () => { + [ + 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul', + 'https://www.example.com/catalog.html?search=mobile+phones&price=100-200&year=2016#top_links', + ].forEach(data => { + var urlShortener = new tasks.UrlShortener(); + var encoded = urlShortener.encode(data); + var actual = urlShortener.decode(encoded); + assert.equal( + data, + actual, + `urlShortener.encode for "${data}" returns "${encoded}" but decode returns "${actual}"` + ); + }); + }); -module.exports = { - findStringInSnakingPuzzle: findStringInSnakingPuzzle, - getPermutations: getPermutations, - getMostProfitFromStockQuotes: getMostProfitFromStockQuotes, - UrlShortener: UrlShortener -}; +});