diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5522a36 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Node modules +node_modules/ + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Build output +dist/ +build/ +out/ +*.tsbuildinfo + +# Environment variables +.env +.env.* + +# OS files +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2181663 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-node", + "request": "launch", + "name": "Debug Active TypeScript File", + "program": "${file}", + "runtimeArgs": ["-r", "ts-node/register"], + "skipFiles": ["/**"] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bcc71c0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "github.copilot.enable": { + "javascript": false, + "typescript": false + } +} \ No newline at end of file diff --git a/README.md b/README.md index 3d2c41c..25fc9a8 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ # code_interview_prep + +## Days + +- [Day 1](day_1/day_1.md) +- [Day 2](day_2/day_2.md) +- [Day 3](day_3/day_3.md) +- [Day 4](day_4/day_4.md) +- [Day 5](day_5/day_5.md) +- [Day 6](day_6/day_6.md) +- [Day 7](day_7/day_7.md) +- [Day 8](day_8/day_8.md) +- [Day 9](day_9/day_9.md) +- [Day 10](day_10/day_10.md) +- [Day 11](day_11/day_11.md) +- [Day 12](day_12/day_12.md) +- [Day 13](day_13/day_13.md) +- [Day 14](day_14/day_14.md) diff --git a/day_1/1_solution.js b/day_1/1_solution.js deleted file mode 100644 index 8e9add8..0000000 --- a/day_1/1_solution.js +++ /dev/null @@ -1,105 +0,0 @@ -"use strict" -class Foo { - bar = 1; - - bla = () => console.log(this.bar); - - baz = function () { console.log(this.bar); }; -} - -new Foo().bla(); // day_1 -new Foo().baz(); // undefined // why day_1 - I don't understand - -///////////////////////////////////////////// - -class Parent { - foo() { - console.log('It works!'); - } -} - -class Example extends Parent {} - -function partial(classObj, mixin) { - // classObj.prototype // adding any fields or methods to class.prototype - Object.setPrototypeOf(mixin, Object.getPrototypeOf(classObj.prototype)) // setting prototype to mixin obj - Object.defineProperties(classObj.prototype, Object.getOwnPropertyDescriptors(mixin)) // define the properties from mixin to classObj -} - -partial(Example, { - foo() { - super.foo(); - console.log('Yeaaah'); - }, - - get bar() { - return Math.random(); - } -}); - -const example = new Example(); - -example.foo(); // It works! Yeaaah - -console.log(example.bar); // Случайное число -console.log(example.bar); // Случайное число -console.log(example.bar); // Случайное число - -//////////////////////// - -const format = (template, data) => { - return template.replace(/\${(.*?)}/g, (_, exportBlock) => { - return Function(...Object.keys(data), `return ${exportBlock}`)(...Object.values(data)); - }) -} - - -console.log(format('Hello ${name}! May age is ${age * day_2}.', {name: 'Bob', age: 12})); // 'Hello Bob! My age is 24.' - - -///////////// - -function allSettled(iter) { - const promises = [...iter].map((el) => Promise.resolve(el)); - - const res = new Array(promises.length); - - let total = 0; - - return new Promise((resolve) => { - promises.forEach((promise, i) => { - promise - .then((value) => { - res[i] = { - status: 'fulfilled', - value - } - - total++; - - if (total >= res.length) { - resolve(res); - } - }) - .catch((reason) => { - res[i] = { - status: 'rejected', - reason - } - - total++; - - if (total >= res.length) { - resolve(res); - } - }) - }) - }) -} - - -allSettled([1, Promise.resolve(2), Promise.reject(3)]).then(([v1, v2, v3]) => { - console.log(v1); // {status: 'fulfilled', value: day_1} - console.log(v2); // {status: 'fulfilled', value: day_2} - console.log(v3); // {status: 'rejected', reason: 3} -}); \ No newline at end of file diff --git a/day_1/2.js b/day_1/2.js deleted file mode 100644 index 0f4ad92..0000000 --- a/day_1/2.js +++ /dev/null @@ -1,129 +0,0 @@ -// allSettled([1, Promise.resolve(2), Promise.reject(3)]).then(([v1, v2, v3]) => { -// console.log(v1); // {status: 'fulfilled', value: day_1} -// console.log(v2); // {status: 'fulfilled', value: day_2} -// console.log(v3); // {status: 'rejected', reason: 3} -// }); - -// const promise1 = Promise.resolve(3); -// const promise2 = 42; -// const promise3 = new Promise((resolve, reject) => { -// setTimeout(resolve, 100, 'foo'); -// }); -// -// promiseAll([promise1, promise2, promise3]).then((values) => { -// console.log(values); -// }); -// Expected output: Array [3, 42, "foo"] - -// const promise1 = Promise.reject(0); -// const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick')); -// const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow')); -// -// const promises = [promise1, promise2, promise3]; -// -// promiseAny(promises).then((value) => console.log(value)); - -// Expected output: "quick" - -const promise1 = new Promise((resolve, reject) => { - setTimeout(resolve, 500, 'one'); -}); - -const promise2 = new Promise((resolve, reject) => { - setTimeout(resolve, 100, 'two'); -}); - -promiseRace([promise1, promise2]).then((value) => { - console.log(value); - // Both resolve, but promise2 is faster -}); -// Expected output: "two" - - -function promiseAll(promises) { - const result = new Array(promises.length); - let resolvedPromisesCount = 0; - - return new Promise((resolve, reject) => { - if (!promises || !Array.isArray(promises) || !promises?.length) { - resolve(result); - } - - promises.forEach(async (promise, index) => { - try { - const res = await Promise.resolve(promise); - result[index] = res; - resolvedPromisesCount += 1; - - if (resolvedPromisesCount === promises.length) { - resolve(result); - } - } catch (e) { - reject(e); - } - }); - }); -} - -function allSettled(promises) { - const result = new Array(promises.length); - let total = 0; - - - return new Promise((resolve) => { - promises.forEach((promise, index) => { - Promise.resolve(promise) - .then((value) => { - result[index] = {status: 'fulfilled', value} - total += 1; - - if (total === promises.length) { - resolve(result); - } - }) - .catch((reason) => { - result[index] = {status: 'rejected', reason} - total += 1; - - if (total === promises.length) { - resolve(result); - } - }) - }); - }); -} - -function promiseAny(iters) { - const promises = [...iters].map((iter) => Promise.resolve(iter)); - const rejectsArray = new Array(promises.length); - let totalRejectedCount = 0; - - return new Promise((resolve, reject) => { - if (!promises.length) { - reject(rejectsArray); - } - - promises.forEach((promise, index) => { - promise - .then((value) => resolve(value)) - .catch((reason) => { - rejectsArray[index] = reason; - totalRejectedCount += 1; - - if (totalRejectedCount === promises.length) { - reject(rejectsArray) - } - }) - }); - }); -} - -function promiseRace(iters) { - const promises = [...iters].map((iter) => Promise.resolve(iter)); - - return new Promise((resolve, reject) => { - promises.forEach((promise) => { - promise.then((value) => resolve(value)); - }) - }); -} \ No newline at end of file diff --git "a/day_1/\320\227\320\260\320\264\320\260\321\207\320\270 (1).md" b/day_1/day_1.md similarity index 65% rename from "day_1/\320\227\320\260\320\264\320\260\321\207\320\270 (1).md" rename to day_1/day_1.md index e8b809d..2cc189e 100644 --- "a/day_1/\320\227\320\260\320\264\320\260\321\207\320\270 (1).md" +++ b/day_1/day_1.md @@ -1,6 +1,6 @@ # День 1 -## Что выведется в консоль? +## 1. Что выведется в консоль? И почему? @@ -17,7 +17,7 @@ new Foo().bla(); // ? new Foo().baz(); // ? ``` -## Реализация Partial классов +## 2. Реализация Partial классов Необходимо написать функцию, которая бы позволяла расширять заданный класс новыми методами. В добавляемых методах должен корректно работать super. @@ -50,15 +50,15 @@ console.log(example.bar); // Случайное число console.log(example.bar); // Случайное число ``` -## Шаблонизатор строки с поддержкой выражений +## 3. Шаблонизатор строки с поддержкой выражений Необходимо создать функцию, которая бы принимала шаблон и объект с данными, а возвращала бы конечную строку. ```js -format('Hello ${name}! May age is ${age * day_2}.', {name: 'Bob', age: 12}); // 'Hello Bob! My age is 24.' +format('Hello ${name}! My age is ${age * 12}.', {name: 'Bob', age: 2}); // 'Hello Bob! My age is 24.' ``` -## Реализация функции аналогичной Promise.allSettled +## 4. Реализация функции аналогичной Promise.allSettled Необходимо написать функцию, которая бы повторяло поведение Promise.allSettled. @@ -69,3 +69,22 @@ allSettled([1, Promise.resolve(2), Promise.reject(3)]).then(([v1, v2, v3]) => { console.log(v3); // {status: 'rejected', reason: 3} }); ``` + +## 5. Реализация функции аналогичной Promise.any + +```js +any([Promise.reject(1), Promise.resolve(2), 3]).then((res) => { + console.log(res); // 2 +}); +``` + +## 6. Реализация функции аналогичной Promise.race + +```js +race([Promise.resolve(2), 3]).then((res) => { + console.log(res); // 2 +}); +race([Promise.reject(1), Promise.resolve(2), 3]).catch((reason) => { + console.log(reason); // 1 +}); +``` diff --git a/day_1/day_1_1.ts b/day_1/day_1_1.ts new file mode 100644 index 0000000..2cc5899 --- /dev/null +++ b/day_1/day_1_1.ts @@ -0,0 +1,36 @@ +class Foo { + bar = 1; + + bla = () => console.log(this.bar); + + baz = function () { console.log(this.bar); }; +} + +new Foo().bla(); // Выведется 1 +new Foo().baz(); // Выведется 1 + +/* + +Согласно спецификации ECMAScript 15.2.4 Runtime Semantics: InstantiateOrdinaryFunctionObject +(https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionobject), +значение "this" в обычной функции определяется в момент вызова функции, а не в момент +её создания. На это указывает пункт 3: +"Let F be OrdinaryFunctionCreate(..., FunctionBody, non-lexical-this, env, privateEnv)". +Как видим, указан "non-lexical-this". Детали вычисления "this" зависят от контекста +вызова функции и описаны в ECMAScript 13.3.6.2 (https://tc39.es/ecma262/#sec-evaluatecall). +Если функция вызывается как метод объекта, то "this" будет ссылаться на этот объект. +Если функция вызывается как обычная функция, то "this" будет ссылаться на глобальный объект +или будет undefined в строгом режиме. + +Значение "this" при вызове стрелочной функции определяется согласно спецификации +ECMAScript 15.3.4 Runtime Semantics: InstantiateArrowFunctionExpression +(https://tc39.es/ecma262/#sec-runtime-semantics-instantiatearrowfunctionexpression). +В частности, в пункте 5: Let closure be OrdinaryFunctionCreate(..., ConciseBody, lexical-this, env, privateEnv). +Видим, что указано "lexical-this". +Поэтому значение "this" в стрелочной функции определяется лексически, то есть +оно берётся из окружающего контекста, в котором функция была объявлена. + +Описание понятия лексической среды можно найти в 9.1 Environment Records +(https://tc39.es/ecma262/#sec-environment-records). + + */ diff --git a/day_1/day_1_2.ts b/day_1/day_1_2.ts new file mode 100644 index 0000000..0bfa9f2 --- /dev/null +++ b/day_1/day_1_2.ts @@ -0,0 +1,54 @@ +class Parent { + foo() { + console.log('It works!'); + } +} + +class Example extends Parent {} + +// Your code starts here + +function partial(target, obj) { + const parentPrototype = Object.getPrototypeOf(target.prototype); + Object.setPrototypeOf(obj, parentPrototype); + Object.defineProperties(target.prototype, Object.getOwnPropertyDescriptors(obj)) +} + +/* +Важный нюанс! Super работает через [[HomeObject]] (10.2 ECMAScript Function Objects). +При этом изменить [[HomeObject]] мы не можем, и, соответственно, мы вынуждены менять +прототип именно у obj, а не его копии, например. Потому что, если бы мы решили сделать +копию obj, то при копировании в неё методов, [[HomeObject]] бы всё равно указывал на obj. + +Детальнее: при объявлении метода, внутри которого используется ключевое слово +"super", у функции создаётся скрытый атрибут [[HomeObject]], который указывает +на объект, внутри которого объявлен метод. При этом, если мы скопируем +метод в другой объект, [[HomeObject]] продолжит указывать на тот же самый +объект. То есть поиск по цепочке прототипов будет происходить исходя из +[[HomeObject]], который был зафиксирован на этапе создания функции, а не на +этапе выполнения. +*/ + +// Your code ends here + +partial(Example, { + foo() { + super.foo(); + console.log('Yeaaah'); + }, + + get bar() { + return Math.random(); + } +}); + +const example = new Example(); + +example.foo(); // It works! Yeaaah + +// @ts-ignore +console.log(example.bar); // Случайное число +// @ts-ignore +console.log(example.bar); // Случайное число +// @ts-ignore +console.log(example.bar); // Случайное число \ No newline at end of file diff --git a/day_1/day_1_3.ts b/day_1/day_1_3.ts new file mode 100644 index 0000000..6d6b46c --- /dev/null +++ b/day_1/day_1_3.ts @@ -0,0 +1,13 @@ + +function format(template: string, args: Record) { + return template.replace(/\$\{([^}]+)\}/g, (_, expression: string) => { + const func = Function( + ...Object.keys(args), + `return ${expression}`, + ); + return func(...Object.values(args)); + }) +} + +let res = format('Hello ${name}! My age is ${age * 2}.', {name: 'Bob', age: 12}); +console.assert(res === 'Hello Bob! My age is 24.', `${res} !== Hello Bob! My age is 24.`); diff --git a/day_1/day_1_4.ts b/day_1/day_1_4.ts new file mode 100644 index 0000000..ca5874e --- /dev/null +++ b/day_1/day_1_4.ts @@ -0,0 +1,38 @@ +type PromiseState = { + status: 'fullfilled', + value: any, +} | { + status: 'rejected', + reason: any, +} + +function allSettled(promises: any[]): Promise { + return new Promise((resolve) => { + let count = 0; + const results: PromiseState[] = []; + for (let i = 0; i < promises.length; i++) { + Promise.resolve(promises[i]).then( + (value) => { + count += 1; + results[i] = {status: 'fullfilled', value}; + if (count === promises.length) { + resolve(results); + } + }, + (reason) => { + count += 1; + results[i] = {status: 'rejected', reason}; + if (count === promises.length) { + resolve(results); + } + } + ); + } + }); +} + +allSettled([1, Promise.resolve(2), Promise.reject(3)]).then(([v1, v2, v3]) => { + console.log(v1); // {status: 'fulfilled', value: 1} + console.log(v2); // {status: 'fulfilled', value: 2} + console.log(v3); // {status: 'rejected', reason: 3} +}); diff --git a/day_1/day_1_5.ts b/day_1/day_1_5.ts new file mode 100644 index 0000000..bffba03 --- /dev/null +++ b/day_1/day_1_5.ts @@ -0,0 +1,25 @@ +function any(promises: any[]): Promise { + let rejectedReasons: any[] = []; + return new Promise((resolve, reject) => { + for (let i = 0; i < promises.length; i++) { + Promise.resolve(promises[i]) + .then( + resolve, + (reason) => { + rejectedReasons.push(reason); + if (rejectedReasons.length === promises.length) { + reject(rejectedReasons); + } + } + ); + } + }); +} + +any([Promise.reject(1), Promise.resolve(2), 3]).then((res) => { + console.log(res); // 2 +}); + +any([Promise.reject(1), Promise.reject(2)]).catch((errors) => { + console.log(errors); // [1, 2] +}); diff --git a/day_1/day_1_6.ts b/day_1/day_1_6.ts new file mode 100644 index 0000000..476ca46 --- /dev/null +++ b/day_1/day_1_6.ts @@ -0,0 +1,26 @@ +function race(promises: any[]): Promise { + let settled = false; + return new Promise((resolve, reject) => { + for (const promise of promises) { + Promise.resolve(promise).then( + (value) => { + if (settled) return; + settled = true; + resolve(value); + }, + (reason) =>{ + if (settled) return; + settled = true; + reject(reason); + } + ); + } + }); +} + +race([Promise.resolve(2), 3]).then((res) => { + console.log(res); // 2 +}); +race([Promise.reject(1), Promise.resolve(2), 3]).then(console.log).catch((reason) => { + console.log(reason); // 1 +}); \ No newline at end of file diff --git "a/day_12/\320\227\320\260\320\264\320\260\321\207\320\270 (12).md" b/day_10/day_10.md similarity index 86% rename from "day_12/\320\227\320\260\320\264\320\260\321\207\320\270 (12).md" rename to day_10/day_10.md index 89af763..48d4b01 100644 --- "a/day_12/\320\227\320\260\320\264\320\260\321\207\320\270 (12).md" +++ b/day_10/day_10.md @@ -1,6 +1,6 @@ # День 10 -## Реализовать функцию свертки списка в диапазоны +## 1. Реализовать функцию свертки списка в диапазоны Необходимо написать функцию, которая бы принимала неотсортированный массив чисел и возвращала бы строку, где подряд идущие числа свернуты в диапазоны. @@ -8,7 +8,7 @@ console.log(reduce([1, 3, 6, 8, 7, 11, 45, 46, 2])); // 1-3, 6-8, 11, 45-46 ``` -## Параллельные асинхронные запросы +## 2. Параллельные асинхронные запросы Необходимо написать функцию, которая бы принимала Iterable функций и возвращала результат аналогичный Promise.allSettled. Каждая из переданных функций может вернуть Promise. Одновременно может быть запущено не более заданного количества Promise, но при этом максимально возможное. @@ -22,7 +22,7 @@ allSettledLimit([ ], 2).then(console.log); ``` -## Написать итератор для дерева +## 3. Написать итератор для дерева Необходимо написать итератор для заданной структуры дерева. @@ -39,7 +39,7 @@ console.log(i.next()); // {value: 4, done: false} console.log(i.next()); // {value: undefined, done: true} ``` -## Реализация функции аналогичной parseFloat +## 4. Реализация функции аналогичной parseFloat Необходимо написать функцию, которая бы повторяло поведение parseFloat. diff --git a/day_10/day_10_1.ts b/day_10/day_10_1.ts new file mode 100644 index 0000000..c6f1641 --- /dev/null +++ b/day_10/day_10_1.ts @@ -0,0 +1,20 @@ +function reduce(arr: number[]): string { + if (!arr.length) return ''; + + const sorted = arr.toSorted((a, b) => a - b); + let result: string[] = []; + let start = sorted[0]; + for (let i = 1; i <= sorted.length; i++) { + if (i === sorted.length || sorted[i] - sorted[i - 1] !== 1) { + const end = sorted[i - 1]; + if (start === end) result.push(`${start}`); + else result.push(`${start}-${end}`); + start = sorted[i]; + } + + } + return result.join(', '); +} + +console.log(reduce([1, 3, 6, 8, 7, 11, 45, 46, 2])); // 1-3, 6-8, 11, 45-46 +console.log(reduce([1, 3, 6, 8, 7, 11, 45, 80, 46, 2])); // 1-3, 6-8, 11, 45-46, 80 \ No newline at end of file diff --git a/day_10/day_10_2.ts b/day_10/day_10_2.ts new file mode 100644 index 0000000..35f305c --- /dev/null +++ b/day_10/day_10_2.ts @@ -0,0 +1,46 @@ +type PromiseFn = () => Promise; + +function allSettledLimit(fns: PromiseFn[], limit: number): Promise[]> { + const results: PromiseSettledResult[] = new Array(fns.length); + + let currentIndex = 0; + let completedCount = 0; + + return new Promise((resolve) => { + function runNext() { + if (currentIndex >= fns.length) return; + + const index = currentIndex; + currentIndex += 1; + fns[index]() + .then((value) => { + results[index] = { status: 'fulfilled', value }; + }) + .catch((reason) => { + results[index] = { status: 'rejected', reason }; + }) + .finally(() => { + completedCount += 1; + if (completedCount === fns.length) { + resolve(results); + } else { + runNext(); + } + }); + } + + const batchCount = Math.min(fns.length, limit); + for (let i = 0; i < batchCount; i++) { + runNext(); + } + }); +} + +allSettledLimit([ + () => fetch('//some-data-1'), + () => fetch('http://example.com'), + () => fetch('//some-data-3'), + () => fetch('//some-data-4') +], 2).then((results) => { + console.log(results); +}); \ No newline at end of file diff --git a/day_10/day_10_3.ts b/day_10/day_10_3.ts new file mode 100644 index 0000000..444ee6d --- /dev/null +++ b/day_10/day_10_3.ts @@ -0,0 +1,33 @@ +interface Tree { + value: T; + children?: Tree[]; +} + +function iterate(tree: Tree): Iterator { + const queue: Tree[] = []; + queue.push(tree); + return { + next() { + const currentNode = queue.shift(); + if (!currentNode) return { value: undefined, done: true }; + + if (currentNode.children?.length) { + queue.push(...currentNode?.children); + } + return { value: currentNode.value, done: false } + } + }; +} + +const i = iterate({ + value: 1, + children: [{value: 2}, {value: 3, children: [{value: 4}]}] +}); + +console.log(i.next()); // {value: 1, done: false} +console.log(i.next()); // {value: 2, done: false} +console.log(i.next()); // {value: 3, done: false} +console.log(i.next()); // {value: 4, done: false} +console.log(i.next()); // {value: undefined, done: true} + +export {}; \ No newline at end of file diff --git a/day_10/day_10_4.ts b/day_10/day_10_4.ts new file mode 100644 index 0000000..a694514 --- /dev/null +++ b/day_10/day_10_4.ts @@ -0,0 +1,56 @@ +function myParseFloat(str: string): number { + str = str.trim(); + if (!str) return NaN; + + let negative = false; + let negativeExponent = false; + let isLeftPart = true; + let exponent = false; + let left = 0; + let right = 0; + let rightCount = 0; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + if (char === '-' && i === 0) { + if (str[i + 1] === '-') return NaN; + negative = true; + } else if (char === 'e') { + if (i === 0) return NaN; + if (exponent) break; + exponent = true; + isLeftPart = false; + if (str[i + 1] === '-') { + negativeExponent = true; + i += 1; + } + } else if (char === '.') { + if (!isLeftPart) break; + isLeftPart = false; + } else if (/\d/.test(char)) { + if (isLeftPart) { + left = 10 * left + Number(char); + } else { + right = right * 10 + Number(char); + rightCount += 1; + } + } else break; + } + if (exponent) { + if (negativeExponent) return left / Math.pow(10, right) * (negative ? -1 : 1); + else return left * Math.pow(10, right) * (negative ? -1 : 1); + } + return (left + right / Math.pow(10, rightCount)) * (negative ? -1 : 1); +} + +console.log(myParseFloat('10')); // 10 +console.log(myParseFloat('-10.2')); // -10.2 +console.log(myParseFloat('-562.234')); // -562.234 +console.log(myParseFloat('6e-2')); // 0.06 +console.log(myParseFloat('6e-e-2')); // 6 +console.log(myParseFloat('6e-f--2')); // 6 +console.log(myParseFloat('--20')); // NaN +console.log(myParseFloat('')); // NaN +console.log(myParseFloat(' 123')); // 123 (ignores leading whitespace) +console.log(myParseFloat('123abc')); // 123 (stops at first invalid char) +console.log(myParseFloat('1.2.3')); // 1.2 (stops at second decimal) +console.log(myParseFloat('e5')); // NaN diff --git a/day_11/1.js b/day_11/1.js deleted file mode 100644 index 6b7efd1..0000000 --- a/day_11/1.js +++ /dev/null @@ -1,23 +0,0 @@ -console.log(bisect([1, 2, 3, 4, 5, 6, 7], (val) => 4 - val)); // 3 -console.log(bisect([1, 2, 3, 4, 5, 6, 7], (val) => 234 - val)); // -1 - -function bisect(arr, compator) { - let start = 0; - let end = arr.length - 1; - - while (start <= end) { - let mid = end - start + 1; - const result = compator(arr[mid]); - - if (result === 0) { - return mid; - } - if (result > 0) { - start = mid + 1; - } else { - end = mid - 1; - } - } - - return undefined; -} \ No newline at end of file diff --git "a/day_13/\320\227\320\260\320\264\320\260\321\207\320\270 (13).md" b/day_11/day_11.md similarity index 82% rename from "day_13/\320\227\320\260\320\264\320\260\321\207\320\270 (13).md" rename to day_11/day_11.md index 509829f..a786381 100644 --- "a/day_13/\320\227\320\260\320\264\320\260\321\207\320\270 (13).md" +++ b/day_11/day_11.md @@ -1,6 +1,6 @@ # День 11 -## Определить век по году +## 1. Определить век по году Необходимо написать функцию, которая определяет век по переданному году. @@ -8,7 +8,7 @@ console.log(getCentury(1901)); // 20 ``` -## Пересечение диапазонов +## 2. Пересечение диапазонов Необходимо написать функцию, которая бы принимала бы две строки с числовыми диапазонами и возвращала бы новую строку, где отображены пересечения этих интервалов. @@ -16,7 +16,7 @@ console.log(getCentury(1901)); // 20 console.log(intersectRanges('1-2; 4-6; 9-11', '1-5; 10-14; 15')); // 1-2; 4-5; 10-11 ``` -## Найти два элемента массива сумма которых дает заданное число +## 3. Найти два элемента массива сумма которых дает заданное число Необходимо написать функцию, которая бы принимала массив чисел и число и возвращала бы индексы двух элементов массива сумма которых даёт заданное число. @@ -25,7 +25,7 @@ console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1] console.log(twoSum([3, 2, 4], 6)); // [1, 2] ``` -## Реализовать итератор на основе EventEmitter +## 4. Реализовать итератор на основе EventEmitter Необходимо написать функцию, которая бы создавала асинхронный итератор на основе заданного EventEmitter и события. diff --git a/day_11/day_11_1.ts b/day_11/day_11_1.ts new file mode 100644 index 0000000..68c7f0b --- /dev/null +++ b/day_11/day_11_1.ts @@ -0,0 +1,6 @@ +function getCentury(year: number): number { + return Math.ceil(year / 100); +} + +console.log(getCentury(1901)); // 20 +console.log(getCentury(1900)); // 19 \ No newline at end of file diff --git a/day_11/day_11_2.ts b/day_11/day_11_2.ts new file mode 100644 index 0000000..46757f5 --- /dev/null +++ b/day_11/day_11_2.ts @@ -0,0 +1,25 @@ +function stringRangesToArray(ranges: string): number[][] { + return ranges.split('; ').map(range => { + const parts = range.split('-').map(Number); + if (parts.length === 1) parts.push(parts[0]); + return parts; + }); +} + +function intersectRanges(rangesAStr: string, rangesBStr: string): string { + const rangesA = stringRangesToArray(rangesAStr); + const rangesB = stringRangesToArray(rangesBStr); + const results: string[] = []; + for (const [aStart, aEnd] of rangesA) { + for (const [bStart, bEnd] of rangesB) { + const start = Math.max(aStart, bStart); + const end = Math.min(aEnd, bEnd); + if (start === end) results.push(`${start}`); + else if (start < end) results.push(`${start}-${end}`); + } + } + return results.join('; '); +} + +console.log('>', intersectRanges('1-2; 4-6; 9-11', '1-5; 10-14; 15')); // 1-2; 4-5; 10-11 +console.log('>', intersectRanges('1-2', '5-6')); // "" (no intersection) diff --git a/day_11/day_11_3.ts b/day_11/day_11_3.ts new file mode 100644 index 0000000..3feff9e --- /dev/null +++ b/day_11/day_11_3.ts @@ -0,0 +1,17 @@ +function twoSum(arr: number[], target: number): [number, number] | undefined { + const complements = new Map(); + + for (let i = 0; i < arr.length; i++) { + const complement = target - arr[i]; + if (complements.has(complement)) { + return [complements.get(complement)!, i]; + } + complements.set(arr[i], i); + } + + return undefined; +} + +console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1] +console.log(twoSum([3, 2, 4], 6)); // [1, 2] +console.log(twoSum([3, 3], 6)); // [0, 1] \ No newline at end of file diff --git a/day_11/day_11_4.ts b/day_11/day_11_4.ts new file mode 100644 index 0000000..2a9e567 --- /dev/null +++ b/day_11/day_11_4.ts @@ -0,0 +1,77 @@ +import { EventEmitter } from 'events'; + +const ee = new EventEmitter(); + +function stream(eventEmitter: EventEmitter, event: string): AsyncIterableIterator { + let resolver: Function | null; + let queue: any[] = []; + + eventEmitter.on(event, (data) => { + if (resolver) { + resolver({ value: data, done: false }); + resolver = null; + } else { + queue.push(data); + } + }); + + return { + [Symbol.asyncIterator]() { + return this; + }, + + next() { + if (queue.length) return Promise.resolve({ value: queue.shift(), done: false }); + return new Promise((resolve) => { + resolver = resolve; + }); + } + }; +} + +(async () => { + for await (const e of stream(ee, 'foo')) { + console.log(e); // 1 2 3 + } +})(); + +ee.emit('foo', 1); +ee.emit('foo', 2); +ee.emit('foo', 3); + +// Generator version + +const ee2 = new EventEmitter(); + +async function *streamGen(eventEmitter: EventEmitter, event: string): AsyncIterableIterator { + const queue: any[] = []; + let resolver: Function | null; + + eventEmitter.on(event, (data) => { + if (resolver) { + resolver(data); + resolver = null; + } else { + queue.push(data); + } + }); + + while (true) { + while (queue.length) { + yield queue.shift(); + } + yield new Promise(resolve => resolver = resolve); + } +} + +(async () => { + for await (const e of streamGen(ee2, 'foo')) { + console.log(e); // 11 21 31 + } +})(); + +ee2.emit('foo', 11); +ee2.emit('foo', 21); +ee2.emit('foo', 31); + +export {}; diff --git "a/day_14/\320\227\320\260\320\264\320\260\321\207\320\270 (14).md" b/day_12/day_12.md similarity index 86% rename from "day_14/\320\227\320\260\320\264\320\260\321\207\320\270 (14).md" rename to day_12/day_12.md index e8f11d5..e3d594a 100644 --- "a/day_14/\320\227\320\260\320\264\320\260\321\207\320\270 (14).md" +++ b/day_12/day_12.md @@ -1,6 +1,6 @@ # День 12 -## Определение палиндрома +## 1. Определение палиндрома Необходимо определить, является ли строка палиндромом или нет. @@ -11,7 +11,7 @@ console.log(isPalindrome('a')); // false console.log(isPalindrome('azt')); // false ``` -## Извлечение подстрок в кавычках +## 2. Извлечение подстрок в кавычках Необходимо написать функцию, которая принимает строку и возвращает массив подстроку исходной. Подстроки выбираются по признаку нахождения в тех или иных кавычках. @@ -20,7 +20,7 @@ console.log(isPalindrome('azt')); // false console.log(extractQuotes('Это строка в "кавычках\'" и `"эта"` тоже, а это "хитрая строка\\""')); // ["кавычках'", '"эта"', 'хитрая строка\\"'] ``` -## Реализовать zip для асинхронно итерируемых объектов +## 3. Реализовать zip для асинхронно итерируемых объектов Необходимо реализовать функцию zip для множества асинхронно итерируемых объектов. @@ -37,7 +37,7 @@ async function* makeAsync(iter) { })(); ``` -## Вычислить разницу двух строк +## 4. Вычислить разницу двух строк Необходимо написать функцию, которая бы вычисляла разницу между двумя заданными строками. Разница вычисляется из суммы всех добавлений, удалений и замен символов так, чтобы строки стали равны. diff --git a/day_12/day_12_1.ts b/day_12/day_12_1.ts new file mode 100644 index 0000000..9b8811c --- /dev/null +++ b/day_12/day_12_1.ts @@ -0,0 +1,16 @@ +function isPalindrome(str: string): boolean { + if (str.length <= 1) return false; + let left = 0; + let right = str.length - 1; + while (left <= right) { + if (str[left] !== str[right]) return false; + left += 1; + right -= 1; + } + return true; +} + +console.log(isPalindrome('bob')); // true +console.log(isPalindrome('abba')); // true +console.log(isPalindrome('a')); // false +console.log(isPalindrome('azt')); // false \ No newline at end of file diff --git a/day_12/day_12_2.ts b/day_12/day_12_2.ts new file mode 100644 index 0000000..c9186ed --- /dev/null +++ b/day_12/day_12_2.ts @@ -0,0 +1,37 @@ +function extractQuotes(str: string): string[] { + const allowedQuotes = new Set(['"', "'", '`']); + const results: string[] = []; + + let i = 0; + while (i < str.length) { + const char = str[i]; + + if (allowedQuotes.has(char)) { + const currentQuote = char; + let fragment = ''; + i += 1; + + while (i < str.length) { + const inQuotesChar = str[i]; + if (inQuotesChar === '\\' && i + 1 < str.length) { + fragment += inQuotesChar + str[i + 1]; + i += 2; + } else if (inQuotesChar === currentQuote) { + results.push(fragment); + i += 1; + break; + } else { + fragment += inQuotesChar; + i += 1; + } + } + } else { + i += 1; + } + } + + return results; +} + +console.log(extractQuotes('Это строка в "кавычках\'" и `"эта"` тоже, а это "хитрая строка\\""')); // ["кавычках'", '"эта"', 'хитрая строка\\"'] +console.log(extractQuotes('"test\\\\"')); // ["test\\"] \ No newline at end of file diff --git a/day_12/day_12_3.ts b/day_12/day_12_3.ts new file mode 100644 index 0000000..d581d24 --- /dev/null +++ b/day_12/day_12_3.ts @@ -0,0 +1,31 @@ +function zip(...iters: AsyncIterable[]): AsyncIterableIterator { + const iterators: AsyncIterator[] = iters.map(iter => iter[Symbol.asyncIterator]()); + return { + [Symbol.asyncIterator]() { + return this; + }, + async next() { + const nextPromises = iterators.map(iterator => iterator.next()); + const results = await Promise.all(nextPromises); + const done = results.some(({ done }) => done); + if (done) return { value: undefined, done: true }; + const combined = results.map(result => result.value); + return { value: combined, done: false }; + } + }; +} + + + +async function* makeAsync(iter) { + yield* iter; +} + +// [1, 'a', '.'] [2, 'b', '.'] +(async () => { + for await (const el of zip(makeAsync(new Set([1, 2])), makeAsync(['a', 'b', 'z']), makeAsync('...'))) { + console.log(el); + } +})(); + +export {}; diff --git a/day_12/day_12_4.ts b/day_12/day_12_4.ts new file mode 100644 index 0000000..524de11 --- /dev/null +++ b/day_12/day_12_4.ts @@ -0,0 +1,172 @@ +function levenshteinRecursive(strA: string, strB: string): number { + // Need to insert every symbol from B to A + if (!strA) return strB.length; + // Need to delete every symbol in A + if (!strB) return strA.length; + + // Do not need to do anything + if (strA.slice(-1) === strB.slice(-1)) { + return levenshteinRecursive(strA.slice(0, -1), strB.slice(0, -1)); + } + + return 1 + Math.min( + // Insert from B to A + levenshteinRecursive(strA, strB.slice(0, -1)), + // Remove from A + levenshteinRecursive(strA.slice(0, -1), strB), + // Replace in A + levenshteinRecursive(strA.slice(0, -1), strB.slice(0, -1)), + ); +} + +function levenshteinRecursiveMemoized(strA: string, strB: string): number { + const cache: Map = new Map(); + + function strsToKey(a: string, b: string): string { + return `${a}::${b}`; + } + + function recur(a: string, b: string): number { + const key = strsToKey(a, b); + if (cache.has(key)) return cache.get(key)!; + + let result: number; + + if (!a) result = b.length; + else if (!b) result = a.length; + else if (a.slice(-1) === b.slice(-1)) { + result = recur(a.slice(0, -1), b.slice(0, -1)); + } else { + result = 1 + Math.min( + recur(a, b.slice(0, -1)), + recur(a.slice(0, -1), b), + recur(a.slice(0, -1), b.slice(0, -1)), + ); + } + + cache.set(key, result); + return result; + } + + return recur(strA, strB); +} + +function levenshteinTabled(strA: string, strB: string): number { + /* + DP table format + s t r B + - b a b + s - 0 1 2 3 + t b 1 ? ? ? + r o 2 ? ? ? + A b 3 ? ? ? <- answer + */ + const table = new Array(strA.length + 1).fill(undefined).map(_ => new Array(strB.length + 1).fill(undefined)); + for (let i = 0; i <= strA.length; i++) table[i][0] = i; + for (let j = 0; j <= strB.length; j++) table[0][j] = j; + + for (let i = 1; i <= strA.length; i++) { + for (let j = 1; j <= strB.length; j++) { + const a = strA[i - 1]; + const b = strB[j - 1]; + if (a === b) table[i][j] = table[i - 1][j - 1]; + else table[i][j] = 1 + Math.min(table[i][j - 1], table[i - 1][j], table[i - 1][j - 1]); + } + } + + return table[strA.length][strB.length]; +} + + +// Test cases +const testCases = [ + // Basic examples + ['bob', 'rob', 1, 'одна замена'], + ['австрия', 'австралия', 2, 'два удаления'], + + // Edge cases + ['', '', 0, 'both empty'], + ['', 'abc', 3, 'insert all'], + ['abc', '', 3, 'delete all'], + ['a', 'a', 0, 'identical'], + + // Single character operations + ['a', 'b', 1, 'replace'], + ['a', 'ab', 1, 'insert'], + ['ab', 'a', 1, 'delete'], + + // Classic examples + ['kitten', 'sitting', 3, 'k→s, e→i, insert g'], + ['saturday', 'sunday', 3, 'sat→sun, ur→, ay→ay'], + + // Insertions only + ['cat', 'cats', 1, 'insert s'], + ['', 'hello', 5, 'insert all'], + + // Deletions only + ['hello', 'hell', 1, 'delete o'], + ['hello', '', 5, 'delete all'], + + // Replacements only + ['abc', 'xyz', 3, 'replace all'], + ['test', 'best', 1, 't→b'], + + // Mixed operations + ['intention', 'execution', 5, 'complex mixed'], + ['distance', 'editing', 5, 'complex mixed'], + + // Same length, different content + ['abcd', 'efgh', 4, 'replace all'], + ['abcd', 'abef', 2, 'replace c,d'], + + // Reversed strings + ['abc', 'cba', 2, 'a↔c'], + ['hello', 'olleh', 4, 'reverse'], + + // Repeated characters + ['aaa', 'aa', 1, 'delete one a'], + ['aa', 'aaa', 1, 'insert one a'], + ['aaa', 'bbb', 3, 'replace all'], + + // Unicode/Cyrillic + ['кот', 'код', 1, 'т→д'], + ['мама', 'папа', 2, 'м→п, м→п'], +] as const; + +// Functions to test +const functions = [ + { name: 'levenshteinRecursive', fn: levenshteinRecursive }, + { name: 'levenshteinRecursiveMemoized', fn: levenshteinRecursiveMemoized }, + { name: 'levenshteinTabled', fn: levenshteinTabled } +]; + +// Run tests +console.log('Running Levenshtein distance tests...\n'); + +for (const { name, fn } of functions) { + console.log(`Testing ${name}:`); + let passed = 0; + let failed = 0; + + for (const [strA, strB, expected, description] of testCases) { + const actual = fn(strA, strB); + const isCorrect = actual === expected; + + console.assert( + isCorrect, + `FAIL: ${name} "${strA}" → "${strB}" expected ${expected}, got ${actual} (${description})` + ); + + if (isCorrect) { + console.log(` ✓ "${strA}" → "${strB}" = ${actual} (${description})`); + passed++; + } else { + console.log(` ✗ "${strA}" → "${strB}" = ${actual}, expected ${expected} (${description})`); + failed++; + } + } + + console.log(` Results: ${passed} passed, ${failed} failed\n`); +} + +console.log('All tests completed!'); diff --git "a/day_15/\320\227\320\260\320\264\320\260\321\207\320\270 (15).md" b/day_13/day_13.md similarity index 88% rename from "day_15/\320\227\320\260\320\264\320\260\321\207\320\270 (15).md" rename to day_13/day_13.md index 8680005..00d0b8d 100644 --- "a/day_15/\320\227\320\260\320\264\320\260\321\207\320\270 (15).md" +++ b/day_13/day_13.md @@ -1,6 +1,6 @@ # День 13 -## Поиск подстроки в строке +## 1. Поиск подстроки в строке Необходимо написать функцию, которая принимала бы строку и подстроку и возвращала бы true, если заданная подстрока присутствует в строке. Регулярные выражения использовать нельзя. @@ -10,7 +10,7 @@ console.log(includes('hello bob!', 'bob')); // true console.log(includes('abba', 'aba')); // false ``` -## Ломающий коммит +## 2. Ломающий коммит Имеется множество коммитов и функция проверки работы программы. На одном из коммитов программа начинает ломаться. Необходимо выяснить за минимальное время этот коммит. @@ -23,7 +23,7 @@ const test = (commit) => commit === 'good'; console.log(findFirstBadCommit(commits, test)); // 3 ``` -## Максимальная подстрока без повторений +## 3. Максимальная подстрока без повторений Необходимо написать функцию, которая принимала бы строку и возвращала бы максимальную подстроку без повторяющихся символов. @@ -34,7 +34,7 @@ console.log(maxUniqueSubstr('bbbbb')); // b console.log(maxUniqueSubstr('pwwkew')); // wke ``` -## Генерация анаграмм +## 4. Генерация анаграмм Необходимо создать функцию, которая бы принимала строку и возвращала список всех анаграмм этого слова. diff --git a/day_13/day_13_1.ts b/day_13/day_13_1.ts new file mode 100644 index 0000000..52d7486 --- /dev/null +++ b/day_13/day_13_1.ts @@ -0,0 +1,77 @@ +function includesBruteforce(haystack: string, needle: string): boolean { + for (let i = 0; i <= haystack.length - needle.length; i++) { + let isSubstring = true; + for (let j = 0; j < needle.length; j++) { + if (haystack[i + j] !== needle[j]) { + isSubstring = false; + break; + } + } + if (isSubstring) return true; + } + return false; +} + +console.log('# Brute force'); +console.log(includesBruteforce('adsgwadsxdsgwadsgz', 'dsgwadsgz')); // true +console.log(includesBruteforce('aaaxaaax', 'aaxa')); // true +console.log(includesBruteforce('aaaxaaax', 'aaaa')); // false +console.log(includesBruteforce('hello bob!', 'bob')); // true +console.log(includesBruteforce('abba', 'aba')); // false +console.log(includesBruteforce('abc', 'c')); // true +console.log(includesBruteforce('aaaaaaaaab', 'aaaab')); // true + +// Knuth-Morris-Pratt algorithm +function includesKMP(haystack: string, needle: string): boolean { + /* + Calculate longest prefix that is also a suffix for needle + aaaxaaaa => [a=0, aa=1, aaa=2, aaax=0, aaaxa=1, aaaxaa=2, aaaxaaa=3, aaaxaaaa=3] = [0,1,2,0,1,2,3,3] + */ + const lps: number[] = [0]; + let lpsIndex = 0; + let needleIndex = 1; + while (needleIndex < needle.length) { + if (needle[lpsIndex] === needle[needleIndex]) { + lps[needleIndex] = lpsIndex + 1; + needleIndex += 1; + lpsIndex += 1; + } else { + if (lpsIndex === 0) { + lps[needleIndex] = 0; + needleIndex += 1; + } else { + lpsIndex = lps[lpsIndex - 1]; + } + } + } + + // Now find substring + let haystackIndex = 0; + needleIndex = 0; + while (haystackIndex < haystack.length) { + if (haystack[haystackIndex] === needle[needleIndex]) { + haystackIndex += 1; + needleIndex += 1; + if (needleIndex === needle.length) { + return true; + } + } else { + if (needleIndex === 0) { + haystackIndex += 1; + } else { + needleIndex = lps[needleIndex - 1]; + } + } + } + + return false; +} + +console.log('# KMP'); +console.log(includesKMP('adsgwadsxdsgwadsgz', 'dsgwadsgz')); // true +console.log(includesKMP('aaaxaaax', 'aaxa')); // true +console.log(includesKMP('aaaxaaax', 'aaaa')); // false +console.log(includesKMP('hello bob!', 'bob')); // true +console.log(includesKMP('abba', 'aba')); // false +console.log(includesKMP('abc', 'c')); // true +console.log(includesKMP('aaaaaaaaab', 'aaaab')); // true diff --git a/day_13/day_13_2.ts b/day_13/day_13_2.ts new file mode 100644 index 0000000..1b3ab0e --- /dev/null +++ b/day_13/day_13_2.ts @@ -0,0 +1,24 @@ +function findFirstBadCommit(commits: T[], comparator: (T) => boolean): number { + let left = 0; + let right = commits.length - 1; + let firstBadIndex = -1; + while (left <= right) { + const middle = Math.floor(left + (right - left) / 2); + const isGood = comparator(commits[middle]); + if (isGood) { + left = middle + 1; + } else { + right = middle - 1; + firstBadIndex = middle; + } + } + return firstBadIndex; +} + +const commits = ['good', 'good', 'good', 'bad', 'bad', 'bad', 'bad', 'bad', 'bad']; + +const test = (commit) => commit === 'good'; + +console.log(findFirstBadCommit(commits, test)); // 3 +console.log(findFirstBadCommit(['good', 'good'], test)); // -1 +console.log(findFirstBadCommit(['bad'], test)); // 0 diff --git a/day_13/day_13_3.ts b/day_13/day_13_3.ts new file mode 100644 index 0000000..c133ade --- /dev/null +++ b/day_13/day_13_3.ts @@ -0,0 +1,33 @@ +function maxUniqueSubstr(str: string): string { + if (str.length <= 1) return str; + + let left = 0; + let right = 0; + const lastIndexByChar: Map = new Map(); + let bestLeft = 0; + let bestLength = 0; + while (right < str.length) { + const currentChar = str[right]; + if (lastIndexByChar.has(currentChar) && lastIndexByChar.get(currentChar)! >= left) { + left = lastIndexByChar.get(currentChar)! + 1; + } + + lastIndexByChar.set(currentChar, right); + + if (right - left + 1 > bestLength) { + bestLeft = left; + bestLength = right - left + 1; + } + + right += 1; + } + + return str.slice(bestLeft, bestLeft + bestLength); +} + +console.log(maxUniqueSubstr('acbabcbb')); // acb +console.log(maxUniqueSubstr('aab')); // ab +console.log(maxUniqueSubstr('abcabcbb')); // abc +console.log(maxUniqueSubstr('bbbbb')); // b +console.log(maxUniqueSubstr('pwwkew')); // wke +console.log(maxUniqueSubstr('abba')); // ab \ No newline at end of file diff --git a/day_13/day_13_4.ts b/day_13/day_13_4.ts new file mode 100644 index 0000000..61aa960 --- /dev/null +++ b/day_13/day_13_4.ts @@ -0,0 +1,58 @@ +function getAnagram(str: string): string[] { + const chars = str.split('').sort(); + const used = new Array(chars.length).fill(false); + const results: string[] = []; + + function recur(permutation: string[]) { + if (permutation.length === chars.length) { + results.push(permutation.join('')); + return; + } + for (let i = 0; i < used.length; i++) { + if (used[i]) continue; + if (i > 0 && chars[i] === chars[i - 1] && !used[i - 1]) continue; + used[i] = true; + permutation.push(chars[i]); + recur(permutation); + used[i] = false; + permutation.pop(); + } + } + + recur([]); + return results; +} + +console.log(getAnagram('cat')); // ['act', 'atc', 'cat', 'cta', 'tac', 'tca'] +console.log(getAnagram('aab')); // ['aab', 'aba', 'baa'] +console.log(getAnagram('aba')); // ['aab', 'aba', 'baa'] + +function getAnagramFreq(str: string): string[] { + const chars = str.split('').sort(); + const freqByChar: Map = new Map(); + chars.forEach(char => freqByChar.set(char, freqByChar.has(char) ? freqByChar.get(char)! + 1 : 1)); + const results: string[] = []; + + function recur(permutation: string[]) { + if (permutation.length === chars.length) { + results.push(permutation.join('')); + return; + } + + for (const [char, freq] of freqByChar) { + if (freq <= 0) continue; + freqByChar.set(char, freq - 1); + permutation.push(char); + recur(permutation); + freqByChar.set(char, freq); + permutation.pop(); + } + } + + recur([]); + return results; +} + +console.log(getAnagramFreq('cat')); // ['act', 'atc', 'cat', 'cta', 'tac', 'tca'] +console.log(getAnagramFreq('aab')); // ['aab', 'aba', 'baa'] +console.log(getAnagramFreq('aba')); // ['aab', 'aba', 'baa'] diff --git "a/day_16/\320\227\320\260\320\264\320\260\321\207\320\270 (16).md" b/day_14/day_14.md similarity index 88% rename from "day_16/\320\227\320\260\320\264\320\260\321\207\320\270 (16).md" rename to day_14/day_14.md index 723f51f..bef9f94 100644 --- "a/day_16/\320\227\320\260\320\264\320\260\321\207\320\270 (16).md" +++ b/day_14/day_14.md @@ -1,6 +1,6 @@ # День 14 -## Сжатие глубокого объекта +## 1. Сжатие глубокого объекта Необходимо написать функцию, которая бы сжимала некоторый глубокий объект в плоский вид. @@ -16,7 +16,7 @@ const obj = { console.log(collapse(obj)); ``` -## Валидация скобочных групп +## 2. Валидация скобочных групп Необходимо написать функцию, которая бы принимала строку и возвращала true, если у каждого из символов `{`, `[` и `(` есть своя закрывающая пара и они стоят в правильной последовательности. @@ -26,7 +26,7 @@ console.log(isValid('(hello{world)} and [me])')); // false console.log(isValid(')')); // false ``` -## Самый большой палиндром в строке +## 3. Самый большой палиндром в строке Необходимо написать функцию, которая бы принимала строку и возвращала подстроку с самым большим палиндромом в строке. @@ -35,7 +35,7 @@ console.log(findPalindromicSubstring('adaabbabla')); // 'abba' console.log(findPalindromicSubstring('blablur')); // null ``` -## Реализация очереди с приоритетом +## 4. Реализация очереди с приоритетом Необходимо написать класс, который бы предоставлял API очереди, но с возможности задания функции-компаратора для сортировки на вставке. diff --git a/day_14/day_14_1.ts b/day_14/day_14_1.ts new file mode 100644 index 0000000..186e30d --- /dev/null +++ b/day_14/day_14_1.ts @@ -0,0 +1,41 @@ +function collapse(obj: Record): Record { + const result: Record = {}; + + function recur(node: any, path: string = '') { + if (node !== null && typeof node === 'object' && node.constructor === Object) { + for (const prop of Object.getOwnPropertyNames(node)) { + recur(node[prop], path ? `${path}.${prop}` : prop); + } + } else if (Array.isArray(node)) { + for (let i = 0; i < node.length; i++) { + recur(node[i], path ? `${path}.${i}` : i.toString()) + } + } else { + result[path] = node; + } + } + + recur(obj); + + return result; +} + +const obj = { + a: { + b: [1, 2], + '': {c: 2} + } +}; + +/* {'a.b.0': 1, 'a.b.1': 2, 'a..c': 2} */ +console.log(collapse(obj)); + +const complexObj = { + a: { + b: [1, {c: 3}, 2], + '': {d: 4} + } +}; + +// {'a.b.0': 1, 'a.b.1.c': 3, 'a.b.2': 2, 'a..d': 4} +console.log(collapse(complexObj)); diff --git a/day_14/day_14_2.ts b/day_14/day_14_2.ts new file mode 100644 index 0000000..23274f5 --- /dev/null +++ b/day_14/day_14_2.ts @@ -0,0 +1,26 @@ +function isValid(str: string): boolean { + const stack: string[] = []; + const matchingParentheses = { + '}': '{', + ')': '(', + ']': '[', + }; + + for (const char of str) { + if (char === '{' || char === '(' || char === '[') { + stack.push(char); + } else if (matchingParentheses[char]) { + const lastParenthesis = stack.pop(); + if (lastParenthesis !== matchingParentheses[char]) return false; + } + } + + return stack.length === 0; +} + +console.log(isValid('(hello{world} and [me])')); // true +console.log(isValid('(hello{world)} and [me])')); // false +console.log(isValid(')')); // false +console.log(isValid('(hello')); // false + +export {}; \ No newline at end of file diff --git a/day_14/day_14_3.ts b/day_14/day_14_3.ts new file mode 100644 index 0000000..fc0a56c --- /dev/null +++ b/day_14/day_14_3.ts @@ -0,0 +1,124 @@ +function findPalindromicSubstring(str: string): string | null { + if (str.length < 2) return null; + + function expand(left: number, right: number): [number, number] { + while (left >= 0 && right < str.length) { + if (str[left] === str[right]) { + left -= 1; + right += 1; + } else break; + } + return [left + 1, right - 1]; + } + + let bestLen = 0; + let bestStart = 0; + let left = 0; + while (left < str.length) { + let [pLeft, pRight] = expand(left, left); + let len = pRight - pLeft + 1; + if (len > bestLen) { + bestLen = len; + bestStart = pLeft; + } + + if (left + 1 < str.length) { + [pLeft, pRight] = expand(left, left + 1); + len = pRight - pLeft + 1; + if (len > bestLen) { + bestLen = len; + bestStart = pLeft; + } + } + + left += 1; + } + return bestLen >= 2 ? str.slice(bestStart, bestStart + bestLen) : null; +} + +/** + * str = i a i a d a i a d a k + * diams = 1 3 3 1 7 1 7 1 3 1 1 + */ +function findPalindromicSubstringManachers(str: string): string | null { + const str_prime = `#${str.split('').join('#')}#`; // Make str always odd len + const diams = new Array(str_prime.length).fill(0); // Palindrome lengths at center i + let center = -1; // Center index of a palindrome with the most extended right border + let right_border = -1; // Inclusive right border + let longestPalindromeCenter = -1; + let longestPalindromeLen = 0; + for (let i = 0; i < str_prime.length; i++) { + let left = i - 1; + let right = i + 1; + if (i <= right_border) { + // Current center is inside another palindrome + const mirrorI = center - (i - center); + const mirrorRadius = diams[mirrorI]; + const distanceToRightBorder = right_border - i; + const containedILen = distanceToRightBorder*2 + 1; + if (mirrorRadius < containedILen) { + // If a mirror palindrome is not touching the border of the containing palindrome + diams[i] = mirrorRadius; + continue; + } else { + // If a mirror palindrome is touching the border of the containing palindrome + left = i - distanceToRightBorder - 1; + right = i + distanceToRightBorder + 1; + } + } + { + // Expand + while (left >= 0 && right < str_prime.length && str_prime[left] === str_prime[right]) { + left -= 1; + right += 1; + } + left += 1; + right -= 1; + const len = right - left + 1; + diams[i] = len; + + // Update center + if (right_border < right) { + center = i; + right_border = right; + } + + if (len > longestPalindromeLen) { + longestPalindromeCenter = i; + longestPalindromeLen = len; + } + } + } + + const realCenter = Math.floor(longestPalindromeCenter / 2); + const realLen = Math.floor(longestPalindromeLen / 2); + const left = realCenter - Math.floor(realLen / 2); + return realLen > 1 + ? str.slice(left, left + realLen) + : null; +} + + +for (const fn of [findPalindromicSubstring, findPalindromicSubstringManachers]) { + console.log('Testing', fn.name); + [ + ["iaiadaiadak", "aiadaia"], + ["", null], + ["a", null], + ["aa", "aa"], + ["ab", null], + ["aba", "aba"], + ["abba", "abba"], + ["cbbd", "bb"], + ["babad", "bab"], // или "aba" — обе ок + ["forgeeksskeegfor", "geeksskeeg"], + ["abcda", null], // длины 1 не считаем + ["aaaabaaa", "aaabaaa"], + ].forEach(([s, expect]) => { + const got = fn(s as string); + console.log(s, "=>", got, "✓", expect === null ? got === null : got === expect || (s==="babad" && (got==="bab"||got==="aba"))); + }); + console.log('\n\n'); +} + +export {}; \ No newline at end of file diff --git a/day_14/day_14_4.ts b/day_14/day_14_4.ts new file mode 100644 index 0000000..0abb11e --- /dev/null +++ b/day_14/day_14_4.ts @@ -0,0 +1,98 @@ +type Comparator = (a: T, b: T) => number; + +class OrderedQueue { + #comparator: Comparator + /* + Examples: + Max heap as complete binary tree: + 9 level 0 + / \ + 5 7 level 1 + / \ / + 3 1 4 level 2 + As an array: + Root Left1 Right1 Left1.L Left1.R Right1.L + [ 9 5 7 3 1 4 ] + 0 1 2 3 4 5 + [9 5 7 3 1 4] + */ + #heap: T[] + + constructor(comparator: (a: T, b: T) => number) { + this.#comparator = comparator; + this.#heap = []; + } + + push(element: T) { + let index = this.#heap.length; + this.#heap.push(element); + while (index !== 0) { + const parentIndex = this.#heapGetParentIndex(index); + if (this.#comparator(this.#heap[parentIndex], element) < 0) { + [this.#heap[index], this.#heap[parentIndex]] = [this.#heap[parentIndex], this.#heap[index]]; + index = parentIndex; + } else break; + } + } + + pop(): T | undefined { + if (!this.#heap.length) return undefined; + + const result = this.#heap[0]; + const last = this.#heap.pop()!; + const size = this.#heap.length; + if (size > 0) { + this.#heap[0] = last + let index = 0; + while (index < size) { + const leftIndex = this.#heapGetLeftIndex(index); + const rightIndex = this.#heapGetRightIndex(index); + if (leftIndex >= size) break; + let swapIndex = leftIndex; + if (rightIndex < size && this.#comparator(this.#heap[leftIndex], this.#heap[rightIndex]) < 0) { + swapIndex = rightIndex; + } + if (this.#comparator(this.#heap[index], this.#heap[swapIndex]) < 0) { + [this.#heap[index], this.#heap[swapIndex]] = [this.#heap[swapIndex], this.#heap[index]]; + index = swapIndex; + } else break; + } + } + + return result; + } + + #heapGetLeftIndex(index: number) { + return 2 * index + 1; + } + + #heapGetRightIndex(index: number) { + return 2 * index + 2; + } + + #heapGetParentIndex(index: number) { + return (index - 1) >> 1; + } +} + +const queue = new OrderedQueue((a, b) => a - b); + +queue.push(1); +console.log(queue.pop()); // 1 +queue.push(1); +queue.push(5); +queue.push(2); +queue.push(-1); +queue.push(5); +queue.push(2); +queue.push(-1); +queue.push(5); + +console.log(queue.pop()); // 5 +console.log(queue.pop()); // 5 + +console.log(queue.pop()); // 5 +console.log(queue.pop()); // 2 +console.log(queue.pop()); // 2 + +export {}; diff --git a/day_2/1.js b/day_2/1.js deleted file mode 100644 index f8fb104..0000000 --- a/day_2/1.js +++ /dev/null @@ -1,13 +0,0 @@ -var val = Promise.resolve(1); - -var arr = [1, 2, 3]; - -for (var i = 0; i < arr.length; ++i) { - (function(i) { - val = val.then((val) => val + arr[i]); - })(i) -} - -val.then(console.log); // NaN - -let o = 0 diff --git a/day_2/2.js b/day_2/2.js deleted file mode 100644 index 586a48a..0000000 --- a/day_2/2.js +++ /dev/null @@ -1,24 +0,0 @@ -const obj = {}; - -function setByPath(obj, keyString, value) { - if (!obj) return; - - const keys = keyString.split('.'); - - if (!keys) return; - - keys.forEach((key, index) => { - if (index === keys.length - 1) { - obj[key] = value; - } else { - obj[key] ??= {}; - obj = obj[key]; - } - }) -} - -setByPath(obj, 'foo.bar', 1); -setByPath(obj, 'foo.bla', 2); -setByPath(obj, 'foo.baz.bla', 3); - -console.log(obj); // {foo: {bar: day_1, bla: day_2}} \ No newline at end of file diff --git a/day_2/3.js b/day_2/3.js deleted file mode 100644 index 27cd817..0000000 --- a/day_2/3.js +++ /dev/null @@ -1,35 +0,0 @@ -const obj = { - value: 'foo', - children: [ - { - value: 'bar' - }, - - { - value: 'bla', - children: [{value: 'baz'}] - } - ] -}; - -console.log(maxDepth(obj)); // day_2 - -function maxDepth(root) { - let max = 0; - - function calculateMaxDepth(root, currentDepth = 0) { - root?.children?.forEach((node) => { - const depth = calculateMaxDepth(node, currentDepth + 1); - - if (depth > max) { - max = depth; - } - }); - - return currentDepth; - } - - calculateMaxDepth(root, 0); - - return max; -} \ No newline at end of file diff --git a/day_2/4.js b/day_2/4.js deleted file mode 100644 index 4290591..0000000 --- a/day_2/4.js +++ /dev/null @@ -1,27 +0,0 @@ -// 101 -// day_1 * day_2 ** day_2 + 0 * day_2 ** day_1 + day_1 * day_2 ** 0 - -const alphabet = new Map(); -const alphabets = { - 36: alphabet -}; - -for (let i = '0'.codePointAt(0), j = 0; i <= '9'.codePointAt(9); i + 1, j + 1) { - alphabet.set(String.fromCodePoint(i), j); -} - -for (let i = 'A'.codePointAt(0), j = 10; i <= 'Z'.codePointAt(0); i + 1, j + 1) { - alphabet.set(String.fromCodePoint(i), j); -} - - - - -function parseInt() { - -} - -console.log(myParseInt('10')); // 10 -console.log(myParseInt('-10', 2)); // -day_2 -console.log(myParseInt('FFP', 16)); // 255 -console.log(myParseInt('--20')); // NaN \ No newline at end of file diff --git "a/day_2/\320\227\320\260\320\264\320\260\321\207\320\270 (2).md" b/day_2/day_2.md similarity index 74% rename from "day_2/\320\227\320\260\320\264\320\260\321\207\320\270 (2).md" rename to day_2/day_2.md index 70dd12b..63798b7 100644 --- "a/day_2/\320\227\320\260\320\264\320\260\321\207\320\270 (2).md" +++ b/day_2/day_2.md @@ -1,6 +1,6 @@ # День 2 -## Что выведется в консоль? +## 1. Что выведется в консоль? И почему? @@ -16,7 +16,7 @@ for (var i = 0; i < arr.length; ++i) { val.then(console.log); // ? ``` -## Установка свойства по сложному пути в объекте +## 2. Установка свойства по сложному пути в объекте Необходимо написать функцию, которая бы устанавливало переданное значение объекту по заданному пути. @@ -26,10 +26,10 @@ const obj = {}; setByPath(obj, 'foo.bar', 1); setByPath(obj, 'foo.bla', 2); -console.log(obj); // {foo: {bar: day_1, bla: day_2}} +console.log(obj); // {foo: {bar: 1, bla: 2}} ``` -## Нахождение максимальной глубины в дереве +## 3. Нахождение максимальной глубины в дереве Необходимо написать функцию, которая бы возвращала максимальную глубину заданного дерева. @@ -48,10 +48,10 @@ const obj = { ] }; -console.log(maxDepth(obj)); // day_2 +console.log(maxDepth(obj)); // 2 ``` -## Реализация функции аналогичной parseInt +## 4. Реализация функции аналогичной parseInt Необходимо написать функцию, которая бы повторяло поведение parseInt. diff --git a/day_2/day_2_1.ts b/day_2/day_2_1.ts new file mode 100644 index 0000000..770c65b --- /dev/null +++ b/day_2/day_2_1.ts @@ -0,0 +1,22 @@ +var val = Promise.resolve(1); + +var arr = [1, 2, 3]; + +for (var i = 0; i < arr.length; ++i) { + val = val.then((val) => val + arr[i]); +} + +val.then(console.log); // NaN + +/* +var i создаёт переменную в области видимости внешней функции, таким образом, +значение переменной всегда одно и то же для всех вызовов then. В данном +случае сначала цикл завершается, i === 3. Потом начинают выполняться +микротаски, вызываться колбэки then. Происходит сложение 1 + arr[3], что +равнозначно 1 + undefined == NaN. Затем в следующему колбэке +NaN + undefined == NaN и т.д. В итоге, получаем NaN. + +В случае использования let, для каждой итерации цикла фиксируется новая +лексическая среда с текущим значением переменной цикла согласно +спецификации 14.7.5.7 ForIn/OfBodyEvaluation. +*/ diff --git a/day_2/day_2_2.ts b/day_2/day_2_2.ts new file mode 100644 index 0000000..b7df83c --- /dev/null +++ b/day_2/day_2_2.ts @@ -0,0 +1,22 @@ +function setByPath(obj: Record, path: string, value: any) { + const keys = path.split('.'); + let currentObj = obj; + for (let i = 0; i < keys.length; i++) { + if (i === keys.length - 1) { + currentObj[keys[i]] = value; + } else if (!currentObj.hasOwnProperty(keys[i])) { + currentObj[keys[i]] = {}; + } + currentObj = currentObj[keys[i]]; + } +} + + +const obj = {}; + +setByPath(obj, 'foo.bar', 1); +setByPath(obj, 'foo.bla', 2); + +console.log(JSON.stringify(obj)); // {foo: {bar: 1, bla: 2}} + +export {}; diff --git a/day_2/day_2_3.ts b/day_2/day_2_3.ts new file mode 100644 index 0000000..e5cd7dd --- /dev/null +++ b/day_2/day_2_3.ts @@ -0,0 +1,29 @@ +interface Tree { + value: any; + children?: Tree[]; +} + +function maxDepth(obj: Tree, rootPoints: 0 | 1 = 0): number { + if (!obj.children || obj.children.length === 0) { + return 1; + } + return rootPoints + Math.max(...obj.children.map((child) => maxDepth(child, 1))); +} + +const obj: Tree = { + value: 'foo', + children: [ + { + value: 'bar' + }, + + { + value: 'bla', + children: [{value: 'baz'}] + } + ] +}; + +console.log(maxDepth(obj)); // 2 + +export {}; diff --git a/day_2/day_2_4.ts b/day_2/day_2_4.ts new file mode 100644 index 0000000..fab1587 --- /dev/null +++ b/day_2/day_2_4.ts @@ -0,0 +1,52 @@ +function myParseInt(num: string, base: 2 | 10 | 16 = 10): number { + num = num.trim(); + const negative = num[0] === '-'; + const startingIndex = negative ? 1 : 0; + + const allowedSymbols = { + 2: {0: 0, 1: 1}, + 10: { + 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, + 7: 7, 8: 8, 9: 9, + }, + 16: { + 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, + 7: 7, 8: 8, 9: 9, 'A': 10, 'B': 11, 'C': 12, + 'D': 13, 'E': 14, 'F': 15, + }, + }; + + let usableLength = 0; + let startsWithBadSymbol = true; + for (let i = startingIndex; i < num.length; i++) { + const symbol = num[i].toUpperCase(); + if (!allowedSymbols[base].hasOwnProperty(symbol)) { + if (startsWithBadSymbol) { + return NaN; + } else { + break; + } + } + startsWithBadSymbol = false; + usableLength += 1; + } + if (usableLength < 1) { + return NaN; + } + + let currentNumber: number = 0; + let currentPlaceValuePower = usableLength - 1; + for (let i = startingIndex; i < startingIndex + usableLength; i++) { + const symbol = num[i].toUpperCase(); + const placeValue = Math.pow(base, currentPlaceValuePower); + currentNumber += allowedSymbols[base][symbol] * placeValue; + currentPlaceValuePower -= 1; + } + return negative ? -currentNumber : currentNumber; +} + +console.log(myParseInt('10')); // 10 +console.log(myParseInt('-10', 2)); // -2 +console.log(myParseInt('FFP', 16)); // 255 +console.log(myParseInt('--20')); // NaN +console.log(myParseInt('')); // NaN \ No newline at end of file diff --git a/day_2/mapSet.js b/day_2/mapSet.js deleted file mode 100644 index f4dbdd6..0000000 --- a/day_2/mapSet.js +++ /dev/null @@ -1,30 +0,0 @@ -function unique(arr) { - return [...new Set(arr).values()].join(','); -} - -let values = ["Hare", "Krishna", "Hare", "Krishna", - "Krishna", "Krishna", "Hare", "Hare", ":-O" -]; - -// console.log( unique(values) ); // Hare,Krishna,:-O - -let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; - -function aclean(arr) { - const map = new Map(); - - arr.toReversed().forEach((el) => { - const sortedElement = el - .toLowerCase() - .split('') - .sort() - .join(''); - - map.set(sortedElement, el); - } - ); - - return [...map.values()].join(','); -} - -console.log(aclean(arr)); // "nap,teachers,ear" или "PAN,cheaters,era" \ No newline at end of file diff --git "a/day_4/\320\227\320\260\320\264\320\260\321\207\320\270 (4).md" b/day_3/day_3.md similarity index 82% rename from "day_4/\320\227\320\260\320\264\320\260\321\207\320\270 (4).md" rename to day_3/day_3.md index 944fad1..949cf12 100644 --- "a/day_4/\320\227\320\260\320\264\320\260\321\207\320\270 (4).md" +++ b/day_3/day_3.md @@ -1,6 +1,6 @@ # День 3 -## Что и в каком порядке выведется в консоль? +## 1. Что и в каком порядке выведется в консоль? И почему? @@ -24,7 +24,7 @@ new Promise((resolve) => { console.log('bak'); ``` -## Реализовать функцию setImmediate +## 2. Реализовать функцию setImmediate Необходимо функцию, которая бы предоставляла API схожее с setTimeout, но создавала бы микротаску. @@ -44,7 +44,7 @@ const timer = setImmediate(() => { clearImmediate(timer); ``` -## Реализация функции каррирования +## 3. Реализация функции каррирования Необходимо написать функцию, которая бы принимала другую функцию и возвращала её каррированную версию. @@ -56,7 +56,7 @@ console.log(sum(1)(2)(3, 4)); // 10; console.log(sum(1)(2, 3, 4)); // 10; ``` -## Реализовать zip для синхронных Iterable объектов +## 4. Реализовать zip для синхронных Iterable объектов Общее количество кортежей берется по минимальному значению. Функция должна возвращать IterableIterator. diff --git a/day_3/day_3_1.ts b/day_3/day_3_1.ts new file mode 100644 index 0000000..9cd6ade --- /dev/null +++ b/day_3/day_3_1.ts @@ -0,0 +1,42 @@ +console.log('foo'); + +setTimeout(() => { + console.log('bar'); +}, 0); + +queueMicrotask(() => { + console.log('baz'); + Promise.resolve().then().then(() => console.log('ban')); +}); + +new Promise((resolve) => { + console.log('bla'); + resolve('baf'); +}).then(console.log); + +console.log('bak'); + +// foo bla bak baz baf ban bar + +/* +Поведение Event Loop зафиксировано в стандарте HTML 8.1.7.3 Processing model +https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model + +В ECMAScript 9.5 Jobs and Host Operations to Enqueue Jobs https://tc39.es/ecma262/#sec-jobs +также зафиксированы некоторые операции. + +Из этих спецификаций можно вывести примерный алгоритм работы Event Loop: + +1. Если есть хотя бы одна очередь тасок с доступной для выполнения таской: + 1. Выбрать из одной такой очереди (из какой именно, зависит от реализации) самую старую таску; + 2. Выполнить эту таску; + 3. Выполнить все микротаски, сначала самые старые; +2. Повторить всё сначала. + +Есть одна очередь микротасков, но много очередей тасков (зависит от реализации). +К микротаскам относятся колбэки промисов, queueMicrotask и MutationObserver. + +Что касается process.nextTick в node.js, то он будет выполнен сразу после +завершения любой операции Event Loop. + +*/ \ No newline at end of file diff --git a/day_3/day_3_2.ts b/day_3/day_3_2.ts new file mode 100644 index 0000000..8e1b194 --- /dev/null +++ b/day_3/day_3_2.ts @@ -0,0 +1,43 @@ +interface MySetImmediateStorage { + lastId: number, + activeIds: Set, +} + +const mySetImmediateStorage: MySetImmediateStorage = { + lastId: 0, + activeIds: new Set(), +}; + +function mySetImmediate(cb: Function): number { + const id = mySetImmediateStorage.lastId + 1; + mySetImmediateStorage.activeIds.add(id); + mySetImmediateStorage.lastId += 1; + queueMicrotask(() => { + if (mySetImmediateStorage.activeIds.has(id)) { + cb(); + } + }); + return id; +} + +function myClearImmediate(id: number) { + mySetImmediateStorage.activeIds.delete(id); +} + + +setTimeout(() => { + console.log(3); +}, 0); + +mySetImmediate(() => { + console.log(1); +}); + +const timer = mySetImmediate(() => { + console.log(2); +}); + +myClearImmediate(timer); + +// 1 3 + diff --git a/day_3/day_3_3.ts b/day_3/day_3_3.ts new file mode 100644 index 0000000..ae1fc70 --- /dev/null +++ b/day_3/day_3_3.ts @@ -0,0 +1,17 @@ +function curry(fn: Function): Function { + return function recur(...suppliedArgs) { + if (suppliedArgs.length >= fn.length) { + return fn(...suppliedArgs); + } + return function (...moreArgs) { + return recur(...suppliedArgs, ...moreArgs); + }; + }; +} + +const sum = curry((a, b, c, z) => a + b + c + z); + +console.log(sum(1, 2, 3, 4)); // 10; +console.log(sum(1)(2)(3)(4)); // 10; +console.log(sum(1)(2)(3, 4)); // 10; +console.log(sum(1)(2, 3, 4)); // 10; diff --git a/day_3/day_3_4.ts b/day_3/day_3_4.ts new file mode 100644 index 0000000..c620826 --- /dev/null +++ b/day_3/day_3_4.ts @@ -0,0 +1,48 @@ +function zip(...iterables: Iterable[]): IterableIterator { + const iterators = iterables.map(iterable => iterable[Symbol.iterator]()); + return { + [Symbol.iterator]() { + return this; + }, + + next() { + const zippedValue: any[] = []; + for (const iterator of iterators) { + const result = iterator.next(); + if (result.done) { + return { + done: true, + value: undefined, + }; + } + zippedValue.push(result.value); + } + return { + done: false, + value: zippedValue, + }; + } + } +} + +function generatorZip(...iterables: Iterable[]): IterableIterator { + const generator = function* () { + const iterators = iterables.map(iterable => iterable[Symbol.iterator]()); + while (true) { + const results = iterators.map(iterator => iterator.next()); + if (results.some(result => result.done)) break; + yield results.map(result => result.value); + } + }; + return generator(); +} + +const zipped = zip(new Set([1, 2]), ['a', 'b', 'z'], '...'); +console.log('1st run: ', ...zipped); // [1, 'a', '.'] [2, 'b', '.'] +console.log('2nd run: ', ...zipped); // nothing + +const generatorZipped = generatorZip(new Set([1, 2]), ['a', 'b', 'z'], '...'); +console.log('1st run: ', ...generatorZipped); // [1, 'a', '.'] [2, 'b', '.'] +console.log('2nd run: ', ...generatorZipped); // nothing + +export {}; \ No newline at end of file diff --git a/day_4/1.js b/day_4/1.js deleted file mode 100644 index 15c95fc..0000000 --- a/day_4/1.js +++ /dev/null @@ -1,19 +0,0 @@ -console.log('foo'); - -setTimeout(() => { - console.log('bar'); -}, 0); - -queueMicrotask(() => { - console.log('baz'); - Promise.resolve().then().then(() => console.log('ban')); -}); - -new Promise((resolve) => { - console.log('bla'); - resolve('baf'); -}).then(console.log); - -console.log('bak'); - -// foo bla bak baz baf ban bar \ No newline at end of file diff --git a/day_4/2.js b/day_4/2.js deleted file mode 100644 index 888211b..0000000 --- a/day_4/2.js +++ /dev/null @@ -1,34 +0,0 @@ -setTimeout(() => { - console.log(3); -}, 0); - -const setImmediateTimers= new Map(); - -function setImmediate(cb) { - const timer = setImmediateTimers.size; - setImmediateTimers.set(timer, true); - - queueMicrotask(() => { - if (setImmediateTimers.get(timer) === false) { - return; - } - - cb(); - }); - - return timer; -} - -function clearImmediate(timer) { - setImmediateTimers.set(timer, false); -} - -setImmediate(() => { - console.log(1); -}); - -const timer = setImmediate(() => { - console.log(2); -}); - -clearImmediate(timer); \ No newline at end of file diff --git a/day_4/3.js b/day_4/3.js deleted file mode 100644 index 625deae..0000000 --- a/day_4/3.js +++ /dev/null @@ -1,45 +0,0 @@ -// const sum = curry((a, b, c, z) => a + b + c + z); -// -// console.log(sum(1)(2)(3)(4)); // 10; -// console.log(sum(1)(2)(3, 4)); // 10; -// console.log(sum(1)(2, 3, 4)); // 10; - -function curry(fn) { - return function curriedWrapper(...args) { - if (args.length >= fn.length) { - return fn.apply(this, args); - } - - return function (...args2) { - return curriedWrapper.apply(this, args.concat(args2)); - } - } -} - -function myCurry(fn) { - return function wrapper(...args) { - if (args.length >= fn.length) { - return fn.apply(this, args); - } - - return function (...args2) { - return wrapper.apply(this, [...args, ...args2]); - } - } -} - -function twoSum(array, sum) { - const set = new Set(array); - - array.forEach((number) => { - const candidate = sum - number; - - if (set.has(candidate)) { - return [number, candidate]; - } - }); - - return []; -} - -console.log(twoSum([3,5,-4,8,11,1,-1,6], 10)); \ No newline at end of file diff --git a/day_4/4.js b/day_4/4.js deleted file mode 100644 index 0198dfc..0000000 --- a/day_4/4.js +++ /dev/null @@ -1,35 +0,0 @@ -console.log(...zip(new Set([1, 2]), ['a', 'b', 'z'], '...')); // [day_1, 'a', '.'] [day_2, 'b', '.'] - -function zip(...iterables) { - const iters = [...iterables].map((iter) => iter[Symbol.iterator]()); - - let - done = false; - - return { - [Symbol.iterator]() { - return this; - }, - - next() { - if (done) { - return {done, value: undefined}; - } - - const value = new Array(iters.length); - - for (const [i, iter] of iters.entries()) { - const current = iter.next(); - - if (current.done) { - done = true; - return {done, value: undefined}; - } - - value[i] = current.value; - } - - return {done: false, value}; - } - } -} \ No newline at end of file diff --git "a/day_5/\320\227\320\260\320\264\320\260\321\207\320\270.md" b/day_4/day_4.md similarity index 78% rename from "day_5/\320\227\320\260\320\264\320\260\321\207\320\270.md" rename to day_4/day_4.md index d19476f..4757423 100644 --- "a/day_5/\320\227\320\260\320\264\320\260\321\207\320\270.md" +++ b/day_4/day_4.md @@ -1,6 +1,6 @@ # День 4 -## Реализация функции promisify +## 1. Реализация функции promisify Необходимо написать функцию, которая бы создавал функции с Promise API на основе функций с callback API. Формат callback функции ожидается в стиле Node.js, где первый аргумент - это объект ошибки. @@ -17,11 +17,11 @@ function cbDiv(a, b, cb) { const promiseDiv = promisify(cbDiv); -promiseDiv(1, 2).then(console.log); // 0.day_5 +promiseDiv(1, 2).then(console.log); // 0.5 promiseDiv(1, 0).catch(console.log); // TypeError('Нельзя делить на 0') ``` -## Написать класс числа с рекурсивным API для арифметических операций +## 2. Написать класс числа с рекурсивным API для арифметических операций Необходимо проанализировать пример ниже и реализовать нужный API, где @@ -36,15 +36,15 @@ const num = new MyNumber(10); console.log(num.add(2).mult(2).sub(1) - 5); // 18 ``` -## Выборочная сортировка массива +## 3. Выборочная сортировка массива Необходимо написать функцию sort, которая бы сортировала элементы со значением кратным двум. ```js -console.log(sort([7, 1, 4, 2, 9, 8])); // [7, day_1, day_2, day_4, 9, 8] +console.log(sort([7, 1, 4, 2, 9, 8])); // [7, 1, 2, 4, 9, 8] ``` -## Поиск в массиве строк по заданной подстроке с пропуском символов +## 4. Поиск в массиве строк по заданной подстроке с пропуском символов Необходимо написать функцию, которая бы принимала массив строк и строку и возвращала бы новый массив, состоящий только из элементов с содержанием заданной подстроки. Алгоритм должен учитывать, что подстрока может быть найдена в строке при помощи пропуска части символов в строке (нечеткий поиск). diff --git a/day_4/day_4_1.ts b/day_4/day_4_1.ts new file mode 100644 index 0000000..04f2a05 --- /dev/null +++ b/day_4/day_4_1.ts @@ -0,0 +1,27 @@ +function promisify(fn: Function) { + return function (...args) { + const usableArgs = args.slice(0, fn.length - 1); + return new Promise((resolve, reject) => { + fn(...usableArgs, function (error, result) { + if (error) { + return reject(error); + } + resolve(result); + }); + }); + } +} + +function cbDiv(a, b, cb) { + if (b === 0) { + cb(new TypeError('Нельзя делить на 0')); + + } else { + cb(null, a / b); + } +} + +const promiseDiv = promisify(cbDiv); + +promiseDiv(1, 2).then(console.log); // 0.5 +promiseDiv(1, 0).catch(console.log); // TypeError('Нельзя делить на 0') diff --git a/day_4/day_4_2.ts b/day_4/day_4_2.ts new file mode 100644 index 0000000..54676e8 --- /dev/null +++ b/day_4/day_4_2.ts @@ -0,0 +1,34 @@ +class MyNumber { + #value: number; + + constructor(initialValue: number) { + this.#value = initialValue; + } + + add(value: number) { + this.#value += value; + return this; + } + + mult(value: number) { + this.#value *= value; + return this; + } + + sub(value: number) { + this.#value -= value; + return this; + } + + valueOf(): number { + return this.#value; + } + + [Symbol.toPrimitive](): number { + return this.#value; + } +} + +const num = new MyNumber(10); + +console.log(+num.add(2).mult(2).sub(1) - 5); // 18 \ No newline at end of file diff --git a/day_4/day_4_3.ts b/day_4/day_4_3.ts new file mode 100644 index 0000000..5996115 --- /dev/null +++ b/day_4/day_4_3.ts @@ -0,0 +1,34 @@ +function mySort(arr: number[]): number[] { + if (!arr.length) { + return arr; + } + + const evenNumbers: number[] = []; + const evenIndices: number[] = []; + for (let i = 0; i < arr.length; i++) { + if (arr[i] % 2 === 0) { + evenNumbers.push(arr[i]); + evenIndices.push(i); + } + } + + evenNumbers.sort(); + + const result = arr.slice(); + for (let i = 0; i < evenIndices.length; i++) { + const index = evenIndices[i]; + result[index] = evenNumbers[i]; + } + + return result; +} + +let res = mySort([7, 1, 4, 2, 9, 8]); +let expected = '7,1,2,4,9,8'; +console.assert(res.toString() === expected, `Wrong order (1): ${res}, expected: ${expected}`); + +res = mySort([1, 8, 3, 6, 5, 4]); +expected = '1,4,3,6,5,8'; +console.assert(res.toString() === expected, `Wrong order (2): ${res}, expected: ${expected}`); + +export {}; \ No newline at end of file diff --git a/day_4/day_4_4.ts b/day_4/day_4_4.ts new file mode 100644 index 0000000..d4bafff --- /dev/null +++ b/day_4/day_4_4.ts @@ -0,0 +1,24 @@ +function find(needle: string, haystack: string[]): string[] { + const matches: string[] = []; + for (const hay of haystack) { + let hayIndex = 0; + let needleIndex = 0; + while (needleIndex < needle.length && hayIndex < hay.length) { + if (needle[needleIndex] === hay[hayIndex]) { + needleIndex += 1; + } + hayIndex += 1; + } + if (needleIndex === needle.length) { + matches.push(hay); + } + } + return matches; +} + +console.log(find('kbza', [ + 'kobezzza', + 'bob', + 'kibiza', + 'kobea' +])); // ['kobezzza', 'kibiza'] \ No newline at end of file diff --git a/day_5/1.js b/day_5/1.js deleted file mode 100644 index 06d5fe1..0000000 --- a/day_5/1.js +++ /dev/null @@ -1,31 +0,0 @@ -function cbDiv(a, b, cb) { - if (b === 0) { - cb(new TypeError('Cannot devide by 0')); - - } else { - cb(null, a / b); - } -} - -function promisify(fn) { - return function(...args) { - return new Promise((resolve, reject) => { - if (args.length !== fn.length - 1) { - throw new Error('The fn arguments number is wrong!'); - } - - fn(...args, (err, data) => { - if (err) { - reject(err); - } - - resolve(data); - }) - }); - } -} - -const promiseDiv = promisify(cbDiv); - -promiseDiv(1, 2).then(console.log); // 0.day_5 -promiseDiv(1, 0).catch(console.log); // TypeError('Нельзя делить на 0') \ No newline at end of file diff --git a/day_5/2.js b/day_5/2.js deleted file mode 100644 index 89a2382..0000000 --- a/day_5/2.js +++ /dev/null @@ -1,34 +0,0 @@ -class MyNumber { - constructor(num = 0) { - this.num = num; - } - - add(int) { - this.num += int; - return this; - } - - sub(int) { - this.num -= int; - return this; - } - - mult(int) { - this.num *= int; - return this; - } - - div(int) { - this.num /= int; - return this; - } - - [Symbol.toPrimitive](hint) { - return hint === "number" ? this.num : `${this.num}`; - } -} - -const num = new MyNumber(10); -console.log(num) - -console.log(num.add(2).mult(2).sub(1) - 5); // 18 \ No newline at end of file diff --git a/day_5/3.js b/day_5/3.js deleted file mode 100644 index bd51d17..0000000 --- a/day_5/3.js +++ /dev/null @@ -1,24 +0,0 @@ -console.log(sort([7, 1, 4, 2, 9, 8])); // [7, day_1, day_2, day_4, 9, 8] - -function sort(arr) { - if (!arr || !Array.isArray(arr)) throw new Error('expect type Array as an argument'); - if (!arr.length) return []; - - const positions = []; - const values = []; - - arr.forEach((el, i) => { - if (el % 2 === 0) { - positions.push(i); - values.push(el); - } - }); - - values.sort((a, b) => a - b); - - values.forEach((el, i) => { - arr[positions[i]] = el; - }) - - return arr; -} \ No newline at end of file diff --git a/day_5/4.js b/day_5/4.js deleted file mode 100644 index d12f6b2..0000000 --- a/day_5/4.js +++ /dev/null @@ -1,20 +0,0 @@ -console.log(find('kbza', [ - 'kobezzza', - 'bob', - 'kibiza', - 'kobea', -])); // ['kobezzza', 'kibiza'] - -function find(input, arr) { - const inputChars = input.split(''); - - return arr.reduce((acc,str) => { - const charSet = new Set(str.split('')); - - if (inputChars.every((char) => charSet.has(char))) { - acc.push(str); - } - - return acc; - },[]); -} \ No newline at end of file diff --git "a/day_6/\320\227\320\260\320\264\320\260\321\207\320\270.md" b/day_5/day_5.md similarity index 88% rename from "day_6/\320\227\320\260\320\264\320\260\321\207\320\270.md" rename to day_5/day_5.md index 30440b3..3533e71 100644 --- "a/day_6/\320\227\320\260\320\264\320\260\321\207\320\270.md" +++ b/day_5/day_5.md @@ -1,6 +1,6 @@ # День 5 -## Написать функцию debounce +## 1. Написать функцию debounce Необходимо написать функцию, которая бы принимала другую функцию и возвращала её debounce версию. @@ -18,7 +18,7 @@ debouncedLaugh(); debouncedLaugh(); ``` -## Написать простейший EventEmitter +## 2. Написать простейший EventEmitter Должна быть поддержка множественных и одноразовых событий и отмены сразу всех событий по имени. @@ -34,7 +34,7 @@ ee.off('foo', console.log); // Отмена конкретного обрабо ee.off('foo'); // Отмена всех обработчиков этого события ``` -## Реализация функции аналогичной Array.prototype.flat +## 3. Реализация функции аналогичной Array.prototype.flat Необходимо написать функцию, которая бы повторяло поведение Array.prototype.flat. @@ -43,7 +43,7 @@ console.log(flat([[1, 2], [[1]], 2])); // [1, 2, [1], 2] console.log(flat([[1, 2], [[1]], 2], 2)); // [1, 2, 1, 2] ``` -## Топологическая сортировка массива +## 4. Топологическая сортировка массива Необходимо написать функцию, которая бы делала топологическую сортировку массива по заданному критерию. diff --git a/day_5/day_5_1.ts b/day_5/day_5_1.ts new file mode 100644 index 0000000..3254c99 --- /dev/null +++ b/day_5/day_5_1.ts @@ -0,0 +1,40 @@ +function debounce(fn: Function, delay: number): Function { + let timerId; + return function (...args) { + clearTimeout(timerId); + timerId = setTimeout(() => fn(...args), delay); + }; +} + +function laugh() { + console.log('Ha-ha!') +} + +const debouncedLaugh = debounce(laugh, 300); + +debouncedLaugh(); +debouncedLaugh(); +debouncedLaugh(); +debouncedLaugh(); +debouncedLaugh(); + +function throttle(fn: Function, delay: number): Function { + let waiting = false; + return function (...args) { + if (!waiting) { + fn(...args); + waiting = true; + setTimeout(() => { + waiting = false; + }, delay); + } + }; +} + +const throttledLaugh = throttle(laugh, 300); + +throttledLaugh(); +throttledLaugh(); +throttledLaugh(); +throttledLaugh(); +throttledLaugh(); diff --git a/day_5/day_5_2.ts b/day_5/day_5_2.ts new file mode 100644 index 0000000..45f7e26 --- /dev/null +++ b/day_5/day_5_2.ts @@ -0,0 +1,60 @@ +interface Listeners { + once: Set; + on: Set; +} + +class EventEmitter { + #listeners: Map = new Map(); + + on(event: string, listener: Function) { + if (!this.#listeners.has(event)) { + this.#listeners.set(event, { once: new Set(), on: new Set() }); + } + this.#listeners.get(event)?.on.add(listener); + return this; + } + + once(event: string, listener: Function) { + if (!this.#listeners.has(event)) { + this.#listeners.set(event, { once: new Set(), on: new Set() }); + } + this.#listeners.get(event)?.once.add(listener); + return this; + } + + off(event: string, listener?: Function) { + const listeners = this.#listeners.get(event); + if (!listeners) return; + if (listener) { + listeners.once.delete(listener); + listeners.on.delete(listener); + } else { + this.#listeners.delete(event); + } + } + + emit(event: string, ...args) { + const listeners = this.#listeners.get(event); + if (!listeners) return; + listeners.once.forEach(listener => listener(...args)); + listeners.once.clear(); + listeners.on.forEach(listener => listener(...args)); + } +} + +const ee = new EventEmitter(); + +ee.once('foo', console.log); // Сработает только один раз + +ee.emit('foo', 1); +ee.emit('foo', 2); + +ee.off('foo', console.log); // Отмена конкретного обработчика события по ссылке + +ee.on('foo', console.log); // Сработает много раз +ee.emit('foo', 3); +ee.emit('foo', 4); + +ee.off('foo'); // Отмена всех обработчиков этого события + +ee.emit('foo', 5); \ No newline at end of file diff --git a/day_5/day_5_3.ts b/day_5/day_5_3.ts new file mode 100644 index 0000000..ea2fa8e --- /dev/null +++ b/day_5/day_5_3.ts @@ -0,0 +1,40 @@ +function flat(arr: any[], depth: number = 1): any[] { + if (depth <= 0) { + return arr; + } + return arr.reduce((acc, el) => { + if (Array.isArray(el)) { + return acc.concat(flat(el, depth - 1)); + } + acc.push(el); + return acc; + }, []); +} + +console.log(JSON.stringify(flat([[1, 2], [[1]], 2]))); // [1, 2, [1], 2] +console.log(JSON.stringify(flat([[1, 2], [[1]], 2], 2))); // [1, 2, 1, 2] + +function flatNonRecursive(arr: any[], depth: number = 1): any[] { + const stack: [any, number][] = []; + for (let i = arr.length - 1; i >= 0; i--) { + stack.push([arr[i], 0]); + } + + const result: any[] = []; + + while (stack.length > 0) { + const [item, currentDepth] = stack.pop()!; + if (Array.isArray(item) && currentDepth < depth) { + for (let i = item.length - 1; i >= 0; i--) { + stack.push([item[i], currentDepth + 1]); + } + } else { + result.push(item); + } + } + + return result; +} + +console.log(JSON.stringify(flatNonRecursive([[1, 2], [[1]], 2]))); // [1, 2, [1], 2] +console.log(JSON.stringify(flatNonRecursive([[1, 2], [[1]], 2], 2))); // [1, 2, 1, 2] \ No newline at end of file diff --git a/day_5/day_5_4.ts b/day_5/day_5_4.ts new file mode 100644 index 0000000..cb6832a --- /dev/null +++ b/day_5/day_5_4.ts @@ -0,0 +1,112 @@ +type GraphNode = Record; +type Graph = GraphNode[]; +type MapperFn = (obj: T) => [string, string[] | undefined]; + +function topoSort(graph: T[], mapper: MapperFn) { + const depsByVertex = new Map(); + const nodeByVertex = new Map(); + const verticies: string[] = []; + for (const node of graph) { + const [vertex, deps] = mapper(node); + depsByVertex.set(vertex, deps ?? []); + nodeByVertex.set(vertex, node); + verticies.push(vertex); + } + const visiting = new Set(); + const visited = new Set(); + + const result: T[] = []; + + function visit(vertex: string) { + if (visiting.has(vertex)) throw new Error(`Cyclic dependency detected at "${vertex}"`); + + visiting.add(vertex); + const deps = depsByVertex.get(vertex)!; + if (deps.length) { + for (const dep of deps) { + if (!visited.has(dep)) { + visit(dep); + } + } + } + visiting.delete(vertex); + + if (!visited.has(vertex)) { + visited.add(vertex); + result.push(nodeByVertex.get(vertex)!); + } + } + + for (const vertex of verticies) { + visit(vertex); + } + + return result; +} + +const skills = [ + { + name: 'fireball', + need: ['firehands', 'magicspell'] + }, + + { + name: 'firehands' + }, + + { + name: 'magicspell' + }, + + { + name: 'inferno', + need: ['fireball', 'crazymind'] + }, + + { + name: 'crazymind', + need: ['magicspell'] + } +]; + +/* +[ + { + name: 'firehands' + }, + + { + name: 'magicspell' + }, + + { + name: 'crazymind', + need: ['magicspell'] + } + + { + name: 'fireball', + need: ['firehands', 'magicspell'] + }, + + { + name: 'inferno', + need: ['fireball', 'crazymind'] + } +] +*/ +console.log(JSON.stringify( + topoSort(skills, ({name, need}) => [name, need]), + undefined, + ' ' +)); + +/* +firehands magicspell + \ / \ + v v v + fireball crazymind + \ / + v v + inferno +*/ \ No newline at end of file diff --git a/day_6/1.js b/day_6/1.js deleted file mode 100644 index e5b77b5..0000000 --- a/day_6/1.js +++ /dev/null @@ -1,40 +0,0 @@ -function laugh() { - console.log('Ha-ha!') -} - -const debouncedLaugh = debounce(laugh, 300); - -debouncedLaugh(); -debouncedLaugh(); -debouncedLaugh(); -debouncedLaugh(); -debouncedLaugh(); - -function debounce(fn, delay) { - let timerId; - - return function (...args) { - if (timerId != null) { - clearTimeout(timerId); - } - - timerId = setTimeout(() => { - fn.apply(this, args); - }, delay); - } -} - -function throttle(fn, delay) { - let isRunning; - - return function(...args) { - if (!isRunning) { - isRunning = true; - fn.apply(this, args); - - setTimeout(() => { - isRunning = false; - }, delay); - } - } -} \ No newline at end of file diff --git a/day_6/2.js b/day_6/2.js deleted file mode 100644 index 9f53df4..0000000 --- a/day_6/2.js +++ /dev/null @@ -1,68 +0,0 @@ - // Отмена всех обработчиков этого события - -class EventEmitter { - - #handlers = new Map(); - - on(event, cb) { - const store = this.#getHandlersStore(event); - store.add(cb); - - return cb; - } - - #getHandlersStore(event) { - let store = this.#handlers.get(event); - - if (store == null) { - store = new Set(); - this.#handlers.set(event, store); - } - - return store; - } - - once(event, cb) { - const wrapper = (...args) => { - try { - cb(...args); - } finally { - this.off(event, wrapper); - } - } - - return this.on(event, wrapper); - } - - emit(event, ...payload) { - const store = this.#getHandlersStore(event); - - store.forEach((cb) => cb(...payload)); - } - - off(event, cb) { - if (event == null) { - this.#handlers.clear(); - return; - } - - const store = this.#getHandlersStore(event); - - if (cb == null) { - store.clear(); - return; - } - - store.delete(cb); - } -} - -const ee = new EventEmitter(); - -ee.once('foo', console.log); // Сработает только один раз - -ee.emit('foo', 1); -ee.emit('foo', 2); - -ee.off('foo', console.log); // Отмена конкретного обработчика события по ссылке -ee.off('foo'); \ No newline at end of file diff --git a/day_6/3.js b/day_6/3.js deleted file mode 100644 index 61361c4..0000000 --- a/day_6/3.js +++ /dev/null @@ -1,43 +0,0 @@ -console.log(flat([[1, 2], [[1]], 2])); // [1, 2, [1], 2] -console.log(flat([[1, 2], [[1]], 2], 2)); // [1, 2, 1, 2] - -// function flat(arr, depth = 1) { -// const result = []; -// const wrapper = (arr, depth) => { -// arr.forEach((el) => { -// if (Array.isArray(el) && depth > 0) { -// return wrapper(el, depth - 1); -// } -// -// result.push(el); -// }); -// } -// -// wrapper(arr, depth); -// -// return result; -// } - -function flat(arr, depth = 1) { - const result = []; - - const stack = [ - [depth, arr[Symbol.iterator]()] - ]; - - while (stack.length) { - const [depth, iter] = stack.pop(); - - for (const el of iter) { - if (!Array.isArray(el) || depth <= 0) { - result.push(el); - } else { - stack.push([depth, iter]); - stack.push([depth - 1, el[Symbol.iterator]()]); - break; - } - } - } - - return result; -} \ No newline at end of file diff --git a/day_6/4.js b/day_6/4.js deleted file mode 100644 index 189b969..0000000 --- a/day_6/4.js +++ /dev/null @@ -1,90 +0,0 @@ -const skills = [ - { - name: 'fireball', - need: ['firehands', 'magicspell'] - }, - - { - name: 'firehands' - }, - - { - name: 'magicspell' - }, - - { - name: 'inferno', - need: ['fireball', 'crazymind'] - }, - - { - name: 'crazymind', - need: ['magicspell'] - } -]; - -/* -[ - { - name: 'firehands' - }, - - { - name: 'magicspell' - }, - - { - name: 'crazymind', - need: ['magicspell'] - } - - { - name: 'fireball', - need: ['firehands', 'magicspell'] - }, - - { - name: 'inferno', - need: ['fireball', 'crazymind'] - } -] -*/ -console.log(sort(skills, ({name, need}) => [name, need])); - -function sort(skills, fn) { - const map = new Map(skills.map((value) => { - const [key, dependencies = []] = fn(value); - - return [ - key, - { - value, dependencies, marked: false - } - ] - })); - - const queue = []; - - function traverse(mapInstance) { - mapInstance.forEach((el) => { - if (el.marked) { - return; - } - - if (el.dependencies.length) { - traverse(new Map(el.dependencies.map((key) => [key, map.get(key)]))); - } - - el.marked = true; - queue.push(el.value); - }) - } - - traverse(map); - - skills.forEach((_, i) => { - skills[i] = queue[i]; - }); - - return skills; -} \ No newline at end of file diff --git "a/day_7/\320\227\320\260\320\264\320\260\321\207\320\270.md" b/day_6/day_6.md similarity index 82% rename from "day_7/\320\227\320\260\320\264\320\260\321\207\320\270.md" rename to day_6/day_6.md index f411b10..fb7e080 100644 --- "a/day_7/\320\227\320\260\320\264\320\260\321\207\320\270.md" +++ b/day_6/day_6.md @@ -1,6 +1,6 @@ # День 6 -## Реализация паттерна "Строитель" для класса +## 1. Реализация паттерна "Строитель" для класса Необходимо реализовать паттерн "Строитель" для заданного класса. @@ -16,7 +16,7 @@ class User { User.name('Bob').age(47).skills(['Coding']).create(); // User({name: 'Bob', age: 47, skills: ['Coding']}) ``` -## Обход дерева в ширину +## 2. Обход дерева в ширину Необходимо вывести элементы дерева таким образом, чтобы по очереди выводились все элементы каждого яруса. @@ -37,7 +37,7 @@ const tree = { log(tree); // 1 2 3 4 ``` -## Реализовать функцию преобразования CamelCase в dash-style +## 3. Реализовать функцию преобразования CamelCase в dash-style Необходимо создать функцию, которая бы принимала строку в CamelCase и возвращала бы её вариант в dash-style. @@ -47,7 +47,7 @@ console.log(dasherize('SuperMAN')); // 'super-man' console.log(dasherize('VirtualDOMFragment')); // 'virtual-dom-fragment' ``` -## Реализовать функцию waterfall для callback функций +## 4. Реализовать функцию waterfall для callback функций Необходимо создать функцию для композиции асинхронного кода на callback функциях, которая работает как показано на примере. diff --git a/day_6/day_6_1.ts b/day_6/day_6_1.ts new file mode 100644 index 0000000..9c68a0a --- /dev/null +++ b/day_6/day_6_1.ts @@ -0,0 +1,58 @@ +class User { + name: string; + age: string; + skills: string; + constructor(params) { + this.name = params.name; + this.age = params.age; + this.skills = params.skills; + } + + static name(name: string) { + return new UserBuilder().name(name); + } + + static age(age: number) { + return new UserBuilder().age(age); + } + + static skills(skills: string[]) { + return new UserBuilder().skills(skills); + } +} + +class UserBuilder { + #name: string = ''; + #age: number = 0; + #skills: string[] = []; + + name(name: string) { + this.#name = name; + return this; + } + + age(age: number) { + this.#age = age; + return this; + } + + skills(skills: string[]) { + this.#skills = skills; + return this; + } + + create() { + return new User({ + name: this.#name, + age: this.#age, + skills: this.#skills, + }); + } +} + + +let user1Partial = User.name('Bob').age(47).skills(['Coding']); +let user2 = User.name('Alex').age(13).skills(['None']).create(); +let user1 = user1Partial.create(); +console.log(JSON.stringify(user1)); // User({name: 'Bob', age: 47, skills: ['Coding']}) +console.log(JSON.stringify(user2)); // User({name: 'Alex', age: 13, skills: ['None']}) diff --git a/day_6/day_6_2.ts b/day_6/day_6_2.ts new file mode 100644 index 0000000..a3235d9 --- /dev/null +++ b/day_6/day_6_2.ts @@ -0,0 +1,33 @@ +interface Tree { + value: number; + children?: Tree[]; +} + +function bfs(tree: Tree): number[] { + const queue: Tree[] = [tree]; + const result: number[] = []; + while (queue.length > 0) { + const node = queue.shift()!; + result.push(node.value); + if (!node.children) continue; + for (const child of node.children) { + queue.push(child); + } + } + return result; +} + +const tree = { + value: 1, + children: [ + { + value: 2, + children: [{value: 4}] + }, + { + value: 3 + } + ] +}; + +console.log(bfs(tree)); // 1 2 3 4 \ No newline at end of file diff --git a/day_6/day_6_3.ts b/day_6/day_6_3.ts new file mode 100644 index 0000000..fd174be --- /dev/null +++ b/day_6/day_6_3.ts @@ -0,0 +1,44 @@ +function dasherize(input: string): string { + return input + .replace(/([a-z])([A-Z])|([A-Z])([A-Z][a-z])/g, '$1$3-$2$4') + .toLowerCase(); +} + +console.log(dasherize('createDocumentFragment')); // 'create-document-fragment' +console.log(dasherize('SuperMAN')); // 'super-man' +console.log(dasherize('VirtualDOMFragment')); // 'virtual-dom-fragment' + +function dasherizeStepByStep(input: string): string { + let result = ''; + let uppercasedSeq = ''; + for (const char of input) { + if (char.toUpperCase() === char) { + uppercasedSeq += char.toLowerCase(); + } else { + if (uppercasedSeq) { + if (uppercasedSeq.length === 1) { + if (result) { + result += '-'; + } + result += uppercasedSeq; + } else { + const firstWord = uppercasedSeq.slice(0, -1); + if (result) { + result += '-'; + } + result += firstWord + '-' + uppercasedSeq[uppercasedSeq.length - 1]; + } + } + uppercasedSeq = ''; + result += char; + } + } + if (uppercasedSeq) { + result += '-' + uppercasedSeq; + } + return result; +} + +console.log(dasherizeStepByStep('createDocumentFragment')); // 'create-document-fragment' +console.log(dasherizeStepByStep('SuperMAN')); // 'super-man' +console.log(dasherizeStepByStep('VirtualDOMFragment')); // 'virtual-dom-fragment' \ No newline at end of file diff --git a/day_6/day_6_4.ts b/day_6/day_6_4.ts new file mode 100644 index 0000000..96f60c9 --- /dev/null +++ b/day_6/day_6_4.ts @@ -0,0 +1,71 @@ +function waterfall(fns: Iterable, resultFn: Function) { + const iter = fns[Symbol.iterator](); + function next(forwardedArgs: any[]) { + const { value: fn, done } = iter.next(); + if (done) { + return resultFn(null, ...forwardedArgs); + } + fn(...forwardedArgs, (err, ...args) => { + if (err) { + return resultFn(err); + } + next(args); + }); + } + next([]); +} + +waterfall([ + (cb) => { + cb(null, 'one', 'two'); + }, + + (arg1, arg2, cb) => { + console.log(arg1); // one + console.log(arg2); // two + cb(null, 'three'); + }, + + (arg1, cb) => { + console.log(arg1); // three + cb(null, 'done'); + } +], (err, result) => { + console.log(result); // done +}); + +waterfall(new Set([ + (cb) => { + cb('ha-ha!'); + }, + + (arg1, cb) => { + cb(null, 'done'); + } +]), (err, result) => { + console.log(err); // ha-ha! + console.log(result); // undefined +}); + +waterfall([ + (cb) => { + setTimeout(() => cb(null, 'one', 'two'), 10); + }, + + (arg1, arg2, cb) => { + setTimeout(() => { + console.log(arg1); // one + console.log(arg2); // two + cb(null, 'three'); + }, 20); + }, + + (arg1, cb) => { + setTimeout(() => { + console.log(arg1); // three + cb(null, 'done'); + }, 30); + } +], (err, result) => { + console.log(result); // done +}); diff --git a/day_7/1.js b/day_7/1.js deleted file mode 100644 index ab49f6e..0000000 --- a/day_7/1.js +++ /dev/null @@ -1,28 +0,0 @@ -class User { - constructor(params) { - this.name = params.name; - this.age = params.age; - this.skills = params.skills; - } - - static name(name) { - this.name = name; - return this; - } - - static age(age) { - this.age = age; - return this; - } - - static skills(skills) { - this.skills = skills; - return this; - } - - static create() { - return new this({name: this.name, age: this.age, skills: this.skills}); - } -} - -console.log(User.name('Bob').age(47).skills(['Coding']).create()); // User({name: 'Bob', age: 47, skills: ['Coding']}) diff --git a/day_7/2.js b/day_7/2.js deleted file mode 100644 index 33db5f7..0000000 --- a/day_7/2.js +++ /dev/null @@ -1,26 +0,0 @@ -const tree = { - value: 1, - children: [ - { - value: 2, - children: [{value: 4}] - }, - { - value: 3 - } - ] -}; - -console.log(log(tree)); // 1 2 3 4 - -// tree width traverse -function log(tree) { - const queue = [tree]; - - while (queue.length) { - const head = queue.shift(); - console.log(head.value); - - head.children?.forEach((child) => queue.push(child)); - } -} \ No newline at end of file diff --git a/day_7/3.js b/day_7/3.js deleted file mode 100644 index 3043057..0000000 --- a/day_7/3.js +++ /dev/null @@ -1,40 +0,0 @@ -console.log(dasherize('createDocumentFragment')); // 'create-document-fragment' -console.log(dasherize('SuperMAN')); // 'super-man' -console.log(dasherize('VirtualDOMFragment')); // 'virtual-dom-fragment' -console.log(dasherize('VirtualDOM123Fragment')); // 'virtual-dom-fragment' - - -function dasherize(str) { - const isUpperCase = (char) => char === char.toUpperCase(); - const queue = []; - - return str.split('').reduce((result, char, index, array) => { - if (index === 0) { - result += char.toLowerCase(); - return result; - } - - if (isUpperCase(char)) { - if (!queue.length && !isUpperCase(array[index + 1])) { - result += `-${char.toLowerCase()}`; - return result; - } - - if (array[index + 1] === undefined) { - result += `-${queue.join('')}${char.toLowerCase()}` - return result; - } - - if (queue.length && !isUpperCase(array[index + 1])) { - result += `-${queue.join('')}-${char.toLowerCase()}` - return result; - } - - queue.push(char.toLowerCase()); - return result; - } - - result += char; - return result; - }, ''); -} \ No newline at end of file diff --git "a/day_8/\320\227\320\260\320\264\320\260\321\207\320\270.md" b/day_7/day_7.md similarity index 86% rename from "day_8/\320\227\320\260\320\264\320\260\321\207\320\270.md" rename to day_7/day_7.md index 76cfd1f..fadb1b4 100644 --- "a/day_8/\320\227\320\260\320\264\320\260\321\207\320\270.md" +++ b/day_7/day_7.md @@ -1,6 +1,6 @@ # День 7 -## Нахождения разницы двух множеств +## 1. Нахождения разницы двух множеств Необходимо написать функцию, которая принимает два массива (элементы в массивах не повторяются) и возвращает новый массив. В новом массиве должны быть все элементы которые есть в первом массиве, но нет во втором. @@ -9,7 +9,7 @@ console.log(diff([1, 2, 3, 4, 5], [3, 4, 1])); // [2, 5] ``` -## Написать асинхронный семафор +## 2. Написать асинхронный семафор Необходимо написать функцию, которая бы создавала "асинхронный семафор" на основе переданной функции и набора флагов, как это показано в примере ниже. @@ -26,7 +26,7 @@ semaphore('bar'); // 'Boom!' semaphore(); ``` -## Сериализация нестандартных объектов +## 3. Сериализация нестандартных объектов Необходимо написать функцию, которая бы позволяла преобразовать заданные JS объекты в строку и обратно. @@ -45,7 +45,7 @@ const str = serialize(original); parse(str); ``` -## Вычисление инфиксного выражения с приоритетами +## 4. Вычисление инфиксного выражения с приоритетами Необходимо написать функцию, которая бы принимала строку с математическим выражением и вычисляла бы её результат. В строке могут использоваться операции сложения, вычитания, умножения и деления. Function и eval использовать нельзя. diff --git a/day_7/day_7_1.ts b/day_7/day_7_1.ts new file mode 100644 index 0000000..5d95701 --- /dev/null +++ b/day_7/day_7_1.ts @@ -0,0 +1,12 @@ +function diff(arrA: number[], arrB: number[]): number[] { + const setB = new Set(arrB); + const result: number[] = []; + for (const x of arrA) { + if (!setB.has(x)) { + result.push(x); + } + } + return result; +} + +console.log(diff([1, 2, 3, 4, 5], [3, 4, 1])); // [2, 5] \ No newline at end of file diff --git a/day_7/day_7_2.ts b/day_7/day_7_2.ts new file mode 100644 index 0000000..8e3bfff --- /dev/null +++ b/day_7/day_7_2.ts @@ -0,0 +1,23 @@ +type SemaphoreFn = (flag?: T) => void; + +function createsAsyncSemaphore(fn: Function, ...requiredFlags: T[]): SemaphoreFn { + const raisedFlags = new Set(); + return function (flag?: T) { + if (flag && requiredFlags.includes(flag)) { + raisedFlags.add(flag); + if (raisedFlags.size === requiredFlags.length) { + fn(); + } + } + }; +} + +const semaphore = createsAsyncSemaphore(() => { + console.log('Boom!'); +}, 'foo', 'bar'); + +setTimeout(() => semaphore('foo'), 0); +setTimeout(() => semaphore('bar'), 10); // 'Boom!' + +// Эта функция не будет выполняться +setTimeout(() => semaphore(), 50); \ No newline at end of file diff --git a/day_7/day_7_3.ts b/day_7/day_7_3.ts new file mode 100644 index 0000000..1e6874a --- /dev/null +++ b/day_7/day_7_3.ts @@ -0,0 +1,49 @@ +import { inspect } from 'util'; + +function serialize(obj: any): string { + return JSON.stringify(obj, function (key, value) { + if (this[key] instanceof Date) { + return `:s:d:${value.valueOf()}`; + } else if (this[key] instanceof Set) { + return `:s:s:${serialize([...value])}`; + } else if (this[key] instanceof Map) { + return `:s:m:${serialize([...value])}`; + } + return value; + }); +} + +function parse(objStr: string): any { + return JSON.parse(objStr, function (key, value) { + if (typeof value === 'string' && value.startsWith(':s')) { + const type = value.slice(3, 4); + const data = value.slice(5); + if (type === 'd') { + return new Date(data); + } else if (type === 's') { + return new Set(parse(data)); + } else if (type === 'm') { + return new Map(parse(data)); + } + } + return value; + }); +} + +const original = { + myDate: new Date(), + mySet: new Set([1, 2, 3]), + mySet2: new Set([{insideSet: new Set([1, 2])}, 3]), + myMap: new Map([ + [new Date(), {a: 1}] + ]) +}; + +console.log(inspect(original, false, 10)); + +const str = serialize(original); +console.log(str); + +// Объект должен иметь аналогичную структуру с original +const parsed = parse(str); +console.log(inspect(parsed, false, 10)); diff --git a/day_7/day_7_4.ts b/day_7/day_7_4.ts new file mode 100644 index 0000000..d0b1b44 --- /dev/null +++ b/day_7/day_7_4.ts @@ -0,0 +1,146 @@ +type Token = number | string; + +function tokenize(expression: string): Token[] { + const tokens: Token[] = []; + let i = 0; + while (i < expression.length) { + const char = expression[i]; + if (/\s/.test(char)) { + i += 1; + continue; + } else if (/\d/.test(char)) { + let token = ''; + while (i < expression.length && /\d/.test(expression[i])) { + token += expression[i]; + i += 1; + } + tokens.push(Number(token)); + } else if ('+-*/()'.includes(char)) { + tokens.push(char); + i += 1; + } else { + throw new Error(`Unexpected character '${char}' at position ${i}.`); + } + } + return tokens; +} + +function calcRecursive(expression: string): number { + const tokens = tokenize(expression); + + let pos = 0; + + function parseExpression(): number { + let lhs = parseTerm(); + while (tokens[pos] === '+' || tokens[pos] === '-') { + const op = tokens[pos]; + pos += 1; + const rhs = parseTerm(); + if (op === '+') lhs += rhs; + else lhs -= rhs; + } + return lhs; + } + + function parseTerm(): number { + let lhs = parseFactor(); + while (tokens[pos] === '*' || tokens[pos] === '/') { + const op = tokens[pos]; + pos += 1; + const rhs = parseFactor(); + if (op === '*') lhs *= rhs; + else lhs /= rhs; + } + return lhs; + } + + function parseFactor(): number { + const token = tokens[pos]; + if (typeof token === 'number') { + pos += 1; + return token; + } else if (token === '(') { + pos += 1; + const value = parseExpression(); + if (tokens[pos] !== ')') throw new Error(`Expected ")", got: ${tokens[pos]}.`); + pos += 1; + return value; + } + throw new Error(`Unexpected token: ${token}.`); + } + + const result = parseExpression(); + if (pos !== tokens.length) { + throw new Error(`Unexpected token at the end: ${tokens[pos]}.`); + } + return result; +} + +console.log(calcRecursive('2+4*3')); // 14 +console.log(calcRPN('(2+3)')); // 5 +console.log(calcRecursive('4-2')); // 2 +console.log(calcRecursive('5 * (2 + 3)')); // 25 +console.log(calcRecursive('2*3*4')); // 24 +console.log(calcRecursive('12+5*2/(60-58)-5')); // 12 + +function infixToRPN(tokens: Token[]): Token[] { + const precedenceByToken = { '+': 1, '-': 1, '*': 2, '/': 2 }; + const result: Token[] = []; + const stack: Token[] = []; + for (const token of tokens) { + if (typeof token === 'number') { + result.push(token); + } else if (token === '+' || token === '-' || token === '*' || token === '/') { + while (stack.length) { + const peeked = stack[stack.length - 1]; + if (peeked !== '(' && precedenceByToken[peeked] >= precedenceByToken[token]) { + result.push(peeked); + stack.pop(); + } else break; + } + stack.push(token); + } else if (token === '(') { + stack.push(token); + } else if (token === ')') { + while (stack.length) { + const top = stack.pop()!; + if (top === '(') break; + else result.push(top); + } + } else throw new Error(`Unexpected token: ${token}.`); + } + result.push(...stack.reverse()); + return result; +} + +function calcRPN(expression: string): number { + const tokens = tokenize(expression); + const rpn = infixToRPN(tokens); + const stack: Token[] = []; + for (const token of rpn) { + if (token === '*' || token === '/' || token === '+' || token === '-') { + if (stack.length < 2) throw new Error('Wrong RPN!'); + const rhs = stack.pop()!; + const lhs = stack.pop()!; + if (typeof rhs !== 'number' || typeof lhs !== 'number') throw new Error('Wrong token type, expected number!'); + if (token === '*') stack.push(lhs * rhs); + else if (token === '/') stack.push(lhs / rhs); + else if (token === '+') stack.push(lhs + rhs); + else if (token === '-') stack.push(lhs - rhs); + else throw new Error(`Unexpected token: ${token}.`); + } else { + stack.push(token); + } + } + if (stack.length !== 1) throw new Error(`Unexpected remaining stack: ${stack}.`); + const result = stack.pop(); + if (typeof result !== 'number') throw new Error('Wrong result type, expected number!'); + return result; +} + +console.log(calcRPN('2+4*3')); // 14 +console.log(calcRPN('(2+3)')); // 5 +console.log(calcRPN('4-2')); // 2 +console.log(calcRPN('5 * (2 + 3)')); // 25 +console.log(calcRPN('2*3*4')); // 24 +console.log(calcRPN('12+5*2/(60-58)-5')); // 12 \ No newline at end of file diff --git a/day_8/0.js b/day_8/0.js deleted file mode 100644 index 25e8d43..0000000 --- a/day_8/0.js +++ /dev/null @@ -1,156 +0,0 @@ -// allSettled([1, Promise.resolve(2), Promise.reject(3)]).then(([v1, v2, v3]) => { -// console.log(v1); // {status: 'fulfilled', value: day_1} -// console.log(v2); // {status: 'fulfilled', value: day_2} -// console.log(v3); // {status: 'rejected', reason: 3} -// }); - -// const promise1 = Promise.resolve(3); -// const promise2 = 42; -// const promise3 = new Promise((resolve, reject) => { -// setTimeout(resolve, 100, 'foo'); -// }); -// -// promiseAll([promise1, promise2, promise3]).then((values) => { -// console.log(values); -// }); -// Expected output: Array [3, 42, "foo"] - -// const promise1 = Promise.reject(0); -// const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick')); -// const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow')); -// -// const promises = [promise1, promise2, promise3]; -// -// promiseAny(promises).then((value) => console.log(value)); - -// Expected output: "quick" - -// const promise1 = new Promise((resolve, reject) => { -// setTimeout(resolve, 500, 'one'); -// }); -// -// const promise2 = new Promise((resolve, reject) => { -// setTimeout(resolve, 100, 'two'); -// }); -// -// promiseRace([promise1, promise2]).then((value) => { -// console.log(value); -// // Both resolve, but promise2 is faster -// }); -// // Expected output: "two" - - -function promiseAll(promises) { - const result = new Array(promises.length); - let resolvedPromisesCount = 0; - - return new Promise((resolve, reject) => { - if (!promises || !Array.isArray(promises) || !promises?.length) { - resolve(result); - } - - promises.forEach(async (promise, index) => { - try { - const res = await Promise.resolve(promise); - result[index] = res; - resolvedPromisesCount += 1; - - if (resolvedPromisesCount === promises.length) { - resolve(result); - } - } catch (e) { - reject(e); - } - }); - }); -} - -function allSettled(promises) { - const result = new Array(promises.length); - let total = 0; - - - return new Promise((resolve) => { - promises.forEach((promise, index) => { - Promise.resolve(promise) - .then((value) => { - result[index] = {status: 'fulfilled', value} - total += 1; - - if (total === promises.length) { - resolve(result); - } - }) - .catch((reason) => { - result[index] = {status: 'rejected', reason} - total += 1; - - if (total === promises.length) { - resolve(result); - } - }) - }); - }); -} - -function promiseAny(iters) { - const promises = [...iters].map((iter) => Promise.resolve(iter)); - const rejectsArray = new Array(promises.length); - let totalRejectedCount = 0; - - return new Promise((resolve, reject) => { - if (!promises.length) { - reject(rejectsArray); - } - - promises.forEach((promise, index) => { - promise - .then((value) => resolve(value)) - .catch((reason) => { - rejectsArray[index] = reason; - totalRejectedCount += 1; - - if (totalRejectedCount === promises.length) { - reject(rejectsArray) - } - }) - }); - }); -} - -function promiseRace(iters) { - const promises = [...iters].map((iter) => Promise.resolve(iter)); - - return new Promise((resolve, reject) => { - promises.forEach((promise) => { - promise.then((value) => resolve(value)); - }) - }); -} - -console.log(checkBrackets('[')) // false -console.log(checkBrackets('((()))[]{}')) // true -console.log(checkBrackets('([()])[]()')) // true -console.log(checkBrackets('([()][]()')) // false - -function checkBrackets(str) { - const map = { - '}': '{', - ')': '(', - ']': '[' - }; - - const stack = []; - - str.split('').forEach((char) => { - if (stack.length === 0) { - stack.push(char); - } else if (stack.at(-1) === map[char]) { - stack.pop(); - } else { - stack.push(char); - } - }); - - return stack.length === 0; -} \ No newline at end of file diff --git a/day_8/1.js b/day_8/1.js deleted file mode 100644 index 7074c92..0000000 --- a/day_8/1.js +++ /dev/null @@ -1,7 +0,0 @@ -console.log(diff([1, 2, 3, 4, 5], [3, 4, 1])); // [2, 5] - -function diff(firstArray, secondArray) { - const set = new Set(secondArray); - - return firstArray.filter((el) => !set.has(el)); -} \ No newline at end of file diff --git a/day_8/2.js b/day_8/2.js deleted file mode 100644 index 0c540d9..0000000 --- a/day_8/2.js +++ /dev/null @@ -1,25 +0,0 @@ -const semaphore = createsAsyncSemaphore(() => { - console.log('Boom!'); -}, 'foo', 'bar'); - -semaphore('foo'); -semaphore('bar'); // 'Boom!' - -// will not invoke anymore -semaphore(); - -function createsAsyncSemaphore(cb, ...flags) { - const conditions = new Set(flags); - let isInvoked = false; - - return function (flag) { - conditions.delete(flag); - - if (isInvoked || conditions.size) { - return; - } - - isInvoked = true; - cb.apply(this); - } -} \ No newline at end of file diff --git a/day_8/3.js b/day_8/3.js deleted file mode 100644 index f4bdb57..0000000 --- a/day_8/3.js +++ /dev/null @@ -1,50 +0,0 @@ -const original = { - myDate: new Date(), - mySet: new Set([1, 2, 3]), - myMap: new Map([ - [new Date(), {a: 1}] - ]) -}; - -const str = serialize(original); - -// Объект должен иметь аналогичную структуру с original -parse(str); - -function serialize(obj) { - const toJSON = Date.prototype.toJSON; - delete Date.prototype.toJSON; - - try { - return JSON.stringify(obj, (key, value) => { - if (value instanceof Date) { - return `[[DATA]]:Date;${value.valueOf()}`; - } - - if (value instanceof Set || value instanceof Set) { - return `[[DATA]]:${value.constructor.name};${serialize([...value])}`; - } - - return value; - }) - } finally { - Object.defineProperty(Date.prototype, 'toJSON', { - configurable: true, - enumerable: false, - writable: true, - value: toJSON, - }) - } -} - -function parse(str) { - return JSON.parse(str, (key, value) => { - if (typeof value === 'string' && value.startsWith('[[DATA]]')) { - const [_, type, data] = /^\[\[DATA]]:(.*?);(.*)/.exec(value); - - return Function('data', `return new ${type}(data)`)(parse(data)); - } - - return value; - }); -} \ No newline at end of file diff --git "a/day_9/\320\227\320\260\320\264\320\260\321\207\320\270 (10).md" b/day_8/day_8.md similarity index 84% rename from "day_9/\320\227\320\260\320\264\320\260\321\207\320\270 (10).md" rename to day_8/day_8.md index ec4529d..ebbb8b6 100644 --- "a/day_9/\320\227\320\260\320\264\320\260\321\207\320\270 (10).md" +++ b/day_8/day_8.md @@ -1,6 +1,6 @@ # День 8 -## Написать функцию throttle +## 1. Написать функцию throttle Необходимо написать функцию, которая бы принимала другую функцию и возвращала её throttle версию. @@ -18,7 +18,7 @@ throttledLaugh(); throttledLaugh(); ``` -## Написать функцию для сравнения двух объектов +## 2. Написать функцию для сравнения двух объектов Необходимо написать функцию для глубокого сравнения двух заданных объектов. @@ -27,7 +27,7 @@ console.log(compare({a: 1, b: [1, 2, 3]}, {a: 1, b: [1, 2, 3]})); // true console.log(compare({a: 1, b: [1, 2]}, {a: 1, b: [1, 2, 3]})); // false ``` -## Найти строку для которой нет анаграммы +## 3. Найти строку для которой нет анаграммы Необходимо написать функцию, которая бы принимала массив строк и возвращала бы новый массив. Элементами этого массива должны быть строки, для которых не существует анаграмм среди элементов первого массива. @@ -36,7 +36,7 @@ console.log(compare({a: 1, b: [1, 2]}, {a: 1, b: [1, 2, 3]})); // false console.log(getUniqueStrs(['atoe', 'otea', 'ben', 'enb', 'baz', 'foo'])); // ['baz', 'foo'] ``` -## Реализация структуры данных очередь +## 4. Реализация структуры данных очередь Необходимо создать класс, который бы предоставлял API очереди. Использовать стандартное API массивов JS нельзя. diff --git a/day_8/day_8_1.ts b/day_8/day_8_1.ts new file mode 100644 index 0000000..a1b8f58 --- /dev/null +++ b/day_8/day_8_1.ts @@ -0,0 +1,30 @@ +function throttle any>( + fn: T, + delay: number +): (...args: Parameters) => ReturnType | undefined { + let blocked = false; + return function (this: any, ...args: Parameters): ReturnType | undefined { + if (!blocked) { + blocked = true; + const result = fn.apply(this, args); + setTimeout(() => { + blocked = false; + }, delay); + return result; + } + }; +} + +function laugh() { + console.log('Ha-ha!') +} + +const throttledLaugh = throttle(laugh, 300); + +throttledLaugh(); +throttledLaugh(); +throttledLaugh(); +throttledLaugh(); +throttledLaugh(); + +export {}; \ No newline at end of file diff --git a/day_8/day_8_2.ts b/day_8/day_8_2.ts new file mode 100644 index 0000000..7e69f44 --- /dev/null +++ b/day_8/day_8_2.ts @@ -0,0 +1,27 @@ +function compare(objA: any, objB: any): boolean { + if (objA === objB) return true; + if (objA === null || objB === null) return false; + if (typeof objA !== typeof objB) return false; + if (typeof objA !== 'object') return objA === objB; + + if (Array.isArray(objA) !== Array.isArray(objB)) return false; + if (Array.isArray(objA)) { + if (objA.length !== objB.length) { + return false; + } + return objA.every((item, i) => compare(item, objB[i])); + } + + const keysA = Object.keys(objA); + const keysB = new Set(Object.keys(objB)); + if (keysA.length !== keysB.size) return false; + + return keysA.every(key => keysB.has(key) && compare(objA[key], objB[key])); +} + +console.log(compare({}, null)); // false +console.log(compare({a: null}, {b: null})); // false +console.log(compare({a: 1, b: [1, 2]}, {a: 1, b: [1, 2, 3]})); // false +console.log(compare(null, null)); // true +console.log(compare({a: undefined}, {a: undefined})); // true +console.log(compare({a: 1, b: [1, 2, 3]}, {a: 1, b: [1, 2, 3]})); // true \ No newline at end of file diff --git a/day_8/day_8_3.ts b/day_8/day_8_3.ts new file mode 100644 index 0000000..b3a5081 --- /dev/null +++ b/day_8/day_8_3.ts @@ -0,0 +1,12 @@ +function getUniqueStrs(strs: string[]): string[] { + const anagrams = new Map(); + const keys: string[] = []; + for (const str of strs) { + const key = str.split('').toSorted().join(''); + keys.push(key); + anagrams.set(key, (anagrams.get(key) || 0) + 1); + } + return strs.filter((_, i) => anagrams.get(keys[i]) === 1); +} + +console.log(getUniqueStrs(['atoe', 'otea', 'ben', 'enb', 'baz', 'foo'])); // ['baz', 'foo'] \ No newline at end of file diff --git a/day_8/day_8_4.ts b/day_8/day_8_4.ts new file mode 100644 index 0000000..00aebd4 --- /dev/null +++ b/day_8/day_8_4.ts @@ -0,0 +1,44 @@ +class QueueItem { + value: number; + next: QueueItem | null = null; + + constructor(value: number) { + this.value = value; + } +} + +class Queue { + #head: QueueItem | null = null; + #tail: QueueItem | null = null; + + push(value: number) { + const newItem = new QueueItem(value); + if (this.#tail) { + this.#tail.next = newItem; + this.#tail = newItem; + } else { + this.#head = newItem; + this.#tail = newItem; + } + } + + pop(): number | undefined { + if (this.#head) { + const item = this.#head; + this.#head = item.next; + if (!this.#head) this.#tail = null; + return item.value; + } + } +} + +const queue = new Queue(); + +queue.push(1); +queue.push(2); +queue.push(3); + +console.log(queue.pop()); // 1 +console.log(queue.pop()); // 2 +console.log(queue.pop()); // 3 +console.log(queue.pop()); // undefined \ No newline at end of file diff --git a/day_9/1.js b/day_9/1.js deleted file mode 100644 index 00715f0..0000000 --- a/day_9/1.js +++ /dev/null @@ -1,25 +0,0 @@ -function throttle(cb, delay) { - let timer; - - return function (...args) { - if (!timer) { - cb.apply(this, args); - - timer = setTimeout(() => { - timer = undefined; - }, delay); - } - } -} - -function laugh() { - console.log('Ha-ha!') -} - -const throttledLaugh = throttle(laugh, 300); - -throttledLaugh(); -throttledLaugh(); -throttledLaugh(); -throttledLaugh(); -setTimeout(() => throttledLaugh(), 500); \ No newline at end of file diff --git a/day_9/2.js b/day_9/2.js deleted file mode 100644 index e98a151..0000000 --- a/day_9/2.js +++ /dev/null @@ -1,52 +0,0 @@ -console.log(compare({a: 1, b: [1, 2, 3]}, {a: 1, b: [1, 2, 3]})); // true -console.log(compare({a: 1, b: [1, 2]}, {a: 1, b: [1, 2, 3]})); // false - -function compare(a, b) { - function isDeepComparable(value) { - return value?.constructor == null || value?.constructor === Object || Array.isArray(value); - } - - function sort([aKey], [bKey]) { - return aKey.localeCompare(bKey); - } - - if (!isDeepComparable(a) || !isDeepComparable(b)) { - return a === b; - } - - if (a.constructor !== b.constructor) { - return false; - } - - if (Array.isArray(a)) { - if (a.length !== b.length) { - return false; - } - - for (let i = 0; i < a.length; i + 1) { - if (!compare(a[i], b[i])) { - return false; - } - } - - return true; - } - - const aEntries = Object.entries(a); - const bEntries = Object.entries(b); - - if (aEntries.length !== bEntries.length) { - return false; - } - - aEntries.sort(sort); - bEntries.sort(sort); - - for (let i = 0; i < a.length; i + 1) { - if (!compare(aEntries[i], bEntries[i])) { - return false; - } - } - - return true; -} \ No newline at end of file diff --git a/day_9/3.js b/day_9/3.js deleted file mode 100644 index 3a464f5..0000000 --- a/day_9/3.js +++ /dev/null @@ -1,23 +0,0 @@ -console.log(getUniqueStrs(['atoe', 'otea', 'ben', 'enb', 'baz', 'foo'])); // ['baz', 'foo'] - -function getUniqueStrs(arr) { - const map = new Map(); - - arr.forEach((word) => { - const normalizedStr = word.split('').sort((a, b) => a.localeCompare(b)).join(''); - - if (map.has(normalizedStr)) { - map.get(normalizedStr).count++; - } else { - map.set(normalizedStr, {count: 1, word}) - } - }); - - return [...map.values()].reduce((acc, {count, word}) => { - if (count === 1) { - acc.push(word); - } - - return acc; - }, []) -} \ No newline at end of file diff --git a/day_9/4.js b/day_9/4.js deleted file mode 100644 index 2743da0..0000000 --- a/day_9/4.js +++ /dev/null @@ -1,19 +0,0 @@ -const queue = new Queue(); - -queue.push(1); -queue.push(2); -queue.push(3); - -console.log(queue.pop()); // 1 -console.log(queue.pop()); // 2 -console.log(queue.pop()); // 3 -console.log(queue.pop()); // undefined - - -class Queue { - head = {value: undefined, next: null}; - - push(value) { - - } -} \ No newline at end of file diff --git "a/day_11/\320\227\320\260\320\264\320\260\321\207\320\270 (11).md" b/day_9/day_9.md similarity index 83% rename from "day_11/\320\227\320\260\320\264\320\260\321\207\320\270 (11).md" rename to day_9/day_9.md index f4da58f..193cd86 100644 --- "a/day_11/\320\227\320\260\320\264\320\260\321\207\320\270 (11).md" +++ b/day_9/day_9.md @@ -1,6 +1,6 @@ # День 9 -## Реализовать функция бинарного поиска с callback +## 1. Реализовать функция бинарного поиска с callback Необходимо написать функцию, которая бы принимала отсортированный массив и функцию-компаратор и возвращала бы индекс найденного элемента (если компаратор вернул 0). Для поиска должен использоваться алгоритм бинарного поиска. @@ -10,7 +10,7 @@ console.log(bisect([1, 2, 3, 4, 5, 6, 7], (val) => 4 - val)); // 3 console.log(bisect([1, 2, 3, 4, 5, 6, 7], (val) => 234 - val)); // -1 ``` -## Реализовать функцию для амортизируемого повторного запроса +## 2. Реализовать функцию для амортизируемого повторного запроса Необходимо написать функцию retry, которая бы принимала бы функцию, возвращающую Promise и объект настроек для указания повторений в случае ошибки. Параметр `retry` задает максимальное количество повторения, а `delay` - задержку в мс между повторами (задается в виде функции, которая принимает номер попытки). @@ -19,7 +19,7 @@ console.log(bisect([1, 2, 3, 4, 5, 6, 7], (val) => 234 - val)); // -1 retry(() => fetch('//some-data'), {retry: 3, delay: (n) => n * 1000}).then(console.log, console.error); ``` -## Реализовать класс LRU кеша +## 3. Реализовать класс LRU кеша Необходимо реализовать класс кеширования с алгоритмом LRU. @@ -37,10 +37,10 @@ cache.set('key4', 4); console.log(cache.has('key2')); // false ``` -## Сжатие строки +## 4. Сжатие строки Необходимо написать функцию, которая бы принимала бы строку и "схлопывала" бы все подряд идущие повторения. ```js -console.log(zipStr('abbaabbafffbezza')); // abafbeza +console.log(zipStr('abbaabbafffbezza')); // ababafbeza ``` diff --git a/day_9/day_9_1.ts b/day_9/day_9_1.ts new file mode 100644 index 0000000..8e6e5e0 --- /dev/null +++ b/day_9/day_9_1.ts @@ -0,0 +1,16 @@ +function bisect(arr: number[], comparator: (val: number) => number): number { + let left = 0; + let right = arr.length - 1; + while (left <= right) { + let middle = left + ((right - left) >> 1) + let middleResult = comparator(arr[middle]); + if (middleResult === 0) return middle; + if (middleResult < 0) right = middle - 1; + else left = middle + 1; + } + return - 1; +} + +console.log(bisect([1, 2, 3, 4, 5, 6, 7], (val) => 4 - val)); // 3 +console.log(bisect([1, 2, 3, 4, 5, 6, 7], (val) => 234 - val)); // -1 +console.log(bisect([], (val) => 234 - val)); // -1 \ No newline at end of file diff --git a/day_9/day_9_2.ts b/day_9/day_9_2.ts new file mode 100644 index 0000000..888f5ff --- /dev/null +++ b/day_9/day_9_2.ts @@ -0,0 +1,24 @@ +interface RetryOptions { + retry: number; + delay: (retryNumber: number) => number; +} + +type RetryFn = (...args: any[]) => Promise; + +function retry, R>(fn: T, options: RetryOptions): Promise { + return new Promise((resolve, reject) => { + let retries = 0; + function tryExecute() { + fn().then(resolve).catch((err) => { + console.log(retries); + if (retries >= options.retry) return reject(err); + retries += 1; + const delay = options.delay(retries); + setTimeout(tryExecute, delay); + }); + } + tryExecute(); + }); +} + +retry(() => fetch('//some-data'), {retry: 3, delay: (n) => n * 100}).then(console.log, console.error); \ No newline at end of file diff --git a/day_9/day_9_3.ts b/day_9/day_9_3.ts new file mode 100644 index 0000000..2cccfbf --- /dev/null +++ b/day_9/day_9_3.ts @@ -0,0 +1,96 @@ +class LRUNode { + next: LRUNode | null = null; + prev: LRUNode | null = null; + key: string; + value: number; + + constructor (key: string, value: number) { + this.key = key; + this.value = value; + } +} + +class LRUCache { + #size: number; + #used: number = 0; + #head: LRUNode | null = null; + #tail: LRUNode | null = null; + #nodeByKey: Map = new Map(); + + constructor(size: number) { + this.#size = size; + } + + #moveToHead(node: LRUNode) { + if (this.#head === node) return; + + if (!this.#head) { + this.#head = node; + this.#tail = node; + return; + } + + if (node === this.#tail) this.#tail = node.prev; + + if (node.prev) node.prev.next = node.next; + if (node.next) node.next.prev = node.prev; + + node.prev = null; + node.next = this.#head; + this.#head.prev = node; + this.#head = node; + } + + set(key: string, value: number) { + if (this.#nodeByKey.has(key)) { + const node = this.#nodeByKey.get(key)!; + node.value = value; + this.#moveToHead(node); + } else { + const node = new LRUNode(key, value); + this.#nodeByKey.set(key, node); + this.#used += 1; + this.#moveToHead(node); + } + + if (this.#used > this.#size) { + const tail = this.#tail; + if (tail) { + this.#tail = tail.prev; + if (tail.prev) { + tail.prev.next = null; + } else { + this.#head = null; + } + this.#nodeByKey.delete(tail.key); + this.#used -= 1; + } + } + } + + get(key: string): number | undefined { + const node = this.#nodeByKey.get(key); + if (!node) return; + + this.#moveToHead(node); + + return node.value; + } + + has(key: string): boolean { + return this.#nodeByKey.has(key); + } +} + + +const cache = new LRUCache(3); // Размер кеша + +cache.set('key1', 1); +cache.set('key2', 2); +cache.set('key3', 3); + +console.log(cache.get('key1')); // 1 + +cache.set('key4', 4); + +console.log(cache.has('key2')); // false diff --git a/day_9/day_9_4.ts b/day_9/day_9_4.ts new file mode 100644 index 0000000..5efee83 --- /dev/null +++ b/day_9/day_9_4.ts @@ -0,0 +1,13 @@ +function zipStr(str: string): string { + const result: string[] = []; + let prev = ''; + for (const char of str) { + if (prev !== char) { + result.push(char); + prev = char; + } + } + return result.join(''); +} + +console.log(zipStr('abbaabbafffbezza')); // ababafbeza \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f56faa5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,217 @@ +{ + "name": "js-questions", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "js-questions", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "dev": true, + "peer": true, + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "peer": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6748122 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "js-questions", + "version": "1.0.0", + "description": "## Days", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1d89a84 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,113 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2024", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + "noImplicitThis": false, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}