diff --git "a/\354\234\244\352\261\264\355\235\254/ex1/book-edit.html" "b/\354\234\244\352\261\264\355\235\254/ex1/book-edit.html" index e69de29..9427ece 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex1/book-edit.html" +++ "b/\354\234\244\352\261\264\355\235\254/ex1/book-edit.html" @@ -0,0 +1,92 @@ + + + + + + Tailwind CSS Demo + + + + + +
+ +
+ +
+ +
+
+ +
+ + diff --git "a/\354\234\244\352\261\264\355\235\254/ex1/index.html" "b/\354\234\244\352\261\264\355\235\254/ex1/index.html" index e69de29..6e86816 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex1/index.html" +++ "b/\354\234\244\352\261\264\355\235\254/ex1/index.html" @@ -0,0 +1,144 @@ + + + + + + Tailwind CSS Demo + + + + + +
+ +
+ + + + +
+

Study

+
+ Image +
유튜브 스터디
+ +
+
+ Image +
뉴진스 스터디
+ +
+
+ Image +
페페 스터디
+ +
+
+ +
+
+
+ +
+ + diff --git "a/\354\234\244\352\261\264\355\235\254/ex1/register.html" "b/\354\234\244\352\261\264\355\235\254/ex1/register.html" index e69de29..3ec9e7a 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex1/register.html" +++ "b/\354\234\244\352\261\264\355\235\254/ex1/register.html" @@ -0,0 +1,95 @@ + + + + + + Tailwind CSS Demo + + + + + +
+ +
+

