diff --git a/README.md b/README.md index a81b5c08a..bed94a5f7 100644 --- a/README.md +++ b/README.md @@ -1,113 +1 @@ -[![Brest Rolling Scopes](http://brest.rollingscopes.com/images/logo_rs_text.svg)](http://brest.rollingscopes.com/) -#Brest Rolling Scopes School -## Javascript Assignments [![Build Status](https://travis-ci.org/rolling-scopes-school/js-assignments.svg?branch=master)](https://travis-ci.org/rolling-scopes-school/js-assignments) - -Yet another javascript assignments. There are a lot of interactive javascript resources for beginners, but most of them are online and do not cover the modern programming workflow. There are some excellent training resources on github (https://github.com/rmurphey/js-assessment, https://github.com/mrdavidlaing/javascript-koans, https://github.com/vasanthk/js-bits etc) but they are not exactly simulate the everyday programming process. So the motivation of this project is to show TDD process in the wild to the beginners. Assingment tests are implemented in various ways to feel a difference and gain the experience what manner is good, what is bad and what is ugly. - -Another idea is to prepare assignment to cover all standard javascript functions, to drilling and mastering skills. Some tasks are practical, but some tasks are rather synthetic. - -And the last idea is to inure trainees to work using unit test and feel uncomfortable when programming without tests. - -To start javascript assignments please follow the next steps: -* [Fork this repo](#user-content-how-to-fork-this-repo) -* [Setup travis-ci to test the commits](#user-content-how-to-setup-travis-ci) -* [Setup the work environment](#user-content-how-to-setup-work-environment) -* [Implement assignments using TDD fashion](#user-content-how-to-implement-assignments-using-tdd-fashion) -* [How to debug tasks](#how-to-debug-tasks) - -### How to fork this repo -* Click the **Fork** button at the top-right corner of this page and the repository will be copied to your own account. -* Run `git clone https://github.com//js-assignments.git` from command line to download the repo. - -### How to setup travis-ci -* Open [https://travis-ci.org/](https://travis-ci.org/) and sign in with your github account. -* Activate your forked repo **js-assignments**. -* Edit local README.md file and update all links (just replace all occurrences of `'rolling-scopes-school'` with your account name). -* Commit and push updated README.md to github: -```bash - git add README.md - git commit -m "Update the links" - git push origin master -``` -* Open https://github.com/rolling-scopes-school/js-assignments and test the build icon. Now it will run all tests and update status once you push changes to github. Keep this icon green! - - -### How to setup work environment -* Download and install the latest [Nodejs](https://nodejs.org/en/download/stable/). -* Run `npm install` from you repository folder to download the required modules. All dependent modules will be located in the *node_modules* folder. -* Open your favorite editor and complete tasks. -* Open your terminal and use `npm test` command to run all tests. You can run single file by passing it as argument `npm test ./test/01-strings-tests.js`. -* The local repo folder has the following structure:
-    node_modules - app dependences restored by `npm install` command, you can delete this folder and restore later again.
-    task - folder with tasks modules, it's your main folder.
-    test - folder with tests modules to verify the tasks completion.
-
- -### How to implement assignments using TDD fashion -Now you are ready to implement assignments. Tasks modules are located in the **task** folder. Each module consists of several tasks for specified topic. Each task is usually a regular function: -```javascript - /** - * Returns the result of concatenation of two strings. - * - * @param {string} value1 - * @param {string} value2 - * @return {string} - * - * @example - * 'aa', 'bb' => 'aabb' - * 'aa','' => 'aa' - * '', 'bb' => 'bb' - */ - function concatenateStrings(value1, value2) { - throw new Error('Not implemented'); - } -``` -Resolve this task using the following [TDD steps](https://en.wikipedia.org/wiki/Test-driven_development#Test-driven_development_cycle): -* Run unit tests and make sure that everything is OK and there are no failing tests. -* Read the task description in the comment above the function. Try to understand the idea. If you got it you are to write unit test first, but unit tests are already prepared :) Skip step with writing unit tests. -* Remove the throwing error line from function body -```javascript - throw new Error('Not implemented'); -``` -and run the unit tests again. Find one test failed (red). Now it's time to fix it! -* Implement the function by any way and verify your solution by running tests until the failed test become passed (green). -* Your solution work, but now time to refactor it. Try to make your code as pretty and simple as possible keeping up the test green. -* Once you can't improve your code and tests are passed you can commit your solution. -* Push your updates to github server and check if tests passed on [travis-ci](https://travis-ci.org/rolling-scopes-school/js-assignments/builds). -* If everything is OK you can try to resolve the next task. - -### How to debug tasks -To debug tests you can use **Node inspector**. To install it just run `npm install -g node-inspector` in your terminal. Then follow next steps: -* Add `debugger;` to the first line of your task. -* Run your test file with `npm run test-debug ./test/01-strings-tests.js`. -* In another terminal run `node-inspector` and copy link from the output. -* Open the link in your favorite browser. You should see Chrome Developers Tools like interface where you can debug your tasks. -* When you found and fix your issue, close the browser's tab with the debug tools, stop the node-inspector by pressing Ctrl-C, stop the test runner by pressing Ctrl-C, remove the `debugger;` from your task. - -### How to debug (beginner's way) -There is an easier way to debug for beginners with free Visual Studio Code: -* Install VSC from https://code.visualstudio.com/ -* Open project folder in VSC and follow the instruction from https://code.visualstudio.com/docs/runtimes/nodejs#_debugging-your-node-application to create a default `launch.json` -* Modify the `launch.json` in the IDE, set the properties "program" and "args" (empty "args" value run all tests, to run particular test specify this test file in "args"): -``` -{ - "version": "0.2.0", - "configurations": [ - { - ... - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - ... - "args": ["./test/01-strings-tests.js"], - ... - }, - ... - ] -} -``` -* Click in the gutter to the left of the line number to set the breakpoint. Press `F5` to run debug. -* NOTE: The `launch.json` is stored in the `.vscode` project folder. - - -##Contribution -Feel free to contribute into this project. New tasks and katas are welcome. - +My lab 3 solution is [here](https://www.github.com/ynizhenie/js-assignments) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..fa733ca4e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,220 @@ +{ + "name": "js-training", + "version": "0.9.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "js-training", + "version": "0.9.1", + "license": "MIT", + "devDependencies": { + "mocha": "^2.5.3" + } + }, + "node_modules/commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha512-CD452fnk0jQyk3NfnK+KkR/hUPoHt5pVaKHogtyyv3N0U4QfAal9W0/rXLOg/vVZgQKa7jdtXypKs1YAip11uQ==", + "dev": true, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha512-X0rGvJcskG1c3TgSCPqHJ0XJgwlcvOC7elJ5Y0hYuKBZoVqWpAMfLOeIh2UI/DCQ5ruodIjvsugZtjUYUw2pUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "0.7.1" + } + }, + "node_modules/diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha512-VzVc42hMZbYU9Sx/ltb7KYuQ6pqAw+cbFWVy4XKdkuEL2CFaRLGEnISPs7YdzaUGpi+CpIqvRmu7hPQ4T7EQ5w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha512-cQpUid7bdTUnFin8S7BnNdOk+/eDqQmKgCANSyd/jAhrKEvxUvr9VQ8XZzXiOtest8NLfk3FSBZzwvemZNQ6Vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha512-hVb0zwEZwC1FXSKRPFTeOtN7AArJcJlI6ULGLtrstaswKNlrTJqAA+1lYlSUop4vjA423xlBzqfVS3iWGlqJ+g==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "BSD", + "dependencies": { + "inherits": "2", + "minimatch": "0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha512-RTBwDHhNuOx4F0hqzItc/siXCasGfC4DeWcBamclWd+6jWtBaeB/SGbMkGf0eiQoW7ib8JpvOgnUsmgMHI3Mfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha512-mkk3vzUHFjzKjpCXeu+IjXeZD+QOTjUUdubgmHtHTDwvAO2ZTkMTTVrapts5CWz3JvJryh/4KWZpjeZrCepZ3A==", + "deprecated": "Jade has been renamed to pug, please install the latest version of pug instead of jade", + "dev": true, + "dependencies": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "bin": { + "jade": "bin/jade" + } + }, + "node_modules/jade/node_modules/commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha512-0fLycpl1UMTGX257hRsu/arL/cUbcvQM4zMKwvLvzXtfdezIV4yotPS2dYtknF+NmEfWSoCEF6+hj9XLm/6hEw==", + "dev": true, + "engines": { + "node": ">= 0.4.x" + } + }, + "node_modules/jade/node_modules/mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha512-WFX1jI1AaxNTZVOHLBVazwTWKaQjoykSzCBNXB72vDTCzopQGtyP91tKdFK5cv1+qMwPyiTu1HqUriqplI8pcA==", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "license": "MIT", + "dependencies": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha512-jNt2iEk9FPmZLzL+sm4FNyOIDYXf2wUU6L4Cc8OIKK/kzgMHKPi4YhTZqG4bW4kQVdIv6wutDybRhXfdnujA1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 0.8.x" + } + }, + "node_modules/ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha512-lRLiIR9fSNpnP6TC4v8+4OU7oStC01esuNowdQ34L+Gk8e5Puoc88IqJ+XAY/B3Mn2ZKis8l8HX90oU8ivzUHg==", + "dev": true + }, + "node_modules/sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", + "dev": true, + "license": "ISC" + }, + "node_modules/supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha512-mS5xsnjTh5b7f2DM6bch6lR582UCOTphzINlZnDsfpIRrwI6r58rb6YSSGsdexkm8qw2bBVO2ID2fnJOTuLiPA==", + "dev": true, + "license": "MIT", + "bin": { + "supports-color": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha512-oeHLgfWA7d0CPQa6h0+i5DAJZISz5un0d5SHPkw+Untclcvzv9T+AC3CvGXlZJdOlIbxbTfyyzlqCXc5hjpXYg==", + "deprecated": "to-iso-string has been deprecated, use @segment/to-iso-string instead.", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index 712b264fa..ac6e519fd 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,16 @@ "version": "0.9.1", "description": "JS training tasks", "scripts": { - "test": "./node_modules/.bin/mocha", - "test-debug": "./node_modules/.bin/mocha --debug-brk" + "test": "node ./node_modules/mocha/bin/_mocha", + "test-debug": "node --inspect-brk ./node_modules/mocha/bin/_mocha" }, "author": "aorgish", "license": "MIT", "devDependencies": { - "mocha": "^2.3.4" + "mocha": "^2.5.3" }, - "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..467f8466e 100644 --- a/task/01-strings-tasks.js +++ b/task/01-strings-tasks.js @@ -7,8 +7,6 @@ * * ********************************************************************************************/ - - /** * Returns the result of concatenation of two strings. * @@ -22,10 +20,9 @@ * '', 'bb' => 'bb' */ function concatenateStrings(value1, value2) { - throw new Error('Not implemented'); + return value1 + value2; } - /** * Returns the length of given string. * @@ -38,7 +35,7 @@ function concatenateStrings(value1, value2) { * '' => 0 */ function getStringLength(value) { - throw new Error('Not implemented'); + return value.length; } /** @@ -55,7 +52,7 @@ function getStringLength(value) { * 'Chuck','Norris' => 'Hello, Chuck Norris!' */ function getStringFromTemplate(firstName, lastName) { - throw new Error('Not implemented'); + return `Hello, ${firstName} ${lastName}!`; } /** @@ -69,10 +66,9 @@ function getStringFromTemplate(firstName, lastName) { * 'Hello, Chuck Norris!' => 'Chuck Norris' */ function extractNameFromTemplate(value) { - throw new Error('Not implemented'); + return value.replace('Hello, ', '').replace('!', ''); } - /** * Returns a first char of the given string. * @@ -84,7 +80,7 @@ function extractNameFromTemplate(value) { * 'cat' => 'c' */ function getFirstChar(value) { - throw new Error('Not implemented'); + return value.charAt(0); } /** @@ -99,7 +95,7 @@ function getFirstChar(value) { * '\tHello, World! ' => 'Hello, World!' */ function removeLeadingAndTrailingWhitespaces(value) { - throw new Error('Not implemented'); + return value.trim(); } /** @@ -114,7 +110,7 @@ function removeLeadingAndTrailingWhitespaces(value) { * 'cat', 3 => 'catcatcat' */ function repeatString(value, count) { - throw new Error('Not implemented'); + return value.repeat(count); } /** @@ -130,7 +126,9 @@ function repeatString(value, count) { * 'ABABAB','BA' => 'ABAB' */ function removeFirstOccurrences(str, value) { - throw new Error('Not implemented'); + const index = str.indexOf(value); + if (index === -1) return str; + return str.slice(0, index) + str.slice(index + value.length); } /** @@ -145,10 +143,9 @@ function removeFirstOccurrences(str, value) { * '' => 'a' */ function unbracketTag(str) { - throw new Error('Not implemented'); + return str.slice(1, -1); } - /** * Converts all characters of the specified string into the upper case * @@ -160,7 +157,7 @@ function unbracketTag(str) { * 'abcdefghijklmnopqrstuvwxyz' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' */ function convertToUpperCase(str) { - throw new Error('Not implemented'); + return str.toUpperCase(); } /** @@ -174,7 +171,7 @@ function convertToUpperCase(str) { * 'info@gmail.com' => ['info@gmail.com'] */ function extractEmails(str) { - throw new Error('Not implemented'); + return str.split(';'); } /** @@ -201,10 +198,13 @@ function extractEmails(str) { * */ function getRectangleString(width, height) { - throw new Error('Not implemented'); + const top = '┌' + '─'.repeat(width - 2) + '┐\n'; + const middle = '│' + ' '.repeat(width - 2) + '│\n'; + const bottom = '└' + '─'.repeat(width - 2) + '┘\n'; + if (height === 2) return top + bottom; + return top + middle.repeat(height - 2) + bottom; } - /** * Encode specified string with ROT13 cipher * See details: https://en.wikipedia.org/wiki/ROT13 @@ -221,7 +221,10 @@ function getRectangleString(width, height) { * */ function encodeToRot13(str) { - throw new Error('Not implemented'); + return str.replace(/[a-zA-Z]/g, function(c) { + const base = c <= 'Z' ? 65 : 97; + return String.fromCharCode((c.charCodeAt(0) - base + 13) % 26 + base); + }); } /** @@ -238,10 +241,9 @@ function encodeToRot13(str) { * isString(new String('test')) => true */ function isString(value) { - throw new Error('Not implemented'); + return typeof value === 'string' || value instanceof String; } - /** * Returns playid card id. * @@ -267,24 +269,27 @@ function isString(value) { * 'K♠' => 51 */ function getCardId(value) { - throw new Error('Not implemented'); + const suits = ['♣', '♦', '♥', '♠']; + const ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; + const suit = value.slice(-1); + const rank = value.slice(0, -1); + return suits.indexOf(suit) * 13 + ranks.indexOf(rank); } - module.exports = { - concatenateStrings: concatenateStrings, - getStringLength: getStringLength, - getStringFromTemplate: getStringFromTemplate, - extractNameFromTemplate: extractNameFromTemplate, - getFirstChar: getFirstChar, - removeLeadingAndTrailingWhitespaces: removeLeadingAndTrailingWhitespaces, - repeatString: repeatString, - removeFirstOccurrences: removeFirstOccurrences, - unbracketTag: unbracketTag, - convertToUpperCase: convertToUpperCase, - extractEmails: extractEmails, - getRectangleString: getRectangleString, - encodeToRot13: encodeToRot13, - isString: isString, - getCardId: getCardId -}; + concatenateStrings, + getStringLength, + getStringFromTemplate, + extractNameFromTemplate, + getFirstChar, + removeLeadingAndTrailingWhitespaces, + repeatString, + removeFirstOccurrences, + unbracketTag, + convertToUpperCase, + extractEmails, + getRectangleString, + encodeToRot13, + isString, + getCardId +}; \ No newline at end of file diff --git a/task/02-numbers-tasks.js b/task/02-numbers-tasks.js index c9ed20208..e9d1dcc4d 100644 --- a/task/02-numbers-tasks.js +++ b/task/02-numbers-tasks.js @@ -10,218 +10,67 @@ ********************************************************************************************/ -/** - * 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) { - throw new Error('Not implemented'); + 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) { - throw new Error('Not implemented'); + 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) { - throw new Error('Not implemented'); + 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) { - throw new Error('Not implemented'); + const dx = x2 - x1; + const dy = y2 - y1; + return Math.sqrt(dx * dx + dy * dy); } -/** - * 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) { - throw new Error('Not implemented'); + 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) { - throw new Error('Not implemented'); + const dotProduct = x1 * x2 + y1 * y2; + const magnitude1 = Math.sqrt(x1 * x1 + y1 * y1); + const magnitude2 = Math.sqrt(x2 * x2 + y2 * y2); + const cosTheta = dotProduct / (magnitude1 * magnitude2); + return Math.acos(cosTheta); } -/** - * 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) { - throw new Error('Not implemented'); + return Math.abs(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) { - throw new Error('Not implemented'); + return 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) { - throw new Error('Not implemented'); +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) { - throw new Error('Not implemented'); + const factor = Math.pow(10, pow); + return Math.round(num / factor) * factor; } -/** - * 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) { - throw new Error('Not implemented'); + if (n <= 1) return false; + if (n === 2) return true; + if (n % 2 === 0) return false; + const sqrt = Math.sqrt(n); + for (let i = 3; i <= sqrt; i += 2) { + if (n % i === 0) return false; + } + 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) { - throw new Error('Not implemented'); + const num = Number(value); + return isNaN(num) ? def : num; } module.exports = { diff --git a/task/03-date-tasks.js b/task/03-date-tasks.js index 83c6266bc..7ae8edb65 100644 --- a/task/03-date-tasks.js +++ b/task/03-date-tasks.js @@ -9,92 +9,40 @@ ********************************************************************************************/ -/** - * Parses a rfc2822 string date representation into date value - * For rfc2822 date specification refer to : http://tools.ietf.org/html/rfc2822#page-14 - * - * @param {string} value - * @return {date} - * - * @example: - * 'December 17, 1995 03:24:00' => Date() - * 'Tue, 26 Jan 2016 13:48:02 GMT' => Date() - * 'Sun, 17 May 1998 03:00:00 GMT+01' => Date() - */ function parseDataFromRfc2822(value) { - throw new Error('Not implemented'); + return new Date(value); } -/** - * Parses an ISO 8601 string date representation into date value - * For ISO 8601 date specification refer to : https://en.wikipedia.org/wiki/ISO_8601 - * - * @param {string} value - * @return {date} - * - * @example : - * '2016-01-19T16:07:37+00:00' => Date() - * '2016-01-19T08:07:37Z' => Date() - */ function parseDataFromIso8601(value) { - throw new Error('Not implemented'); + return new Date(value); } - -/** - * Returns true if specified date is leap year and false otherwise - * Please find algorithm here: https://en.wikipedia.org/wiki/Leap_year#Algorithm - * - * @param {date} date - * @return {bool} - * - * @example : - * Date(1900,1,1) => false - * Date(2000,1,1) => true - * Date(2001,1,1) => false - * Date(2012,1,1) => true - * Date(2015,1,1) => false - */ function isLeapYear(date) { - throw new Error('Not implemented'); + const year = date.getFullYear(); + return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0); } - -/** - * Returns the string represention of the timespan between two dates. - * The format of output string is "HH:mm:ss.sss" - * - * @param {date} startDate - * @param {date} endDate - * @return {string} - * - * @example: - * Date(2000,1,1,10,0,0), Date(2000,1,1,11,0,0) => "01:00:00.000" - * Date(2000,1,1,10,0,0), Date(2000,1,1,10,30,0) => "00:30:00.000" - * Date(2000,1,1,10,0,0), Date(2000,1,1,10,0,20) => "00:00:20.000" - * Date(2000,1,1,10,0,0), Date(2000,1,1,10,0,0,250) => "00:00:00.250" - * 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'); + const diff = endDate - startDate; + const ms = diff % 1000; + const sec = Math.floor(diff / 1000) % 60; + const min = Math.floor(diff / (1000 * 60)) % 60; + const hrs = Math.floor(diff / (1000 * 60 * 60)); + + const pad = (num, len = 2) => num.toString().padStart(len, '0'); + return `${pad(hrs)}:${pad(min)}:${pad(sec)}.${pad(ms, 3)}`; } - -/** - * 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} - * - * @example: - * Date.UTC(2016,2,5, 0, 0) => 0 - * Date.UTC(2016,3,5, 3, 0) => Math.PI/2 - * Date.UTC(2016,3,5,18, 0) => Math.PI - * Date.UTC(2016,3,5,21, 0) => Math.PI/2 - */ function angleBetweenClockHands(date) { - throw new Error('Not implemented'); + const utcHours = date.getUTCHours() % 12; + const utcMinutes = date.getUTCMinutes(); + + const minuteAngle = 6 * utcMinutes; + const hourAngle = 0.5 * (60 * utcHours + utcMinutes); + let angle = Math.abs(hourAngle - minuteAngle); + if (angle > 180) angle = 360 - angle; + + return (angle * Math.PI) / 180; } diff --git a/task/04-arrays-tasks.js b/task/04-arrays-tasks.js index ff3a4c019..a994c0b7d 100644 --- a/task/04-arrays-tasks.js +++ b/task/04-arrays-tasks.js @@ -1,598 +1,127 @@ '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) { - 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 ] - * 5 => [ 1, 3, 5, 7, 9 ] - */ function generateOdds(len) { - throw new Error('Not implemented'); + return Array.from({ length: len }, (_, i) => 2 * i + 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) { - throw new Error('Not implemented'); + return arr.concat(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(x => typeof x === 'number' && x > 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(x => typeof x === '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(Boolean); } -/** - * 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(x => x.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(x => x.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) { - throw new Error('Not implemented'); + arr.splice(index, 0, item); + return arr; } -/** - * 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) { - 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 - * - * @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.slice(-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'); + return arr.map(row => row.join(',')).join('\n'); } -/** - * 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(x => x * x); } - -/** - * 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) { - throw new Error('Not implemented'); + let sum = 0; + return arr.map(x => sum += x); } -/** - * 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((_, index) => index % 2 === 1); } - -/** - * 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) { - throw new Error('Not implemented'); + return arr.flatMap((item, index) => Array(index + 1).fill(item)); } - -/** - * 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) { - throw new Error('Not implemented'); + return arr.slice().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'); + return arr.filter(x => typeof x === 'number' && x > 0).length; } - -/** - * 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) { - throw new Error('Not implemented'); + const digits = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']; + return arr.sort((a, b) => digits.indexOf(a) - digits.indexOf(b)); } -/** - * 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) { - throw new Error('Not implemented'); + return arr.reduce((sum, x) => sum + x, 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) { - throw new Error('Not implemented'); + return arr.filter(x => !x).length; } -/** - * 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) { - throw new Error('Not implemented'); + return arr.filter(x => x === item).length; } -/** - * 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' }, - * { 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) { - throw new Error('Not implemented'); + return arr.slice().sort((a, b) => + a.country.localeCompare(b.country) || a.city.localeCompare(b.city) + ); } -/** - * 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) { - throw new Error('Not implemented'); + return Array.from({ length: n }, (_, i) => + Array.from({ length: n }, (_, j) => (i === j ? 1 : 0)) + ); } -/** - * 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) { - throw new Error('Not implemented'); + return Array.from({ length: end - start + 1 }, (_, i) => start + i); } -/** - * 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) { - throw new Error('Not implemented'); + return [...new Set(arr)]; } -/** - * 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) { - throw new Error('Not implemented'); -} - - -/** - * 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) { - throw new Error('Not implemented'); -} - - -/** - * 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) { - throw new Error('Not implemented'); -} - - -/** - * 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) { - throw new Error('Not implemented'); + return array.reduce((map, item) => { + const key = keySelector(item); + const value = valueSelector(item); + const group = map.get(key) || []; + group.push(value); + map.set(key, group); + return map; + }, new Map()); } - - -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 -}; diff --git a/task/05-regex-tasks.js b/task/05-regex-tasks.js index b1c60f2d4..0e293901c 100644 --- a/task/05-regex-tasks.js +++ b/task/05-regex-tasks.js @@ -1,125 +1,26 @@ 'use strict'; -/******************************************************************************************** - * * - * Plese read the following tutorial before implementing tasks: * - * https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions * - * * - ********************************************************************************************/ - - -/** - * Returns the regexp that matches a GUID string representation - * '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}', - * where X is hexadecimal digit (0,1,2...,9,A,a,B,b,C,c,D,d,F,f) - * - * See more details: https://en.wikipedia.org/wiki/Globally_unique_identifier - * - * Match : - * '{3F2504E0-4F89-41D3-9A0C-0305E82C3301}' - * '{21EC2020-3AEA-4069-A2DD-08002B30309D}' - * '{0c74f13f-fa83-4c48-9b33-68921dd72463}' - * - * Do not match: - * '{D44EF4F4-280B47E5-91C7-261222A59621}' - * '{D1A5279D-B27D-4CD4-A05E-EFDH53D08E8D}' - * '{5EDEB36C-9006-467A8D04-AFB6F62CD7D2}' - * '677E2553DD4D43B09DA77414DB1EB8EA' - * '0c74f13f-fa83-4c48-9b33-68921dd72463' - * 'The roof, the roof, the roof is on fire' - * - * @return {RegExp} - */ function getRegexForGuid() { - throw new Error('Not implemented'); + return /^\{[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\}$/; } - -/** - * Returns the regexp that matches all the strings from first column - * but of them from the second - * - * Match : Do not match: - * ----------- -------------- - * 'pit' ' pt' - * 'spot' 'Pot' - * 'spate' 'peat' - * 'slap two' 'part' - * 'respite' - * - * NOTE : the regex lenth should be < 13 - * - * @return {RegExp} - * - */ function getRegexForPitSpot() { - throw new Error('Not implemented'); + return /p.t/; } - -/** - * Returns the regexp that matches all IPv4 strings in - * 'XX.XX.XX.XX' dotted format where XX is number 0 to 255 - * - * Valid IPv4: Invalid IPv4 - * --------------- ----------------- - * '0.0.0.0' '300.0.0.0' - * '127.0.0.1' '127.0.0.-1' - * '10.10.1.1' '23.24.25.26.27' - * '46.61.155.237' 'Set dns to 8.8.8.8' - * '010.234.015.001' - * - * @return {RegExp} - */ function getRegexForIPv4() { - throw new Error('Not implemented'); + const num = '(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)'; + return new RegExp(`^(${num}\\.){3}${num}$`); } - -/** - * Returns the regexp that matches all SSN (Social Security Number) codes in - * 'XXX-XX-XXXX' format where X is digit, where each group can't be all zeros - * https://en.wikipedia.org/wiki/Social_Security_number - * - * Valid SSN: Invalid SSN - * --------------- ----------------- - * '123-45-6789' '123456789' - * '234-56-2349' '000-56-2349' - * '875-43-0298' '875-00-0298' - * '034-01-0008' '034-01-0000' - * '0S4-H1-HACK' - * @return {RegExp} - */ function getRegexForSSN() { - throw new Error('Not implemented'); + return /^(?!000)\d{3}-(?!00)\d{2}-(?!0000)\d{4}$/; } - -/** - * Returns the password validator regex. - * Regex will validate a password to make sure it meets the follwing criteria: - * - At least specified characters long (argument minLength) - * - Contains a lowercase letter - * - Contains an uppercase letter - * - Contains a number - * - Valid passwords will only be alphanumeric characters. - * - * @param {number} minLength - * @return {Regex} - * - * @example - * let validator = getPasswordValidator(6); - * 'password'.match(validator) => false - * 'Pa55Word'.match(validator) => true - * 'PASSw0rd'.match(validator) => true - * 'PASSW0RD'.match(validator) => false - * 'Pa55'.match(validator) => false - */ function getPasswordValidator(minLength) { - throw new Error('Not implemented'); + return new RegExp(`^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[A-Za-z\\d]{${minLength},}$`); } - module.exports = { getRegexForGuid: getRegexForGuid, getRegexForPitSpot: getRegexForPitSpot, diff --git a/task/06-conditions-n-loops-tasks.js b/task/06-conditions-n-loops-tasks.js index 249194c34..f99b2f0b6 100644 --- a/task/06-conditions-n-loops-tasks.js +++ b/task/06-conditions-n-loops-tasks.js @@ -1,440 +1,183 @@ 'use strict'; -/************************************************************************************************** - * * - * Plese read the following tutorial before implementing tasks: * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration * - * * - **************************************************************************************************/ - - -/** - * Returns the 'Fizz','Buzz' or an original number using the following rules: - * 1) return original number - * 2) but if number multiples of three return 'Fizz' - * 3) for the multiples of five return 'Buzz' - * 4) for numbers which are multiples of both three and five return 'FizzBuzz' - * - * @param {number} num - * @return {any} - * - * @example - * 2 => 2 - * 3 => 'Fizz' - * 5 => 'Buzz' - * 4 => 4 - * 15 => 'FizzBuzz' - * 20 => 'Buzz' - * 21 => 'Fizz' - * - */ function getFizzBuzz(num) { - throw new Error('Not implemented'); + if (num % 3 === 0 && num % 5 === 0) return 'FizzBuzz'; + if (num % 3 === 0) return 'Fizz'; + if (num % 5 === 0) return 'Buzz'; + return num; } - -/** - * Returns the factorial of the specified integer n. - * - * @param {number} n - * @return {number} - * - * @example: - * 1 => 1 - * 5 => 120 - * 10 => 3628800 - */ function getFactorial(n) { - throw new Error('Not implemented'); + if (n === 0) return 1; + return n * getFactorial(n - 1); } - -/** - * Returns the sum of integer numbers between n1 and n2 (inclusive). - * - * @param {number} n1 - * @param {number} n2 - * @return {number} - * - * @example: - * 1,2 => 3 ( = 1+2 ) - * 5,10 => 45 ( = 5+6+7+8+9+10 ) - * -1,1 => 0 ( = -1 + 0 + 1 ) - */ function getSumBetweenNumbers(n1, n2) { - throw new Error('Not implemented'); + let sum = 0; + for (let i = n1; i <= n2; i++) { + sum += i; + } + return sum; } - -/** - * Returns true, if a triangle can be built with the specified sides a,b,c and false in any other ways. - * - * @param {number} a - * @param {number} b - * @param {number} c - * @return {bool} - * - * @example: - * 1,2,3 => false - * 3,4,5 => true - * 10,1,1 => false - * 10,10,10 => true - */ -function isTriangle(a,b,c) { - throw new Error('Not implemented'); +function isTriangle(a, b, c) { + 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 - * { - * top: 5, - * left: 5, - * width: 20, - * height: 10 - * } - * - * (5;5) - * ------------- - * | | - * | | height = 10 - * ------------- - * 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} - * - * @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 < rect2.left + rect2.width && + rect1.left + rect1.width > rect2.left && + rect1.top < rect2.top + rect2.height && + rect1.top + rect1.height > rect2.top; } - -/** - * Returns true, if point lies inside the circle, otherwise false. - * Circle is an object of - * { - * center: { - * x: 5, - * y: 5 - * }, - * radius: 20 - * } - * - * Point is object of - * { - * x: 5, - * y: 5 - * } - * - * @param {object} circle - * @param {object} point - * @return {bool} - * - * @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'); + const dx = point.x - circle.center.x; + const dy = point.y - circle.center.y; + return dx * dx + dy * dy < circle.radius * circle.radius; } - -/** - * Returns the first non repeated char in the specified strings otherwise returns null. - * - * @param {string} str - * @return {string} - * - * @example: - * 'The quick brown fox jumps over the lazy dog' => 'T' - * 'abracadabra' => 'c' - * 'entente' => null - */ function findFirstSingleChar(str) { - throw new Error('Not implemented'); + const charCount = {}; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + if (charCount[char]) { + charCount[char]++; + } else { + charCount[char] = 1; + } + } + for (let i = 0; i < str.length; i++) { + if (charCount[str[i]] === 1) { + return str[i]; + } + } + return null; } - -/** - * Returns the string representation of math interval, specified by two points and include / exclude flags. - * See the details: https://en.wikipedia.org/wiki/Interval_(mathematics) - * - * Please take attention, that the smaller number should be the first in the notation - * - * @param {number} a - * @param {number} b - * @param {bool} isStartIncluded - * @param {bool} isEndIncluded - * @return {string} - * - * @example - * 0, 1, true, true => '[0, 1]' - * 0, 1, true, false => '[0, 1)' - * 0, 1, false, true => '(0, 1]' - * 0, 1, false, false => '(0, 1)' - * Smaller number has to be first : - * 5, 3, true, true => '[3, 5]' - * - */ function getIntervalString(a, b, isStartIncluded, isEndIncluded) { - throw new Error('Not implemented'); + const start = Math.min(a, b); + const end = Math.max(a, b); + const startBracket = isStartIncluded ? '[' : '('; + const endBracket = isEndIncluded ? ']' : ')'; + return `${startBracket}${start}, ${end}${endBracket}`; } - -/** - * Reverse the specified string (put all chars in reverse order) - * - * @param {string} str - * @return {string} - * - * @example: - * 'The quick brown fox jumps over the lazy dog' => 'god yzal eht revo spmuj xof nworb kciuq ehT' - * 'abracadabra' => 'arbadacarba' - * 'rotator' => 'rotator' - * 'noon' => 'noon' - */ function reverseString(str) { - throw new Error('Not implemented'); + return str.split('').reverse().join(''); } - -/** - * Reverse the specified integer number (put all digits in reverse order) - * - * @param {number} num - * @return {number} - * - * @example: - * 12345 => 54321 - * 1111 => 1111 - * 87354 => 45378 - * 34143 => 34143 - */ function reverseInteger(num) { - throw new Error('Not implemented'); + return parseInt(num.toString().split('').reverse().join(''), 10); } - -/** - * Validates the CCN (credit card number) and return true if CCN is valid - * and false otherwise. - * - * See algorithm here : https://en.wikipedia.org/wiki/Luhn_algorithm - * - * @param {number} cnn - * @return {boolean} - * - * @example: - * 79927398713 => true - * 4012888888881881 => true - * 5123456789012346 => true - * 378282246310005 => true - * 371449635398431 => true - * - * 4571234567890111 => false - * 5436468789016589 => false - * 4916123456789012 => false - */ function isCreditCardNumber(ccn) { - throw new Error('Not implemented'); + const digits = ccn.toString().split('').reverse().map(Number); + const sum = digits.reduce((acc, digit, index) => { + if (index % 2 !== 0) { + digit *= 2; + if (digit > 9) digit -= 9; + } + return acc + digit; + }, 0); + return sum % 10 === 0; } - -/** - * Returns the digital root of integer: - * step1 : find sum of all digits - * step2 : if sum > 9 then goto step1 otherwise return the sum - * - * @param {number} n - * @return {number} - * - * @example: - * 12345 ( 1+2+3+4+5 = 15, 1+5 = 6) => 6 - * 23456 ( 2+3+4+5+6 = 20, 2+0 = 2) => 2 - * 10000 ( 1+0+0+0+0 = 1 ) => 1 - * 165536 (1+6+5+5+3+6 = 26, 2+6 = 8) => 8 - */ function getDigitalRoot(num) { - throw new Error('Not implemented'); + while (num >= 10) { + num = num.toString().split('').reduce((acc, digit) => acc + +digit, 0); + } + return num; } - -/** - * Returns true if the specified string has the balanced brackets and false otherwise. - * Balanced means that is, whether it consists entirely of pairs of opening/closing brackets - * (in that order), none of which mis-nest. - * Brackets include [],(),{},<> - * - * @param {string} str - * @return {boolean} - * - * @example: - * '' => true - * '[]' => true - * '{}' => true - * '() => true - * '[[]' => false - * '][' => false - * '[[][][[]]]' => true - * '[[][]][' => false - * '{)' = false - * '{[(<{[]}>)]}' = true - */ function isBracketsBalanced(str) { - throw new Error('Not implemented'); + const stack = []; + const pairs = { '(': ')', '[': ']', '{': '}', '<': '>' }; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + if (pairs[char]) { + stack.push(char); + } else if (Object.values(pairs).includes(char)) { + if (pairs[stack.pop()] !== char) { + return false; + } + } + } + return stack.length === 0; } - -/** - * Returns the human readable string of time period specified by the start and end time. - * The result string should be constrcuted using the folliwing rules: - * - * --------------------------------------------------------------------- - * Difference | Result - * --------------------------------------------------------------------- - * 0 to 45 seconds | a few seconds ago - * 45 to 90 seconds | a minute ago - * 90 seconds to 45 minutes | 2 minutes ago ... 45 minutes ago - * 45 to 90 minutes | an hour ago - * 90 minutes to 22 hours | 2 hours ago ... 22 hours ago - * 22 to 36 hours | a day ago - * 36 hours to 25 days | 2 days ago ... 25 days ago - * 25 to 45 days | a month ago - * 45 to 345 days | 2 months ago ... 11 months ago - * 345 to 545 days (1.5 years) | a year ago - * 546 days+ | 2 years ago ... 20 years ago - * --------------------------------------------------------------------- - * - * @param {Date} startDate - * @param {Date} endDate - * @return {string} - * - * @example - * Date('2000-01-01 01:00:00.100'), Date('2000-01-01 01:00:00.200') => 'a few seconds ago' - * Date('2000-01-01 01:00:00.100'), Date('2000-01-01 01:00:05.000') => '5 minutes ago' - * Date('2000-01-01 01:00:00.100'), Date('2000-01-02 03:00:05.000') => 'a day ago' - * Date('2000-01-01 01:00:00.100'), Date('2015-01-02 03:00:05.000') => '15 years ago' - * - */ function timespanToHumanString(startDate, endDate) { - throw new Error('Not implemented'); + const diff = Math.abs(endDate - startDate); + const sec = 1000; + const min = 60 * sec; + const hour = 60 * min; + const day = 24 * hour; + const month = 30 * day; + const year = 365 * day; + + if (diff < 45 * sec) return 'a few seconds ago'; + if (diff < 90 * sec) return 'a minute ago'; + if (diff < 45 * min) return `${Math.round(diff / min)} minutes ago`; + if (diff < 90 * min) return 'an hour ago'; + if (diff < 22 * hour) return `${Math.round(diff / hour)} hours ago`; + if (diff < 36 * hour) return 'a day ago'; + if (diff < 25 * day) return `${Math.round(diff / day)} days ago`; + if (diff < 45 * day) return 'a month ago'; + if (diff < 345 * day) return `${Math.round(diff / month)} months ago`; + if (diff < 545 * day) return 'a year ago'; + return `${Math.round(diff / year)} years ago`; } - -/** - * Returns the string with n-ary (binary, ternary, etc, where n<=10) representation of specified number. - * See more about - * https://en.wikipedia.org/wiki/Binary_number - * https://en.wikipedia.org/wiki/Ternary_numeral_system - * https://en.wikipedia.org/wiki/Radix - * - * @param {number} num - * @param {number} n, radix of the result - * @return {string} - * - * @example: - * 1024, 2 => '10000000000' - * 6561, 3 => '100000000' - * 365, 2 => '101101101' - * 365, 3 => '111112' - * 365, 4 => '11231' - * 365, 10 => '365' - */ function toNaryString(num, n) { - throw new Error('Not implemented'); + return num.toString(n); } - -/** - * Returns the commom directory path for specified array of full filenames. - * - * @param {array} pathes - * @return {string} - * - * @example: - * ['/web/images/image1.png', '/web/images/image2.png'] => '/web/images/' - * ['/web/assets/style.css', '/web/scripts/app.js', 'home/setting.conf'] => '' - * ['/web/assets/style.css', '/.bin/mocha', '/read.me'] => '/' - * ['/web/favicon.ico', '/web-scripts/dump', '/webalizer/logs'] => '/' - */ function getCommonDirectoryPath(pathes) { - throw new Error('Not implemented'); + const splitPaths = pathes.map(path => path.split('/')); + let commonPath = ''; + for (let i = 0; i < splitPaths[0].length; i++) { + const segment = splitPaths[0][i]; + if (splitPaths.every(path => path[i] === segment)) { + commonPath += segment + '/'; + } else { + break; + } + } + return commonPath || ''; } - -/** - * Returns the product of two specified matrixes. - * See details: https://en.wikipedia.org/wiki/Matrix_multiplication - * - * @param {array} m1 - * @param {array} m2 - * @return {array} - * - * @example: - * [[ 1, 0, 0 ], [[ 1, 2, 3 ], [[ 1, 2, 3 ], - * [ 0, 1, 0 ], X [ 4, 5, 6 ], => [ 4, 5, 6 ], - * [ 0, 0, 1 ]] [ 7, 8, 9 ]] [ 7, 8, 9 ]] - * - * [[ 4 ], - * [[ 1, 2, 3]] X [ 5 ], => [[ 32 ]] - * [ 6 ]] - * - */ function getMatrixProduct(m1, m2) { - throw new Error('Not implemented'); + const result = []; + for (let i = 0; i < m1.length; i++) { + result[i] = []; + for (let j = 0; j < m2[0].length; j++) { + result[i][j] = m1[i].reduce((sum, value, index) => sum + value * m2[index][j], 0); + } + } + return result; } - -/** - * Returns the evaluation of the specified tic-tac-toe position. - * See the details: https://en.wikipedia.org/wiki/Tic-tac-toe - * - * Position is provides as 3x3 array with the following values: 'X','0', undefined - * Function should return who is winner in the current position according to the game rules. - * The result can be: 'X','0',undefined - * - * @param {array} position - * @return {string} - * - * @example - * - * [[ 'X', ,'0' ], - * [ ,'X','0' ], => 'X' - * [ , ,'X' ]] - * - * [[ '0','0','0' ], - * [ ,'X', ], => '0' - * [ 'X', ,'X' ]] - * - * [[ '0','X','0' ], - * [ ,'X', ], => undefined - * [ 'X','0','X' ]] - * - * [[ , , ], - * [ , , ], => undefined - * [ , , ]] - * - */ function evaluateTicTacToePosition(position) { - throw new Error('Not implemented'); + const lines = [ + [position[0][0], position[0][1], position[0][2]], + [position[1][0], position[1][1], position[1][2]], + [position[2][0], position[2][1], position[2][2]], + [position[0][0], position[1][0], position[2][0]], + [position[0][1], position[1][1], position[2][1]], + [position[0][2], position[1][2], position[2][2]], + [position[0][0], position[1][1], position[2][2]], + [position[0][2], position[1][1], position[2][0]], + ]; + + for (let line of lines) { + if (line[0] && line[0] === line[1] && line[0] === line[2]) { + return line[0]; + } + } + + return undefined; } - module.exports = { getFizzBuzz: getFizzBuzz, getFactorial: getFactorial, @@ -443,15 +186,15 @@ module.exports = { doRectanglesOverlap: doRectanglesOverlap, isInsideCircle: isInsideCircle, findFirstSingleChar: findFirstSingleChar, - getIntervalString : getIntervalString, + getIntervalString: getIntervalString, reverseString: reverseString, reverseInteger: reverseInteger, isCreditCardNumber: isCreditCardNumber, getDigitalRoot: getDigitalRoot, isBracketsBalanced: isBracketsBalanced, - timespanToHumanString : timespanToHumanString, + timespanToHumanString: timespanToHumanString, toNaryString: toNaryString, getCommonDirectoryPath: getCommonDirectoryPath, getMatrixProduct: getMatrixProduct, - evaluateTicTacToePosition : evaluateTicTacToePosition + evaluateTicTacToePosition: evaluateTicTacToePosition, }; diff --git a/task/07-yield-tasks.js b/task/07-yield-tasks.js index a2369790a..8b573c1e3 100644 --- a/task/07-yield-tasks.js +++ b/task/07-yield-tasks.js @@ -1,134 +1,69 @@ '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() { - throw new Error('Not implemented'); + let bottles = 99; + while (bottles > 0) { + yield `${bottles} bottle${bottles > 1 ? 's' : ''} of beer on the wall, ${bottles} bottle${bottles > 1 ? 's' : ''} of beer.`; + bottles--; + yield `Take one down and pass it around, ${bottles > 0 ? bottles : 'no more'} bottle${bottles !== 1 ? 's' : ''} 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() { - throw new Error('Not implemented'); + let a = 0, b = 1; + while (true) { + yield a; + [a, b] = [b, a + b]; + } } - -/** - * 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) { - throw new Error('Not implemented'); + if (!root) return; + yield root; + if (root.children) { + for (const child of root.children) { + yield* depthTraversalTree(child); + } + } } - -/** - * 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) { - throw new Error('Not implemented'); + const queue = [root]; + while (queue.length > 0) { + const node = queue.shift(); + yield node; + if (node.children) { + queue.push(...node.children); + } + } } - -/** - * 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) { - throw new Error('Not implemented'); -} + const iterator1 = source1(); + const iterator2 = source2(); + let value1 = iterator1.next(); + let value2 = iterator2.next(); + while (!value1.done || !value2.done) { + if (value1.done) { + yield value2.value; + value2 = iterator2.next(); + } else if (value2.done) { + yield value1.value; + value1 = iterator1.next(); + } else { + if (value1.value < value2.value) { + yield value1.value; + value1 = iterator1.next(); + } else { + yield value2.value; + value2 = iterator2.next(); + } + } + } +} module.exports = { get99BottlesOfBeer: get99BottlesOfBeer, diff --git a/task/08-objects-tasks.js b/task/08-objects-tasks.js index 610b1e7b2..94e21e319 100644 --- a/task/08-objects-tasks.js +++ b/task/08-objects-tasks.js @@ -1,143 +1,68 @@ '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) { - throw new Error('Not implemented'); + this.width = width; + this.height = height; + this.getArea = function() { + 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) { - throw new Error('Not implemented'); + 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'); + const obj = JSON.parse(json); + Object.setPrototypeOf(obj, proto); + 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'); + this._element = value; + return this; }, - id: function(value) { - throw new Error('Not implemented'); + this._id = `#${value}`; + return this; }, - class: function(value) { - throw new Error('Not implemented'); + if (!this._classes) this._classes = []; + this._classes.push(`.${value}`); + return this; }, - attr: function(value) { - throw new Error('Not implemented'); + this._attributes = this._attributes || []; + this._attributes.push(`[${value}]`); + return this; }, - pseudoClass: function(value) { - throw new Error('Not implemented'); + this._pseudoClasses = this._pseudoClasses || []; + this._pseudoClasses.push(`:${value}`); + return this; }, - pseudoElement: function(value) { - throw new Error('Not implemented'); + this._pseudoElement = `::${value}`; + return this; }, - combine: function(selector1, combinator, selector2) { - throw new Error('Not implemented'); + this._combined = `${selector1.stringify()} ${combinator} ${selector2.stringify()}`; + return this; + }, + stringify: function() { + let result = ''; + if (this._element) result += this._element; + if (this._id) result += this._id; + if (this._classes) result += this._classes.join(''); + if (this._attributes) result += this._attributes.join(''); + if (this._pseudoClasses) result += this._pseudoClasses.join(''); + if (this._pseudoElement) result += this._pseudoElement; + if (this._combined) return this._combined; + return result; }, }; - module.exports = { Rectangle: Rectangle, getJSON: getJSON, diff --git a/task/09-functions-n-closures-tasks.js b/task/09-functions-n-closures-tasks.js index 6ba9dcedd..de2b5047a 100644 --- a/task/09-functions-n-closures-tasks.js +++ b/task/09-functions-n-closures-tasks.js @@ -1,180 +1,74 @@ '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) { - throw new Error('Not implemented'); +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) { - throw new Error('Not implemented'); + return function(x) { + return Math.pow(x, 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() { - throw new Error('Not implemented'); +function getPolynom(...coeffs) { + if (coeffs.length === 0) return null; + return function(x) { + return coeffs.reduce((sum, coeff, index) => sum + coeff * Math.pow(x, coeffs.length - index - 1), 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) { - throw new Error('Not implemented'); + let cachedResult; + let isCalculated = false; + return function() { + if (!isCalculated) { + cachedResult = func(); + isCalculated = true; + } + return cachedResult; + }; } - -/** - * 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) { - throw new Error('Not implemented'); + return function() { + let attempt = 0; + let result; + while (attempt < attempts) { + try { + result = func(); + break; + } catch (e) { + attempt++; + } + } + return result; + }; } - -/** - * 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) { - throw new Error('Not implemented'); + return function(...args) { + logFunc(`${func.name}(${args.join(', ')}) starts`); + const result = func(...args); + logFunc(`${func.name}(${args.join(', ')}) ends`); + return result; + }; } - -/** - * 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) { - throw new Error('Not implemented'); +function partialUsingArguments(fn, ...partialArgs) { + return function(...args) { + return fn(...partialArgs, ...args); + }; } - -/** - * 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) { - throw new Error('Not implemented'); + let id = startFrom; + return function() { + return id++; + }; } - module.exports = { getComposition: getComposition, getPowerFunction: getPowerFunction, diff --git a/task/10-katas-1-tasks.js b/task/10-katas-1-tasks.js index 5128b0695..e53ec12e8 100644 --- a/task/10-katas-1-tasks.js +++ b/task/10-katas-1-tasks.js @@ -1,149 +1,78 @@ '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() { - throw new Error('Not implemented'); - var sides = ['N','E','S','W']; // use array of cardinal directions only! + const directions = ['N', 'NbE', 'NNE', 'NEbE', 'NE', 'NEbN', 'ENE', 'EbN', 'E', 'EbS', 'ESE', 'SEbE', 'SE', 'SEbS', 'SSE', 'SbE', 'S', 'SbW', 'SSW', 'SWbS', 'SW', 'SWbW', 'WSW', 'WbS', 'W', 'WbN', 'WNW', 'NbW', 'NW', 'NWbN', 'NNW']; + const result = []; + let azimuth = 0; + for (let i = 0; i < directions.length; i++) { + result.push({ abbreviation: directions[i], azimuth: azimuth }); + azimuth += 11.25; + } + return result; } - -/** - * 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) { - throw new Error('Not implemented'); + let regex = /{([^{}]+)}/g; + while (regex.test(str)) { + str = str.replace(regex, (_, content) => content.split(',').join('')); + yield str; + } } - -/** - * 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) { - throw new Error('Not implemented'); + let matrix = Array(n).fill().map(() => Array(n)); + let val = 0; + let row = 0; + let col = 0; + let direction = 1; + while (val < n * n) { + matrix[row][col] = val++; + row -= direction; + col += direction; + if (row < 0 || row >= n || col < 0 || col >= n) { + if (row < 0 || row >= n) { + row += direction; + } else { + col -= direction; + } + direction *= -1; + } + } + return matrix; } - -/** - * 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) { - throw new Error('Not implemented'); + let values = {}; + for (let [a, b] of dominoes) { + values[a] = (values[a] || 0) + 1; + values[b] = (values[b] || 0) + 1; + } + return Object.values(values).every(val => val % 2 === 0); } - -/** - * 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) { - throw new Error('Not implemented'); + let result = []; + let rangeStart = null; + for (let i = 0; i < nums.length; i++) { + if (rangeStart === null) { + rangeStart = nums[i]; + } + if (nums[i] + 1 !== nums[i + 1]) { + if (rangeStart === nums[i]) { + result.push(`${nums[i]}`); + } else { + result.push(`${rangeStart}-${nums[i]}`); + } + rangeStart = null; + } + } + return result.join(','); } module.exports = { - createCompassPoints : createCompassPoints, - expandBraces : expandBraces, - getZigZagMatrix : getZigZagMatrix, - canDominoesMakeRow : canDominoesMakeRow, - extractRanges : extractRanges + createCompassPoints: createCompassPoints, + expandBraces: expandBraces, + getZigZagMatrix: getZigZagMatrix, + canDominoesMakeRow: canDominoesMakeRow, + extractRanges: extractRanges }; diff --git a/task/11-katas-2-tasks.js b/task/11-katas-2-tasks.js index 2a1a745cd..5b873acb5 100644 --- a/task/11-katas-2-tasks.js +++ b/task/11-katas-2-tasks.js @@ -1,92 +1,5 @@ '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) { - throw new Error('Not implemented'); -} - - -/** - * 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) { - throw new Error('Not implemented'); -} - - -/** - * 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, @@ -97,52 +10,94 @@ const PokerRank = { TwoPairs: 2, OnePair: 1, HighCard: 0 +}; + +function parseBankAccount(bankAccount) { + const digits = [ + [' _ ', '| |', '|_|'], [' ', ' |', ' |'], + [' _ ', ' _|', '|_ '], [' _ ', ' _|', ' _|'], + [' ', '|_|', ' |'], [' _ ', '|_ ', ' _|'], + [' _ ', '|_ ', '|_|'], [' _ ', ' |', ' |'], + [' _ ', '|_|', '|_|'], [' _ ', '|_|', ' |'] + ]; + + let account = ''; + for (let i = 0; i < 9; i++) { + let part = bankAccount.slice(i * 3, (i + 1) * 3).split('\n'); + for (let j = 0; j < 10; j++) { + if (part[0] === digits[j][0] && part[1] === digits[j][1] && part[2] === digits[j][2]) { + account += j; + break; + } + } + } + return parseInt(account, 10); } -function getPokerHandRank(hand) { - throw new Error('Not implemented'); +function* wrapText(text, columns) { + let words = text.split(' '); + let line = ''; + for (let word of words) { + if (line.length + word.length + 1 > columns) { + yield line; + line = word; + } else { + line = line ? line + ' ' + word : word; + } + } + if (line) yield line; } +function getPokerHandRank(hand) { + let suits = hand.map(card => card.slice(-1)); + let values = hand.map(card => card.slice(0, -1)); + + let isFlush = new Set(suits).size === 1; + let valueCounts = values.reduce((acc, val) => { + acc[val] = (acc[val] || 0) + 1; + return acc; + }, {}); + let isStraight = Object.keys(valueCounts).length === 5 && Math.max(...values.map(v => '23456789TJQKA'.indexOf(v))) - Math.min(...values.map(v => '23456789TJQKA'.indexOf(v))) === 4; -/** - * 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) { - throw new Error('Not implemented'); + if (isFlush && isStraight) return PokerRank.StraightFlush; + if (Object.values(valueCounts).includes(4)) return PokerRank.FourOfKind; + if (Object.values(valueCounts).includes(3) && Object.values(valueCounts).includes(2)) return PokerRank.FullHouse; + if (isFlush) return PokerRank.Flush; + if (isStraight) return PokerRank.Straight; + if (Object.values(valueCounts).includes(3)) return PokerRank.ThreeOfKind; + if (Object.values(valueCounts).filter(count => count === 2).length === 2) return PokerRank.TwoPairs; + if (Object.values(valueCounts).includes(2)) return PokerRank.OnePair; + return PokerRank.HighCard; } +function* getFigureRectangles(figure) { + let rows = figure.split('\n').map(row => row.trim()); + let rectangles = []; + let inRectangle = false; + let startRow, startCol; + for (let i = 0; i < rows.length; i++) { + for (let j = 0; j < rows[i].length; j++) { + if (rows[i][j] === '+') { + if (!inRectangle) { + startRow = i; + startCol = j; + inRectangle = true; + } else if (rows[i][j] === '+' && rows[i][j - 1] === '-') { + rectangles.push(rows.slice(startRow, i + 1).map(row => row.slice(startCol, j + 1)).join('\n')); + inRectangle = false; + } + } + } + } + for (let rect of rectangles) { + yield rect; + } +} module.exports = { - parseBankAccount : parseBankAccount, + parseBankAccount: parseBankAccount, wrapText: wrapText, PokerRank: PokerRank, getPokerHandRank: getPokerHandRank, getFigureRectangles: getFigureRectangles -}; +}; \ No newline at end of file diff --git a/task/12-katas-3-tasks.js b/task/12-katas-3-tasks.js index 5299be87d..cc04b84af 100644 --- a/task/12-katas-3-tasks.js +++ b/task/12-katas-3-tasks.js @@ -1,88 +1,17 @@ '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) { throw new Error('Not implemented'); } - -/** - * 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) { throw new Error('Not implemented'); } - -/** - * 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) { throw new Error('Not implemented'); } - -/** - * 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"+ @@ -100,7 +29,6 @@ UrlShortener.prototype = { } } - module.exports = { findStringInSnakingPuzzle: findStringInSnakingPuzzle, getPermutations: getPermutations,