From 01159b4dc8f331e4a564f9ec46a32fd16635665a Mon Sep 17 00:00:00 2001 From: siny Date: Wed, 1 Feb 2023 18:58:02 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[siny]=20unique,=20groupBy=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/siny/ps/solution1.js | 8 +-- src/refactoring/siny/ps/solution2.js | 2 +- src/refactoring/siny/utils.js | 92 ++++++++++++++++++++++++++++ src/utils/groupBy.test.js | 8 +-- src/utils/lib.js | 5 +- src/utils/unique.test.js | 9 +-- 6 files changed, 108 insertions(+), 16 deletions(-) diff --git a/src/refactoring/siny/ps/solution1.js b/src/refactoring/siny/ps/solution1.js index 5a5c4a4..54b896a 100644 --- a/src/refactoring/siny/ps/solution1.js +++ b/src/refactoring/siny/ps/solution1.js @@ -1,7 +1,5 @@ -const { reduce, pipe, curry2, sort, filter } = require('../utils.js'); +const { reduce, pipe, curry2, sort, G } = require('../utils.js'); const curredSort = curry2(sort); -const curredFilter = curry2(filter); - const solution1 = arr => { const calcMulti = (acc, item, i) => { if (i % 2) return acc; @@ -21,8 +19,8 @@ const solution1 = arr => { const _중간결과 = pipe(calcAddByArr, calcMultiByArr)(arr); const sortByAsc = curredSort((a, b) => a - b); - const _2의_배수만_filter = curredFilter(item => !(item % 2)); - const _3의_배수만_filter = curredFilter(item => !(item % 3)); + const _2의_배수만_filter = G.filter(item => !(item % 2)); + const _3의_배수만_filter = G.filter(item => !(item % 3)); return { t: pipe(_2의_배수만_filter, sortByAsc)(_중간결과), h: pipe(_3의_배수만_filter, sortByAsc)(_중간결과), diff --git a/src/refactoring/siny/ps/solution2.js b/src/refactoring/siny/ps/solution2.js index a5c941a..bfe7bd2 100644 --- a/src/refactoring/siny/ps/solution2.js +++ b/src/refactoring/siny/ps/solution2.js @@ -8,7 +8,7 @@ const solution2 = str => { const splitByComma = curredSplit(','); const addToSet = (set, key) => set.add(key); const compareTo_글자수가_긴것_우선정렬_글자수가_같으면_알파벳빠른순 = (a, b) => - (b.length === a.length && a.charCodeAt(0) - b.charCodeAt(0)) || b.length - a.length; + (b.length === a.length && a < b ? -1 : 1) || b.length - a.length; return pipe( splitByComma, diff --git a/src/refactoring/siny/utils.js b/src/refactoring/siny/utils.js index 8e4d92c..d493e3e 100644 --- a/src/refactoring/siny/utils.js +++ b/src/refactoring/siny/utils.js @@ -60,6 +60,96 @@ const toArray = obj => { const trim = str => str.trim(); const split = (separator, str) => str.split(separator); +const G = {}; + +function* gMap(fn, arr) { + for (const item of arr) { + yield fn(item); + } +} + +function* gFilter(fn, arr) { + for (const item of arr) { + if (fn(item)) yield item; + } +} + +function* gTake(num, arr) { + let count = 0; + for (const item of arr) { + if (count++ !== num) yield item; + } +} + +function* gTakeWhile(fn, arr) { + for (const item of arr) { + if (fn(item)) yield item; + } +} + +function* gRange(end) { + let start = 0; + while (start !== end) { + yield start++; + } +} + +function* gSkip(n, arr) { + let count = 0; + for (const item of arr) { + if (count++ >= n) yield item; + } +} + +G.map = curry2(gMap); +G.filter = curry2(gFilter); +G.range = gRange; +G.take = curry2(gTake); +G.takeWhile = curry2(gTakeWhile); +G.skip = curry2(gSkip); + +function groupBy(iter, callback) { + const newObj = {}; + const genKey = item => { + if (typeof callback === 'function') { + return callback(item); + } else { + // callback은 index 혹은 key + return item[callback]; + } + }; + const genIter = anIter => { + if (anIter instanceof Array) { + return anIter; + } + if (anIter instanceof Object) { + return Object.values(anIter); + } + return anIter; + }; + for (const item of genIter(iter)) { + let key = genKey(item); + + newObj[key] = newObj?.[key] === undefined ? [item] : [...newObj[key], item]; + } + return newObj; +} + +function* gUnique(arr) { + const map = new Map(); + if (arr === undefined || arr === null) { + throw new Error('첫번째 인자는 꼭 필요합니다.'); + } + for (const item of arr) { + const key = JSON.stringify(item); + if (!map.has(key)) { + yield item; + } + map.set(key, item); + } +} + +G.unique = gUnique; module.exports = { filter, reduce, @@ -71,4 +161,6 @@ module.exports = { curry2, curry3, toArray, + groupBy, + G, }; diff --git a/src/utils/groupBy.test.js b/src/utils/groupBy.test.js index eddd03a..0bd2763 100644 --- a/src/utils/groupBy.test.js +++ b/src/utils/groupBy.test.js @@ -1,9 +1,7 @@ // FIXME: npm test /src/utils/groupBy.test.js - +const { timer } = require('../utils/lib.js'); +const { groupBy } = require('../refactoring/siny/utils'); // 어떤 것을 해볼까요? -const groupBy = (arr, callback) => { - return arr; -}; describe('groupBy 테스트', () => { describe('non-lazy', () => { @@ -42,7 +40,9 @@ describe('groupBy 테스트', () => { }); it('case: 3, Advanced', () => { + timer.start(); const grouped = groupBy({ a: 6.1, b: 4.2, c: 6.3 }, Math.floor); + timer.end(); expect(grouped).toEqual({ 4: [4.2], 6: [6.1, 6.3] }); }); diff --git a/src/utils/lib.js b/src/utils/lib.js index c13d4f7..19476c9 100644 --- a/src/utils/lib.js +++ b/src/utils/lib.js @@ -1,3 +1,5 @@ +const { G, map } = require('../refactoring/siny/utils.js'); + // FIXME: 프로토타입 ================================================================= Array.prototype.generate = function (size = 10) { let returnArray = []; @@ -20,7 +22,7 @@ Array.prototype.shuffle = function () { }; // FIXME: 상수 라인=================================================================== -const 엄청_큰_배열_크기 = 1000; +const 엄청_큰_배열_크기 = 10000; const 이름_목록 = [ 'Zachary', @@ -363,7 +365,6 @@ const timer = { }; const getMockData = () => Object.assign([], 무작위_데이터_리스트); - module.exports = { timer, getMockData, diff --git a/src/utils/unique.test.js b/src/utils/unique.test.js index b5f3a92..7bea003 100644 --- a/src/utils/unique.test.js +++ b/src/utils/unique.test.js @@ -1,9 +1,10 @@ // FIXME: npm test /src/utils/unique.test.js // 어떤 것을 해볼까요? -const unique = (arr, callback, isSorted) => { - return arr; -}; + +const { G } = require('../refactoring/siny/utils.js'); +const gUnique = G.unique; +const unique = (arr, callback, isSorted) => (isSorted ? [...gUnique(arr)].sort() : [...gUnique(arr)]); describe('unique 테스트', () => { describe('non-lazy', () => { @@ -21,7 +22,7 @@ describe('unique 테스트', () => { it('case: 2, Advanced', () => { const [firstArray, secondArray, thirdArray] = [ - [1, 2, 3], + [3, 2, 1], [1, 1, 2, 2, 3], [1, 2, 3, 3, 3, 3, 3], ]; From a224dc0665dcace1255e8337157fbfd82e1f8641 Mon Sep 17 00:00:00 2001 From: siny Date: Wed, 1 Feb 2023 21:35:21 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[siny]=20MyPromise=20then=EA=B9=8C=EC=A7=80?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/siny/MyPromise.test.js | 98 ++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/refactoring/siny/MyPromise.test.js diff --git a/src/refactoring/siny/MyPromise.test.js b/src/refactoring/siny/MyPromise.test.js new file mode 100644 index 0000000..c54fd6d --- /dev/null +++ b/src/refactoring/siny/MyPromise.test.js @@ -0,0 +1,98 @@ +// new 키워드로 생성한다. +/** + * 기본형 + * new MyPromise((res,rej)=>{ + * setTimeout(()=>{ + * + * res('값'); + * },100) + * }).then(data) + * + * 1. 해당 클래스의 constructor 가 받는 함수는 res,rej를 반환하는 일급함수이다. + * 2. res는 실행 이후에 then,catch,finally 메서드를 실행할 수 있다. + * 3. res나 rej를 실행하기 전에 then, catch,finally를 실행하면, 지연실행이 된다. + * 4. then의 첫번째 인자인 일급함수를 실행하면 fullfiled가 되고, 해당 함수가 반환하는 값을 value로 갖는다. + * 5. then의 두번째 인자인 일급함수를 실행하면, rejected가 되고 해당함수가 반환하는 에러를 value로 갖는다. + * 6. catch의 첫번째 인자인 일급함수를 실행하면, rejected가 되고 해당함수가 반환하는 에러를 value로 갖는다. + * + * */ + +class MyPromise { + #value; + #status; + #error; + #PENDING = 'pending'; + #FULFILLED = 'fulfilled'; + #REJECTED = 'rejected'; + #fulfilledCallbacks = []; + constructor(executor) { + if (executor === undefined || executor === null) { + throw new Error('executor 필수'); + } + if (typeof executor !== 'function') { + throw new Error('executor는 꼭 함수'); + } + this.#status = this.#PENDING; + executor(this.resolve.bind(this), this.reject.bind(this)); + } + + reject(error) { + if (this.#status === this.#PENDING) { + this.#error = error; + this.#status = this.#REJECTED; + } + } + resolve(data) { + if (this.#status === this.#PENDING) { + this.#value = data; + this.#status = this.#FULFILLED; + this.#fulfilledCallbacks.forEach(cb => cb(this.#value)); + } + } + + then(onRes, onRej) { + const promise = new MyPromise((res, rej) => { + if (this.#status === this.#PENDING) { + this.#fulfilledCallbacks.push(value => { + setTimeout(() => { + onRes(value); + }); + }); + } + if (this.#status === this.#FULFILLED) { + res(onRes(this.#value)); + } + if (this.#status === this.#REJECTED) { + rej(onRej(this.#error)); + } + }); + return promise; + } +} + +const sleep = ms => + new Promise(res => { + setTimeout(res, ms); + }); + +describe('MyPromise', () => { + it('new MyPromise() 를 실행하면 에러를 반환한다,', () => { + expect(() => new MyPromise()).toThrow(); + }); + it('new MyPromise(1) 를 실행하면 에러를 반환한다,', () => { + expect(() => new MyPromise(1)).toThrow('executor는 꼭 함수'); + }); + it('new MyPromise(() => {})를 실행하면 에러가 발생하지 않는다. ', () => { + expect(() => new MyPromise(() => {})).not.toThrow(); + }); + it('실행해봐', async () => { + let value; + new MyPromise((res, rej) => { + setTimeout(() => { + res(123); + }, 500); + }).then(data => (value = data)); + await sleep(1000); + expect(value).toEqual(123); + }); +});