Sign Up

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + diff --git "a/\354\234\244\352\261\264\355\235\254/ex1/src/input.css" "b/\354\234\244\352\261\264\355\235\254/ex1/src/input.css" new file mode 100644 index 0000000..bd6213e --- /dev/null +++ "b/\354\234\244\352\261\264\355\235\254/ex1/src/input.css" @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git "a/\354\234\244\352\261\264\355\235\254/ex1/src/output.css" "b/\354\234\244\352\261\264\355\235\254/ex1/src/output.css" new file mode 100644 index 0000000..3070061 --- /dev/null +++ "b/\354\234\244\352\261\264\355\235\254/ex1/src/output.css" @@ -0,0 +1,51 @@ +/* Custom styles to ensure footer stays at the bottom */ +html, body { + height: 100%; + margin: 0; +} + +#wrap { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +footer { + margin-top: auto; +} + +/* Custom styles */ +.sidebar { + background-color: #f5f5f5; /* Light gray background */ + padding: 1rem; /* Same as ml-3 */ +} + +.main-content { + background-color: #ffffff; /* White background */ +} + +.add-book-btn { + background-color: #000000; /* Black background */ + color: #ffffff; /* White text */ +} + +.bookmark-menu { + background-color: #d9f7be; /* Light green background */ +} + +.bookmark-menu a { + color: #00bfae; /* Green text */ +} + diff --git "a/\354\234\244\352\261\264\355\235\254/ex1/tailwind.config.js" "b/\354\234\244\352\261\264\355\235\254/ex1/tailwind.config.js" new file mode 100644 index 0000000..a0cebb6 --- /dev/null +++ "b/\354\234\244\352\261\264\355\235\254/ex1/tailwind.config.js" @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./src/**/*.{html,js}"], + theme: { + extend: {}, + }, + plugins: [], +} \ No newline at end of file diff --git "a/\354\234\244\352\261\264\355\235\254/ex10.test.ts" "b/\354\234\244\352\261\264\355\235\254/ex10.test.ts" index 6218a79..e0868c1 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex10.test.ts" +++ "b/\354\234\244\352\261\264\355\235\254/ex10.test.ts" @@ -1,4 +1,32 @@ import { ArrayList } from './ex10'; -console.log('🚀 ArrayList:', ArrayList); +import assert from 'assert'; -// 여기에 테스트코드를 작성하세요. +const alist = new ArrayList(1, 2); + +alist.add(3); +alist.add(5, 1); +alist.removeByValue(2); +alist.add(22, 1); +alist.add(33, 1); + +console.log(alist.toString()); //ArrayList(4) { 1, 33, 22, 5 } + +alist.set(1, 300); +assert.strictEqual(alist.get(1), 300); +assert.strictEqual(alist.get(2), 22); +assert.strictEqual(alist.size(), 4); + +assert.strictEqual(alist.indexOf(300), 1); //인덱스 1에 300 +assert.strictEqual(alist.contains(300), true); //300 존재 +assert.strictEqual(alist.contains(301), false); //301은 x + +assert.strictEqual(alist.isEmpty, false); //배열에 요소가 존재 +assert.strictEqual(alist.peek, 5); //마지막 요소=5 + +console.log(alist.toArray()); //[1, 300, 22, 5] + +assert.strictEqual(alist.iterator().next().value, 5); +alist.clear(); // 모두 제거 + +assert.strictEqual(alist.isEmpty, true); //배열이 비었음 +console.log('All tests passed.'); diff --git "a/\354\234\244\352\261\264\355\235\254/ex10.ts" "b/\354\234\244\352\261\264\355\235\254/ex10.ts" index 1ffaef5..0a1888c 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex10.ts" +++ "b/\354\234\244\352\261\264\355\235\254/ex10.ts" @@ -1,71 +1,30 @@ -class Collection { - private readonly arr = Array(); +import { ArrayList } from './ex10'; +import assert from 'assert'; - constructor(...args: T[]) { - this.arr.push(...args); - } +const alist = new ArrayList(1, 2); +alist.add(3); +alist.add(5, 1); +alist.removeByValue(2); +alist.add(22, 1); +alist.add(33, 1); +console.log(alist.toString()); // ArrayList(4) { 1, 33, 22, 5 } - get _arr() { - return this.arr; - } +alist.set(1, 300); +assert.strictEqual(alist.get(1), 300); +assert.strictEqual(alist.get(2), 22); +assert.strictEqual(alist.size(), 4); - push(...args: T[]) { - this.arr.push(...args); - return this.arr; - } +assert.strictEqual(alist.indexOf(300), 1); +assert.strictEqual(alist.contains(300), true); +assert.strictEqual(alist.contains(301), false); - get peek(): T | undefined { - return this.isQueue() ? this.arr[0] : this.arr.at(-1); - } +assert.strictEqual(alist.isEmpty, false); +assert.strictEqual(alist.peek, 5); - get poll(): T | undefined { - return this.isQueue() ? this.arr.shift() : this.arr.pop(); - } +console.log(alist.toArray()); - remove() { - return this.poll; - } +assert.strictEqual(alist.iterator().next().value, 5); // 이제 오류 없이 반복기에서 첫 요소를 확인 가능 +alist.clear(); - get length() { - return this.arr.length; - } - - get isEmpty() { - return !this.arr.length; - } - - clear() { - this.arr.length = 0; - } - - iterator() { - return this[Symbol.iterator](); - } - - // [1, 2, 3] - *[Symbol.iterator]() { - for (let i = this.length - 1; i >= 0; i -= 1) { - yield this.toArray()[i]; - } - } - - toArray() { - return this.isQueue() ? this.arr.toReversed() : this.arr; - } - - print() { - console.log(`<${this.constructor.name}: [${this.toArray()}]>`); - } - - private isQueue() { - return this instanceof Queue; - } -} - -class Stack extends Collection {} -class Queue extends Collection {} - -// ArrayList 클래스를 작성하세요. -class ArrayList extends Collection {} - -export { Stack, Queue, ArrayList }; +assert.strictEqual(alist.isEmpty, true); +console.log('All tests passed.'); diff --git "a/\354\234\244\352\261\264\355\235\254/ex2.js" "b/\354\234\244\352\261\264\355\235\254/ex2.js" index 6b95f04..18d518f 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex2.js" +++ "b/\354\234\244\352\261\264\355\235\254/ex2.js" @@ -1,4 +1,45 @@ -// range 함수를 작성하세요. -const range = (start, end, step = start > end ? -1 : 1) => { }; +const range = (s,e,step = s > e ? -1 : 1)=>{ + if (step===0 || s===e) { + return [s]; + } + + if (e===undefined) { + if (s<0) { + e=-1; + } + else if (s>0) { + e=s; + s=1; + } + else return [0]; + } + + if ((se && step>0)) // 예외처리 + return []; + + + const result = []; + if(step<0) { + for (let i=s;i>=e;i=parseFloat((i+step).toFixed(2))) { //소수점 둘쨰 자리까지 고정 + result.push(i); + } + } + else { + for (let i=s;i<=e;i=parseFloat((i+step).toFixed(2))) { + result.push(i); + } + } + return result; +}; module.exports = { range }; + + + console.log(range(1, 5, 1)); // [1, 2, 3, 4, 5] + console.log(range(1, 10, 2)); // [1, 3, 5, 7, 9] + console.log(range(1, 10)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + console.log(range(10, 1)); // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + console.log(range(10, 1, -2)); // [10, 8, 6, 4, 2] + console.log(range(7)); // [1, 2, 3, 4, 5, 6, 7] + console.log(range(100)); // [1, 2, 3, 4, 5, ..., 99, 100 + \ No newline at end of file diff --git "a/\354\234\244\352\261\264\355\235\254/ex3.js" "b/\354\234\244\352\261\264\355\235\254/ex3.js" index b1b0d75..9e7b744 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex3.js" +++ "b/\354\234\244\352\261\264\355\235\254/ex3.js" @@ -1,3 +1,27 @@ -Array.prototype.sortBy = function (sortProp = '') { - return this; +Array.prototype.sortBy = function (sortKey = '') { + const props = sortKey.split(',').map(prop => { + const [key, dir] = prop.split(':'); + + return { key, dir: dir ? dir : 'asc' }; + }).filter(prop => prop.key); + + //정렬 + return this.slice().sort((a, b) => { + for (const { key, dir } of props) { + const x = a[key]; + const y = b[key]; + + //undefined는 항상 작은거로 여기기 + if (x === undefined || y === undefined) { + if (x === undefined) return 1; //x가 undefined일 때 1 반환 + else return -1; //y가 undefined일 때 -1 반환 + } + + if (x > y) return dir === 'asc' ? 1 : -1; + if (x < y) return dir === 'asc' ? -1 : 1; + } + return 0; //값이 같으면 순서유지 + }); }; + +module.exports = {}; diff --git "a/\354\234\244\352\261\264\355\235\254/ex3.test.js" "b/\354\234\244\352\261\264\355\235\254/ex3.test.js" index 6c27a4d..bc37804 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex3.test.js" +++ "b/\354\234\244\352\261\264\355\235\254/ex3.test.js" @@ -8,11 +8,11 @@ const users = [lee, hong, kim]; assert.deepStrictEqual(users.sortBy('id'), [hong, kim, lee]); assert.deepStrictEqual(users.sortBy('name:desc'), [lee, kim, hong]); -assert.deepStrictEqual(users.sortBy('dept:desc,city:asc'), [hong, lee, kim]); +assert.deepStrictEqual(users.sortBy('dept:desc,city:asc'), [lee, kim, hong]); assert.deepStrictEqual(users.sortBy('dept:desc,city:desc'), [kim, lee, hong]); assert.deepStrictEqual(users.sortBy('name:desc,id:,dept:desc'), [ - kim, lee, + kim, hong, ]); -assert.deepStrictEqual(users.sortBy('dept:desc,id'), [hong, kim, lee]); +assert.deepStrictEqual(users.sortBy('dept:desc,id'), [kim, lee, hong]); \ No newline at end of file diff --git "a/\354\234\244\352\261\264\355\235\254/ex4.js" "b/\354\234\244\352\261\264\355\235\254/ex4.js" index 9ede02f..407574c 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex4.js" +++ "b/\354\234\244\352\261\264\355\235\254/ex4.js" @@ -1,3 +1,58 @@ -function deepCopy(obj) {} +function deepCopy(obj, map = new WeakMap()) { + if (obj === null || typeof obj !== 'object') + return obj; + + if (map.has(obj)) + return map.get(obj); + + const type = Object.prototype.toString.call(obj); + let copy; + + if (Array.isArray(obj)) { + //배열 복사 + copy = []; + map.set(obj, copy); + obj.forEach(item => copy.push(deepCopy(item, map))); + } + else if (type === '[object Map]') { + //Map 복사 + copy = new Map(); + map.set(obj, copy); + obj.forEach((value, key) => copy.set(deepCopy(key, map), deepCopy(value, map))); + } + else if (type === '[object Set]') { + //Set 복사 + copy = new Set(); + map.set(obj, copy); + obj.forEach(item => copy.add(deepCopy(item, map))); + } + else if (type === '[object WeakMap]') { + //WeakMap 복사 + copy = new WeakMap(); + map.set(obj, copy); + } + else if (type === '[object WeakSet]') { + // WeakSet 복사 + copy = new WeakSet(); + map.set(obj, copy); + } + else if (type === '[object Date]') { + //Date 복사 + copy = new Date(obj.getTime()); + } + else if (type === '[object RegExp]') { + //RegExp 복사 + copy = new RegExp(obj); + } + else { + //일반객체 복사 + copy = Object.create(Object.getPrototypeOf(obj)); + map.set(obj, copy); + Object.keys(obj).forEach(key => copy[key] = deepCopy(obj[key], map)); + Object.getOwnPropertySymbols(obj).forEach(symbol => copy[symbol] = deepCopy(obj[symbol], map)); + } + + return copy; +} module.exports = { deepCopy }; diff --git "a/\354\234\244\352\261\264\355\235\254/ex5.js" "b/\354\234\244\352\261\264\355\235\254/ex5.js" index 464a05a..d254c37 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex5.js" +++ "b/\354\234\244\352\261\264\355\235\254/ex5.js" @@ -1,3 +1,32 @@ -module.exports = { - searchByKoreanInitialSound: (data, firstSounds) => {}, +const initialSoundMap = { + 'ㄱ': '[ㄱㄲ]', + 'ㄲ': '[ㄲ]', + 'ㄴ': '[ㄴ]', + 'ㄷ': '[ㄷ]', + 'ㄸ': '[ㄸ]', + 'ㄹ': '[ㄹ]', + 'ㅁ': '[ㅁ]', + 'ㅂ': '[ㅂ]', + 'ㅃ': '[ㅃ]', + 'ㅅ': '[ㅅ]', + 'ㅆ': '[ㅆ]', + 'ㅇ': '[ㅇ]', + 'ㅈ': '[ㅈ]', + 'ㅉ': '[ㅉ]', + 'ㅊ': '[ㅊ]', + 'ㅋ': '[ㅋ]', + 'ㅌ': '[ㅌ]', + 'ㅍ': '[ㅍ]', + 'ㅎ': '[ㅎ]', }; + +const searchByKoreanInitialSound = (data, initSound) => { + //정규식패턴 + const pattern = initSound.split('').map(char => + initialSoundMap[char] !== undefined ? initialSoundMap[char] : char).join('.*?'); + const regExp = new RegExp(pattern); + + return data.filter(item => regExp.test(item)); +}; + +module.exports = {searchByKoreanInitialSound}; diff --git "a/\354\234\244\352\261\264\355\235\254/ex6.ts" "b/\354\234\244\352\261\264\355\235\254/ex6.ts" index 424ca54..95713a5 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex6.ts" +++ "b/\354\234\244\352\261\264\355\235\254/ex6.ts" @@ -1,4 +1,11 @@ export const randTime = (val: T): Promise => new Promise(resolve => setTimeout(resolve, Math.random() * 1000, val)); -export function promiseAllSettled(promises: Promise[]) {} +export function promiseAllSettled(promises: Promise[]): Promise> { + return Promise.all(promises.map(p => + p.then( + (value): { status: "fulfilled"; value: T } => ({ status: "fulfilled", value }), + (reason): { status: "rejected"; reason: any } => ({ status: "rejected", reason }) + ) + )); +} diff --git "a/\354\234\244\352\261\264\355\235\254/ex7.test.ts" "b/\354\234\244\352\261\264\355\235\254/ex7.test.ts" index 62b881d..18eb4d7 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex7.test.ts" +++ "b/\354\234\244\352\261\264\355\235\254/ex7.test.ts" @@ -64,7 +64,24 @@ async function test(userId: string | number) { ], }); - // 추가 테스트 코드를 작성하시오. + // 추가 테스트 코드 작성 +// 1. 첫 번째 댓글의 이메일과 첫 번째 게시글의 댓글 수 +assert.strictEqual(posts[0].comments?.[0].email, 'Eliseo@gardner.biz'); +assert.strictEqual(posts[0].comments?.length, 5); + +// 2. 두번째 댓글의 이메일,, 두번째 게시글의 댓글 수 +assert.strictEqual(posts[1].comments?.[1].email, 'Jayne_Kuhic@sydney.com'); +assert.strictEqual(posts[1].comments?.length, 5); + + +//3. 마지막 게시글의 제목과 댓글 수 +assert.strictEqual(posts[9].title, 'qui eius qui autem sed'); +assert.strictEqual(posts[9].comments?.length, 5); + +//4. userId가 0일 때 게시글이 비어 있는지 +const emptyPosts = await getPosts(0); +assert.strictEqual(emptyPosts.length, 0); + } test(1); diff --git "a/\354\234\244\352\261\264\355\235\254/ex7.ts" "b/\354\234\244\352\261\264\355\235\254/ex7.ts" index 62812ac..b011721 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex7.ts" +++ "b/\354\234\244\352\261\264\355\235\254/ex7.ts" @@ -1,3 +1,46 @@ const POST_URL = 'https://jsonplaceholder.typicode.com/posts'; +const COMMENT_URL = 'https://jsonplaceholder.typicode.com/comments'; -export async function getPosts(userId: number | string) {} +interface Comment { + postId: number; + id: number; + email: string; + body: string; +} + +interface Post { + postId: number; + title: string; + comments?: Comment[]; +} + +export async function getPosts(userId: number | string): Promise { + try { + //게시글 불러옴 + const postsResponse = await fetch(`${POST_URL}?userId=${userId}`); + const postsData = await postsResponse.json(); + + //게시글 10개까지 처리 + const limitedPosts = postsData.slice(0, 10); + + //게시글의 댓글 + const postsWithComments = await Promise.all( + limitedPosts.map(async (post: { id: number; title: string }) => { + + const commentsResponse = await fetch(`${COMMENT_URL}?postId=${post.id}`); + const comments = await commentsResponse.json(); + + return { + postId: post.id, + title: post.title, + comments: comments.slice(0, 5), //댓글 5개까지 포함 + }; + }) + ); + + return postsWithComments; + } catch (error) { + console.error('Error fetching posts or comments:', error); + return []; + } +} diff --git "a/\354\234\244\352\261\264\355\235\254/ex8.ts" "b/\354\234\244\352\261\264\355\235\254/ex8.ts" index a67a2d2..855c615 100644 --- "a/\354\234\244\352\261\264\355\235\254/ex8.ts" +++ "b/\354\234\244\352\261\264\355\235\254/ex8.ts" @@ -1,11 +1,32 @@ -// dummy(mock)입니다. 올바르게 수정하세요. -const debounce = (cb: any, delay: number) => (i: number) => {}; -const throttle = (cb: any, delay: number) => (i: number) => {}; +function debounce void>(cb: T, delay: number): (...args: Parameters) => void { + let id: NodeJS.Timeout | null = null; + + return (...args: Parameters) => { -// function throttle... - -const debo = debounce((a: number) => console.log(a + 1), 500); -for (let i = 10; i < 15; i++) debo(i); // 15 출력 - -const thro = throttle((a: number) => console.log(a + 1), 500); -for (let i = 10; i < 15; i++) thro(i); // 11 출력 + if (id) + clearTimeout(id); + id = setTimeout(() => { + cb(...args); + }, delay); + }; +} + + function throttle void>(cb: T, delay: number): (...args: Parameters) => void { + let last = 0; + + return (...args: Parameters) => { + const now = Date.now(); + + if (now - last >= delay) { + last = now; + cb(...args); + } + }; + } + + // 테스트 + const debo = debounce((a: number) => console.log(a + 1), 500); + for (let i = 10; i < 15; i++) debo(i); //15(마지막 호출) + + const thro = throttle((a: number) => console.log(a + 1), 500); + for (let i = 10; i < 15; i++) thro(i); //11(첫 호출) \ No newline at end of file diff --git "a/\354\234\244\352\261\264\355\235\254/tsconfig.json" "b/\354\234\244\352\261\264\355\235\254/tsconfig.json" new file mode 100644 index 0000000..4f1373c --- /dev/null +++ "b/\354\234\244\352\261\264\355\235\254/tsconfig.json" @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "CommonJS", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +}