diff --git a/package.json b/package.json
index c356ea8..0611333 100644
--- a/package.json
+++ b/package.json
@@ -11,5 +11,14 @@
"TS"
],
"author": "indiflex",
- "license": "ISC"
+ "license": "ISC",
+ "devDependencies": {
+ "prettier": "^3.3.3",
+ "prettier-plugin-tailwindcss": "^0.6.6",
+ "tailwindcss": "^3.4.10"
+ },
+ "dependencies": {
+ "@types/node": "^22.5.4",
+ "package.json": "^2.0.1"
+ }
}
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/assets/images/kakao.png" "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/kakao.png"
new file mode 100644
index 0000000..5a7ae79
Binary files /dev/null and "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/kakao.png" differ
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/assets/images/naver.png" "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/naver.png"
new file mode 100644
index 0000000..c46a3a7
Binary files /dev/null and "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/naver.png" differ
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/assets/images/next.js.svg" "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/next.js.svg"
new file mode 100644
index 0000000..5037097
--- /dev/null
+++ "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/next.js.svg"
@@ -0,0 +1,31 @@
+
+
+
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/assets/images/onion.jpg" "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/onion.jpg"
new file mode 100644
index 0000000..1eecb94
Binary files /dev/null and "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/onion.jpg" differ
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/assets/images/tailwind-css.svg" "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/tailwind-css.svg"
new file mode 100644
index 0000000..c7b4103
--- /dev/null
+++ "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/tailwind-css.svg"
@@ -0,0 +1,32 @@
+
+
+
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/assets/images/youtube.svg" "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/youtube.svg"
new file mode 100644
index 0000000..d3fd48e
--- /dev/null
+++ "b/\354\236\204\354\210\230\354\247\204/ex1/assets/images/youtube.svg"
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/book-edit.html" "b/\354\236\204\354\210\230\354\247\204/ex1/book-edit.html"
deleted file mode 100644
index e69de29..0000000
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/index.html" "b/\354\236\204\354\210\230\354\247\204/ex1/index.html"
deleted file mode 100644
index e69de29..0000000
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/register.html" "b/\354\236\204\354\210\230\354\247\204/ex1/register.html"
deleted file mode 100644
index e69de29..0000000
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/src/book-edit.html" "b/\354\236\204\354\210\230\354\247\204/ex1/src/book-edit.html"
new file mode 100644
index 0000000..d42e5b1
--- /dev/null
+++ "b/\354\236\204\354\210\230\354\247\204/ex1/src/book-edit.html"
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+ 북마크 편집
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/src/index.html" "b/\354\236\204\354\210\230\354\247\204/ex1/src/index.html"
new file mode 100644
index 0000000..5ed3167
--- /dev/null
+++ "b/\354\236\204\354\210\230\354\247\204/ex1/src/index.html"
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+ 북마크 편집
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
My BookMark
+
나만의 북마크를 만들어 보세요 :)
+
+
+
+

+
+
+
+
+
+
+

+
+
네이버
+
네이버 메인에서 다양한 정보와 유용한 컨텐츠를 만나 보세요
+
+
+
+

+
+
+
+
+
+
+
+

+
+
Kakao
+
기술과 사람으로 더 나은 세상을 만듭니다.
+
+
+
+

+
+
+
+
+
+
+
+

+
+
Youtube
+
YouTube에서 마음에 드는 동영상과 음악을 감상하고, 직접 만든 콘텐츠를 업로드하여 친구, 가족뿐 아니라 전 세계 사람들과 공유할 수 있습니다.
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
Tailwind CSS
+
Tailwind CSS is a utility-first CSS framework for rapidly building modern websites without ever leaving your HTML.
+
+
+
+

+
+
+
+
+
+
+

+
+
Create Next App
+
Generated by create next app
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/src/input.css" "b/\354\236\204\354\210\230\354\247\204/ex1/src/input.css"
new file mode 100644
index 0000000..bd6213e
--- /dev/null
+++ "b/\354\236\204\354\210\230\354\247\204/ex1/src/input.css"
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/src/output.css" "b/\354\236\204\354\210\230\354\247\204/ex1/src/output.css"
new file mode 100644
index 0000000..9c0d472
--- /dev/null
+++ "b/\354\236\204\354\210\230\354\247\204/ex1/src/output.css"
@@ -0,0 +1,1185 @@
+/*
+! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com
+*/
+
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+ /* 1 */
+ border-width: 0;
+ /* 2 */
+ border-style: solid;
+ /* 2 */
+ border-color: #e5e7eb;
+ /* 2 */
+}
+
+::before,
+::after {
+ --tw-content: '';
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+5. Use the user's configured `sans` font-feature-settings by default.
+6. Use the user's configured `sans` font-variation-settings by default.
+7. Disable tap highlights on iOS
+*/
+
+html,
+:host {
+ line-height: 1.5;
+ /* 1 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */
+ -moz-tab-size: 4;
+ /* 3 */
+ -o-tab-size: 4;
+ tab-size: 4;
+ /* 3 */
+ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ /* 4 */
+ font-feature-settings: normal;
+ /* 5 */
+ font-variation-settings: normal;
+ /* 6 */
+ -webkit-tap-highlight-color: transparent;
+ /* 7 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+ margin: 0;
+ /* 1 */
+ line-height: inherit;
+ /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+ height: 0;
+ /* 1 */
+ color: inherit;
+ /* 2 */
+ border-top-width: 1px;
+ /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font-family by default.
+2. Use the user's configured `mono` font-feature-settings by default.
+3. Use the user's configured `mono` font-variation-settings by default.
+4. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ /* 1 */
+ font-feature-settings: normal;
+ /* 2 */
+ font-variation-settings: normal;
+ /* 3 */
+ font-size: 1em;
+ /* 4 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+ font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+ text-indent: 0;
+ /* 1 */
+ border-color: inherit;
+ /* 2 */
+ border-collapse: collapse;
+ /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ /* 1 */
+ font-feature-settings: inherit;
+ /* 1 */
+ font-variation-settings: inherit;
+ /* 1 */
+ font-size: 100%;
+ /* 1 */
+ font-weight: inherit;
+ /* 1 */
+ line-height: inherit;
+ /* 1 */
+ letter-spacing: inherit;
+ /* 1 */
+ color: inherit;
+ /* 1 */
+ margin: 0;
+ /* 2 */
+ padding: 0;
+ /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+ text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+input:where([type='button']),
+input:where([type='reset']),
+input:where([type='submit']) {
+ -webkit-appearance: button;
+ /* 1 */
+ background-color: transparent;
+ /* 2 */
+ background-image: none;
+ /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+ outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+ box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+ vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+ -webkit-appearance: textfield;
+ /* 1 */
+ outline-offset: -2px;
+ /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ /* 1 */
+ font: inherit;
+ /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+ display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+ margin: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+legend {
+ padding: 0;
+}
+
+ol,
+ul,
+menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+Reset default styling for dialogs.
+*/
+
+dialog {
+ padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+ resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+input::placeholder,
+textarea::placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role="button"] {
+ cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+ cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block;
+ /* 1 */
+ vertical-align: middle;
+ /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+/* Make elements with the HTML hidden attribute stay hidden by default */
+
+[hidden] {
+ display: none;
+}
+
+*, ::before, ::after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
+::backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
+.container {
+ width: 100%;
+}
+
+@media (min-width: 640px) {
+ .container {
+ max-width: 640px;
+ }
+}
+
+@media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+}
+
+@media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+}
+
+.fixed {
+ position: fixed;
+}
+
+.absolute {
+ position: absolute;
+}
+
+.relative {
+ position: relative;
+}
+
+.inset-x-0 {
+ left: 0px;
+ right: 0px;
+}
+
+.top-0 {
+ top: 0px;
+}
+
+.z-50 {
+ z-index: 50;
+}
+
+.-m-1\.5 {
+ margin: -0.375rem;
+}
+
+.mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.mb-10 {
+ margin-bottom: 2.5rem;
+}
+
+.mb-4 {
+ margin-bottom: 1rem;
+}
+
+.mb-8 {
+ margin-bottom: 2rem;
+}
+
+.ml-4 {
+ margin-left: 1rem;
+}
+
+.ml-6 {
+ margin-left: 1.5rem;
+}
+
+.mr-10 {
+ margin-right: 2.5rem;
+}
+
+.mr-2 {
+ margin-right: 0.5rem;
+}
+
+.mt-2 {
+ margin-top: 0.5rem;
+}
+
+.mt-20 {
+ margin-top: 5rem;
+}
+
+.mt-4 {
+ margin-top: 1rem;
+}
+
+.mt-5 {
+ margin-top: 1.25rem;
+}
+
+.mt-10 {
+ margin-top: 2.5rem;
+}
+
+.mb-5 {
+ margin-bottom: 1.25rem;
+}
+
+.mb-3 {
+ margin-bottom: 0.75rem;
+}
+
+.block {
+ display: block;
+}
+
+.flex {
+ display: flex;
+}
+
+.h-10 {
+ height: 2.5rem;
+}
+
+.h-20 {
+ height: 5rem;
+}
+
+.h-5 {
+ height: 1.25rem;
+}
+
+.h-8 {
+ height: 2rem;
+}
+
+.h-screen {
+ height: 100vh;
+}
+
+.max-h-screen {
+ max-height: 100vh;
+}
+
+.max-h-10 {
+ max-height: 2.5rem;
+}
+
+.min-h-screen {
+ min-height: 100vh;
+}
+
+.w-1\/2 {
+ width: 50%;
+}
+
+.w-10 {
+ width: 2.5rem;
+}
+
+.w-20 {
+ width: 5rem;
+}
+
+.w-8 {
+ width: 2rem;
+}
+
+.w-full {
+ width: 100%;
+}
+
+.w-max {
+ width: -moz-max-content;
+ width: max-content;
+}
+
+.w-11\/12 {
+ width: 91.666667%;
+}
+
+.w-1\/12 {
+ width: 8.333333%;
+}
+
+.max-w-md {
+ max-width: 28rem;
+}
+
+.max-w-xs {
+ max-width: 20rem;
+}
+
+.max-w-full {
+ max-width: 100%;
+}
+
+.max-w-52 {
+ max-width: 13rem;
+}
+
+.max-w-screen-xl {
+ max-width: 1280px;
+}
+
+.max-w-screen-lg {
+ max-width: 1024px;
+}
+
+.flex-1 {
+ flex: 1 1 0%;
+}
+
+.flex-col {
+ flex-direction: column;
+}
+
+.items-start {
+ align-items: flex-start;
+}
+
+.items-center {
+ align-items: center;
+}
+
+.justify-end {
+ justify-content: flex-end;
+}
+
+.justify-center {
+ justify-content: center;
+}
+
+.justify-between {
+ justify-content: space-between;
+}
+
+.justify-around {
+ justify-content: space-around;
+}
+
+.space-x-10 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(2.5rem * var(--tw-space-x-reverse));
+ margin-left: calc(2.5rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-x-4 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(1rem * var(--tw-space-x-reverse));
+ margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-x-6 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(1.5rem * var(--tw-space-x-reverse));
+ margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-y-6 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
+}
+
+.overflow-hidden {
+ overflow: hidden;
+}
+
+.overflow-x-auto {
+ overflow-x: auto;
+}
+
+.truncate {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.rounded {
+ border-radius: 0.25rem;
+}
+
+.rounded-full {
+ border-radius: 9999px;
+}
+
+.rounded-lg {
+ border-radius: 0.5rem;
+}
+
+.rounded-md {
+ border-radius: 0.375rem;
+}
+
+.rounded-sm {
+ border-radius: 0.125rem;
+}
+
+.border-0 {
+ border-width: 0px;
+}
+
+.border-l-2 {
+ border-left-width: 2px;
+}
+
+.border-t {
+ border-top-width: 1px;
+}
+
+.border-black {
+ --tw-border-opacity: 1;
+ border-color: rgb(0 0 0 / var(--tw-border-opacity));
+}
+
+.border-gray-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(229 231 235 / var(--tw-border-opacity));
+}
+
+.bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+}
+
+.bg-gray-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
+}
+
+.bg-gray-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity));
+}
+
+.bg-white {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
+}
+
+.p-1\.5 {
+ padding: 0.375rem;
+}
+
+.p-2 {
+ padding: 0.5rem;
+}
+
+.p-4 {
+ padding: 1rem;
+}
+
+.p-6 {
+ padding: 1.5rem;
+}
+
+.px-10 {
+ padding-left: 2.5rem;
+ padding-right: 2.5rem;
+}
+
+.px-5 {
+ padding-left: 1.25rem;
+ padding-right: 1.25rem;
+}
+
+.px-6 {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+}
+
+.py-1\.5 {
+ padding-top: 0.375rem;
+ padding-bottom: 0.375rem;
+}
+
+.py-10 {
+ padding-top: 2.5rem;
+ padding-bottom: 2.5rem;
+}
+
+.py-2 {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.py-2\.5 {
+ padding-top: 0.625rem;
+ padding-bottom: 0.625rem;
+}
+
+.px-4 {
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+.pl-1 {
+ padding-left: 0.25rem;
+}
+
+.pl-4 {
+ padding-left: 1rem;
+}
+
+.pl-6 {
+ padding-left: 1.5rem;
+}
+
+.pr-10 {
+ padding-right: 2.5rem;
+}
+
+.pr-20 {
+ padding-right: 5rem;
+}
+
+.pt-24 {
+ padding-top: 6rem;
+}
+
+.pr-4 {
+ padding-right: 1rem;
+}
+
+.text-center {
+ text-align: center;
+}
+
+.text-3xl {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+}
+
+.text-4xl {
+ font-size: 2.25rem;
+ line-height: 2.5rem;
+}
+
+.text-sm {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.text-xl {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+}
+
+.font-bold {
+ font-weight: 700;
+}
+
+.font-extrabold {
+ font-weight: 800;
+}
+
+.font-medium {
+ font-weight: 500;
+}
+
+.font-semibold {
+ font-weight: 600;
+}
+
+.leading-6 {
+ line-height: 1.5rem;
+}
+
+.tracking-tight {
+ letter-spacing: -0.025em;
+}
+
+.text-black {
+ --tw-text-opacity: 1;
+ color: rgb(0 0 0 / var(--tw-text-opacity));
+}
+
+.text-blue-400 {
+ --tw-text-opacity: 1;
+ color: rgb(96 165 250 / var(--tw-text-opacity));
+}
+
+.text-blue-600 {
+ --tw-text-opacity: 1;
+ color: rgb(37 99 235 / var(--tw-text-opacity));
+}
+
+.text-gray-900 {
+ --tw-text-opacity: 1;
+ color: rgb(17 24 39 / var(--tw-text-opacity));
+}
+
+.text-gray-950 {
+ --tw-text-opacity: 1;
+ color: rgb(3 7 18 / var(--tw-text-opacity));
+}
+
+.text-white {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
+.shadow {
+ --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-md {
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-sm {
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.ring-1 {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.ring-inset {
+ --tw-ring-inset: inset;
+}
+
+.ring-gray-300 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));
+}
+
+.placeholder\:text-gray-400::-moz-placeholder {
+ --tw-text-opacity: 1;
+ color: rgb(156 163 175 / var(--tw-text-opacity));
+}
+
+.placeholder\:text-gray-400::placeholder {
+ --tw-text-opacity: 1;
+ color: rgb(156 163 175 / var(--tw-text-opacity));
+}
+
+.hover\:bg-blue-500:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity));
+}
+
+.hover\:text-white:hover {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
+.hover\:underline:hover {
+ text-decoration-line: underline;
+}
+
+.focus\:ring-2:focus {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.focus\:ring-inset:focus {
+ --tw-ring-inset: inset;
+}
+
+.focus-visible\:outline:focus-visible {
+ outline-style: solid;
+}
+
+.focus-visible\:outline-2:focus-visible {
+ outline-width: 2px;
+}
+
+.focus-visible\:outline-offset-2:focus-visible {
+ outline-offset: 2px;
+}
+
+@media (min-width: 640px) {
+ .sm\:mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ .sm\:w-full {
+ width: 100%;
+ }
+
+ .sm\:max-w-sm {
+ max-width: 24rem;
+ }
+
+ .sm\:text-5xl {
+ font-size: 3rem;
+ line-height: 1;
+ }
+
+ .sm\:text-sm {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ }
+
+ .sm\:leading-6 {
+ line-height: 1.5rem;
+ }
+}
+
+@media (min-width: 1024px) {
+ .lg\:px-8 {
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+}
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/src/register.html" "b/\354\236\204\354\210\230\354\247\204/ex1/src/register.html"
new file mode 100644
index 0000000..661ad62
--- /dev/null
+++ "b/\354\236\204\354\210\230\354\247\204/ex1/src/register.html"
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+ 가입하기
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git "a/\354\236\204\354\210\230\354\247\204/ex1/tailwind.config.js" "b/\354\236\204\354\210\230\354\247\204/ex1/tailwind.config.js"
new file mode 100644
index 0000000..a0cebb6
--- /dev/null
+++ "b/\354\236\204\354\210\230\354\247\204/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\236\204\354\210\230\354\247\204/ex10.test.ts" "b/\354\236\204\354\210\230\354\247\204/ex10.test.ts"
index 6218a79..e58144e 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex10.test.ts"
+++ "b/\354\236\204\354\210\230\354\247\204/ex10.test.ts"
@@ -1,4 +1,54 @@
import { ArrayList } from './ex10';
console.log('🚀 ArrayList:', ArrayList);
+import * as assert from 'assert';
-// 여기에 테스트코드를 작성하세요.
+// 여기에 테스트코드
+function testArrayList() {
+ const alist = new ArrayList([1, 2]);
+
+ assert.deepStrictEqual(ArrayList.listToArray({ value: 1, rest: { value: 2, rest: null } }), [1, 2]);
+ assert.deepStrictEqual(ArrayList.arrayToList([1, 2]), { value: 1, rest: { value: 2, rest: null } });
+
+ alist.add(3);
+ assert.deepStrictEqual(alist.toString(), '{ value: 1, rest: { value: 2, rest: { value: 3, rest: null } } }');
+
+ alist.add(5, 1);
+ assert.deepStrictEqual(alist.toString(), '{ value: 1, rest: { value: 5, rest: { value: 2, rest: { value: 3, rest: null } } } }');
+
+ alist.remove(2);
+ assert.deepStrictEqual(alist.toString(), '{ value: 1, rest: { value: 5, rest: { value: 3, rest: null } } }');
+
+ alist.add(22, 1);
+ assert.deepStrictEqual(alist.toString(), '{ value: 1, rest: { value: 22, rest: { value: 5, rest: { value: 3, rest: null } } } }');
+
+ alist.add(33, 1);
+ assert.deepStrictEqual(alist.toArray(), [1, 33, 22, 5, 3]);
+
+ assert.deepStrictEqual(alist.toString(), '{ value: 1, rest: { value: 33, rest: { value: 22, rest: { value: 5, rest: { value: 3, rest: null } } } } }');
+
+ alist.set(1, 300);
+ alist.get(2);
+
+ assert.strictEqual(alist.get(2), 22);
+ assert.strictEqual(alist.size(), 5);
+ assert.strictEqual(alist.indexOf(300), 1);
+ assert.strictEqual(alist.contains(300), true);
+ assert.strictEqual(alist.contains(301), false);
+ assert.strictEqual(alist.isEmpty(), false);
+ assert.strictEqual(alist.peek(), 1);
+
+ assert.deepStrictEqual(alist.toArray(), [1, 300, 22, 5, 3]);
+
+ const iterator = alist[Symbol.iterator]();
+ assert.deepStrictEqual(iterator.next(), { value: 1, done: false });
+ assert.deepStrictEqual(iterator.next(), { value: 300, done: false });
+ assert.deepStrictEqual(iterator.next(), { value: 22, done: false });
+ assert.deepStrictEqual(iterator.next(), { value: 5, done: false });
+ assert.deepStrictEqual(iterator.next(), { value: 3, done: false });
+ assert.deepStrictEqual(iterator.next(), { value: undefined, done: true });
+
+ alist.clear();
+ assert.deepStrictEqual(alist.size(), 0);
+}
+
+testArrayList();
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex10.ts" "b/\354\236\204\354\210\230\354\247\204/ex10.ts"
index 1ffaef5..4002cde 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex10.ts"
+++ "b/\354\236\204\354\210\230\354\247\204/ex10.ts"
@@ -1,71 +1,224 @@
-class Collection {
- private readonly arr = Array();
-
- constructor(...args: T[]) {
- this.arr.push(...args);
+interface ListNode {
+ value: T;
+ rest: ListNode | null;
}
-
- get _arr() {
- return this.arr;
- }
-
- push(...args: T[]) {
- this.arr.push(...args);
- return this.arr;
- }
-
- get peek(): T | undefined {
- return this.isQueue() ? this.arr[0] : this.arr.at(-1);
- }
-
- get poll(): T | undefined {
- return this.isQueue() ? this.arr.shift() : this.arr.pop();
- }
-
- remove() {
- return this.poll;
- }
-
- 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];
+
+ class Collection {
+ protected items: T[] = [];
+
+ push(...args: T[]): T[] {
+ this.items.push(...args);
+ return this.items;
+ }
+
+ get _arr(): T[] {
+ return this.items;
}
}
-
- toArray() {
- return this.isQueue() ? this.arr.toReversed() : this.arr;
- }
-
- print() {
- console.log(`<${this.constructor.name}: [${this.toArray()}]>`);
- }
-
- private isQueue() {
- return this instanceof Queue;
+
+ class ArrayList extends Collection {
+ private node: ListNode | null = null;
+
+ constructor(arr: T[] = []) {
+ super();
+ this.push(...arr);
+ this.node = this.arrayToLinkedNode(this._arr);
+ }
+
+ public static arrayToList(array: T[]): ListNode | null {
+ if (array.length === 0) return null;
+ let result: ListNode = { value: array[0], rest: null };
+ let current = result;
+ for (let i = 1; i < array.length; i++) {
+ current.rest = { value: array[i], rest: null };
+ current = current.rest;
+ }
+ return result;
+ }
+
+ public static listToArray(list: ListNode | null): T[] {
+ const result: T[] = [];
+ let current = list;
+ while (current) {
+ result.push(current.value);
+ current = current.rest || null;
+ }
+ return result;
+ }
+
+ arrayToLinkedNode(arr: T[]): ListNode | null {
+ if (arr.length === 0) return null;
+ let node: ListNode = { value: arr[0], rest: null };
+ let current = node;
+ for (let i = 1; i < arr.length; i++) {
+ current.rest = { value: arr[i], rest: null };
+ current = current.rest;
+ }
+ return node;
+ }
+
+ add(item: T, index?: number): ListNode {
+ if (index === undefined) {
+ let lst = this.node;
+ if (!lst) {
+ this.node = { value: item, rest: null };
+ return this.node;
+ }
+ while (lst.rest !== null) {
+ lst = lst.rest as ListNode;
+ }
+ lst.rest = { value: item, rest: null };
+ } else {
+ if (index < 0) throw new Error('Index must be greater than or equal to 0');
+ if (index === 0) {
+ this.node = { value: item, rest: this.node };
+ return this.node;
+ }
+
+ let current = this.node;
+ let depth = 0;
+ while (current?.rest !== null && depth < index - 1) {
+ if (current) current = current.rest;
+
+ depth++;
+ }
+
+ if (!current) throw new Error('Index is out of range');
+ current.rest = { value: item, rest: current.rest };
+ }
+ return this.node as ListNode;
+ }
+
+ size(): number {
+ let current = this.node;
+ let count = 0;
+ while (current !== null) {
+ count++;
+ current = current.rest;
+ }
+ return count;
+ }
+
+ remove(item: T): ListNode | null {
+ if (!this.node) return null;
+ if (this.node.value === item) {
+ this.node = this.node.rest || null;
+ return this.node;
+ }
+
+ let current = this.node;
+ while (current.rest !== null) {
+ if (current.rest.value === item) {
+ current.rest = current.rest.rest;
+ return this.node;
+ }
+ current = current.rest;
+ }
+ return this.node;
+ }
+
+ removeByIndex(index: number): ListNode | null {
+ if (!this.node) return null;
+ if (index === 0) {
+ this.node = this.node.rest || null;
+ return this.node;
+ }
+
+ let current = this.node;
+ let depth = 0;
+ while (current?.rest !== null && depth < index - 1) {
+ current = current.rest;
+ depth++;
+ }
+
+ if (!current?.rest) throw new Error('Index is out of range');
+ current.rest = current.rest.rest;
+ return this.node;
+ }
+
+ set(index: number, item: T): ListNode | null {
+ if (!this.node) throw new Error('Array is empty');
+ let current = this.node;
+ let depth = 0;
+ while (depth < index) {
+ if (!current?.rest) throw new Error('Index is out of range');
+ current = current.rest;
+ depth++;
+ }
+ current.value = item;
+ return this.node;
+ }
+
+ get(index: number): T | undefined {
+ if (!this.node) return undefined;
+ let current = this.node;
+ let depth = 0;
+ while (depth < index) {
+ if (!current?.rest) return undefined;
+ current = current.rest;
+ depth++;
+ }
+ return current?.value;
+ }
+
+ toArray(): T[] {
+ return ArrayList.listToArray(this.node);
+ }
+
+ toString(): string {
+ const listToString = (node: ListNode | null): string => {
+ if (!node) return 'null';
+ return `{ value: ${node.value}, rest: ${listToString(node.rest || null)} }`;
+ };
+ return listToString(this.node);
+ }
+
+ clear(): void {
+ this.node = null;
+ }
+
+ isEmpty(): boolean {
+ return this.node === null;
+ }
+
+ contains(item: T): boolean {
+ let current = this.node;
+ while (current) {
+ if (current.value === item) return true;
+ current = current.rest;
+ }
+ return false;
+ }
+
+ indexOf(item: T): number | null {
+ let current = this.node;
+ let index = 0;
+ while (current) {
+ if (current.value === item) return index;
+ current = current.rest;
+ index++;
+ }
+ return -1;
+ }
+
+ peek(): T | null {
+ return this.node?.value || null;
+ }
+
+ [Symbol.iterator](): Iterator {
+ let current = this.node;
+ return {
+ next(): IteratorResult {
+ if (!current) {
+ return { value: undefined, done: true };
+ }
+ let value = current.value;
+ current = current.rest || null;
+ return { value, done: false };
+ },
+ };
+ }
}
-}
-
-class Stack extends Collection {}
-class Queue extends Collection {}
-
-// ArrayList 클래스를 작성하세요.
-class ArrayList extends Collection {}
-
-export { Stack, Queue, ArrayList };
+
+ export { ArrayList, Collection };
+
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex2.js" "b/\354\236\204\354\210\230\354\247\204/ex2.js"
index 6b95f04..7ca42f5 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex2.js"
+++ "b/\354\236\204\354\210\230\354\247\204/ex2.js"
@@ -1,4 +1,39 @@
-// range 함수를 작성하세요.
-const range = (start, end, step = start > end ? -1 : 1) => { };
+const range = (start, end, step = start > end ? -1 : 1) => {
-module.exports = { range };
+ const result = [];
+ const EPSILON = 1e-10; // 부동 소수점 오차를 보정하기 위한 아주 작은 값
+ const DECIMALS = 10; // 소수점 자리수
+
+ if (step === 0 || start === end) return [start];
+
+ // end가 없을 경우
+ if (end === undefined) {
+ if (start > 0) {
+ end = start;
+ start = 1;
+ } else if (start < 0) {
+ end = -1;
+ } else {
+ // start가 0일 경우
+ result.push(start);
+ }
+ }
+
+ // 예외
+ if ((start - end) * step > 0) return [];
+
+ if (step > 0) {
+ for (let i = start; i <= end + EPSILON; i += step) {
+ if (i > end + EPSILON) break; // 범위를 초과할 경우 종료
+ result.push(parseFloat(i.toFixed(DECIMALS)));
+ }
+ } else {
+ for (let i = start; i >= end - EPSILON; i += step) {
+ if (i < end - EPSILON) break; // 범위를 초과할 경우 종료
+ result.push(parseFloat(i.toFixed(DECIMALS)));
+ }
+ }
+ return result;
+};
+
+module.exports = { range };
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex3.js" "b/\354\236\204\354\210\230\354\247\204/ex3.js"
index b1b0d75..76c87f3 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex3.js"
+++ "b/\354\236\204\354\210\230\354\247\204/ex3.js"
@@ -1,3 +1,17 @@
Array.prototype.sortBy = function (sortProp = '') {
- return this;
-};
+ const sortArray = sortProp.split(',');
+
+ // 각 기준을 [sortKey, order] 형태로 변환
+ const sortPairs = sortArray.map(value => {
+ const [sortKey, order] = value.split(':');
+ return [sortKey, order || 'asc']; // order가 비어있을 경우 기본값 'asc'로 설정
+ });
+
+ return this.slice().sort((a, b) => {
+ for (const [sortKey, order] of sortPairs) {
+ if (a[sortKey] > b[sortKey]) return order === 'desc' ? -1 : 1;
+ if (a[sortKey] < b[sortKey]) return order === 'desc' ? 1 : -1;
+ }
+ return 0;
+ });
+};
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex3.test.js" "b/\354\236\204\354\210\230\354\247\204/ex3.test.js"
index 6c27a4d..bc37804 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex3.test.js"
+++ "b/\354\236\204\354\210\230\354\247\204/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\236\204\354\210\230\354\247\204/ex4.js" "b/\354\236\204\354\210\230\354\247\204/ex4.js"
index 9ede02f..8bc2d29 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex4.js"
+++ "b/\354\236\204\354\210\230\354\247\204/ex4.js"
@@ -1,3 +1,69 @@
-function deepCopy(obj) {}
+function deepCopy(obj, references = new WeakMap()) {
-module.exports = { deepCopy };
+ // 기본형이거나 함수일 경우, 그대로 반환
+ if (typeof obj !== 'object' || obj === null) {
+ return obj;
+ }
+
+ // 이미 참조한 객체가 있을 경우, 복사된 객체를 반환
+ if (references.has(obj)) {
+ return references.get(obj);
+ }
+
+ // 배열일 경우
+ if (Array.isArray(obj)) {
+ const copy = [];
+ references.set(obj, copy); // 원본 객체 저장
+
+ for (let i = 0; i < obj.length; i++) {
+ copy[i] = deepCopy(obj[i], references);
+ }
+ return copy;
+ }
+
+ // Set일 경우
+ if (obj instanceof Set) {
+ const copy = new Set();
+ references.set(obj, copy); // 원본 객체랑 복사본을 WeakSet에 저장
+ for (let item of obj) {
+ copy.add(deepCopy(item, references)); // 각 값을 재귀적으로 복사
+ }
+ return copy;
+ }
+
+ // Map일 경우
+ if (obj instanceof Map) {
+ const copyMap = new Map();
+ references.set(obj, copyMap); // 원본 객체랑 복사본을 WeakMap에 저장
+ for (let [key, value] of obj) {
+ copyMap.set(deepCopy(key, references), deepCopy(value, references)); // 키-값을 재귀적으로 복사
+ }
+ return copyMap;
+ }
+
+ // WeakSet과 WeakMap일 경우, 그대로 반환 (참조형)
+ if (obj instanceof WeakSet || obj instanceof WeakMap) {
+ return obj;
+ }
+
+ // 일반 객체일 경우
+ const result = {};
+ references.set(obj, result); // 원본 객체를 WeakMap에 저장
+
+ // 1) 객체의 심볼 속성 복사
+ const symbols = Object.getOwnPropertySymbols(obj);
+ for (let i = 0; i < symbols.length; i++) {
+ const symbol = symbols[i];
+ result[symbol] = deepCopy(obj[symbol], references);
+ }
+
+ // 2) 객체의 일반 속성 복사
+ for (const key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ result[key] = deepCopy(obj[key], references);
+ }
+ }
+ return result;
+}
+
+module.exports = { deepCopy };
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex5.js" "b/\354\236\204\354\210\230\354\247\204/ex5.js"
index 464a05a..0158319 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex5.js"
+++ "b/\354\236\204\354\210\230\354\247\204/ex5.js"
@@ -1,3 +1,54 @@
module.exports = {
- searchByKoreanInitialSound: (data, firstSounds) => {},
-};
+ searchByKoreanInitialSound(data, firstSounds) {
+ // 한글 유니코드 시작값과 끝값
+ const HANGUL_START_CHARCODE = '가'.charCodeAt(0);
+ const HANGUL_END_CHARCODE = '힣'.charCodeAt(0);
+
+ // 초성 + 중성 + 종성 조합의 합
+ const TOTAL_SYLLABLE_COMBINATIONS = 588;
+
+ const initialConsonants = [
+ 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ',
+ 'ㄹ', 'ㅁ', 'ㅂ','ㅃ', 'ㅅ',
+ 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ',
+ 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
+ ]
+
+ // 초성 가져오기
+ const getInitialSound = (char) => {
+ const code = char.charCodeAt(0);
+ if (code >= HANGUL_START_CHARCODE && code <= HANGUL_END_CHARCODE) {
+ // 한글 음절의 초성 반환
+ const index = Math.floor((code - HANGUL_START_CHARCODE) / TOTAL_SYLLABLE_COMBINATIONS);
+ return initialConsonants[index] || '';
+ } else {
+ // 한글이 아닐 경우, 그대로 반환
+ return char;
+ }
+ };
+
+ // 초성 정규식 패턴 생성 함수
+ const getInitialSoundRegex = (firstSounds) => {
+ const pattern = firstSounds.split('').map(char => {
+ if (initialConsonants.includes(char)) {
+ // 초성 문자는 [char]로 패턴 생성
+ return `[${char}]`;
+ } else {
+ // 숫자나 기타 문자는 그대로 사용
+ return char;
+ }
+ }).join('');
+ return new RegExp(pattern, 'u'); // 'u' 플래그를 사용하여 유니코드 문자 인식
+ };
+
+ // 초성 정규식 패턴을 생성
+ const regex = getInitialSoundRegex(firstSounds);
+
+ // 데이터 필터링
+ return data.filter(element => {
+ // 문자열에서 초성 추출
+ const extractedInitialSounds = Array.from(element).map(getInitialSound).join('');
+ return regex.test(extractedInitialSounds);
+ });
+ }
+};
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex6.test.ts" "b/\354\236\204\354\210\230\354\247\204/ex6.test.ts"
index 680c5e6..9a96794 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex6.test.ts"
+++ "b/\354\236\204\354\210\230\354\247\204/ex6.test.ts"
@@ -21,4 +21,4 @@ import { promiseAllSettled, randTime } from './ex6';
randTime(33),
])
);
-})();
+})();
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex6.ts" "b/\354\236\204\354\210\230\354\247\204/ex6.ts"
index 424ca54..4ef8dd8 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex6.ts"
+++ "b/\354\236\204\354\210\230\354\247\204/ex6.ts"
@@ -1,4 +1,17 @@
export const randTime = (val: T): Promise =>
- new Promise(resolve => setTimeout(resolve, Math.random() * 1000, val));
+ new Promise(resolve => setTimeout(resolve, Math.random() * 1000, val));
-export function promiseAllSettled(promises: Promise[]) {}
+export function promiseAllSettled(promises: Promise[]): Promise> {
+ // 모든 프로미스의 결과를 담을 배열
+ const results: Array<{ status: 'fulfilled' | 'rejected'; value?: T; reason?: any }> = [];
+
+ // 모든 프로미스의 상태를 추적
+ const wrappedPromises = promises.map(promise =>
+ promise
+ .then(value => ({ status: 'fulfilled', value } as { status: 'fulfilled'; value: T }))
+ .catch(reason => ({ status: 'rejected', reason } as { status: 'rejected'; reason: any }))
+ );
+
+ // 모든 프로미스가 처리될 때까지 기다리고 결과를 반환
+ return Promise.all(wrappedPromises);
+}
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex7.test.ts" "b/\354\236\204\354\210\230\354\247\204/ex7.test.ts"
index 62b881d..c445b6a 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex7.test.ts"
+++ "b/\354\236\204\354\210\230\354\247\204/ex7.test.ts"
@@ -64,7 +64,56 @@ async function test(userId: string | number) {
],
});
- // 추가 테스트 코드를 작성하시오.
+ // 추가 테스트 코드
+ assert.strictEqual(posts?.at(0)?.title, 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit');
+ assert.strictEqual(posts?.at(2)?.comments[2].email,'Kariane@jadyn.tv');
+ assert.deepStrictEqual(posts[3].comments, [
+ {
+ postId: 4,
+ id: 16,
+ email: 'Christine@ayana.info',
+ body: 'iste ut laborum aliquid velit facere itaque\n' +
+ 'quo ut soluta dicta voluptate\n' +
+ 'error tempore aut et\n' +
+ 'sequi reiciendis dignissimos expedita consequuntur libero sed fugiat facilis'
+ },
+ {
+ postId: 4,
+ id: 17,
+ email: 'Preston_Hudson@blaise.tv',
+ body: 'consequatur necessitatibus totam sed sit dolorum\n' +
+ 'recusandae quae odio excepturi voluptatum harum voluptas\n' +
+ 'quisquam sit ad eveniet delectus\n' +
+ 'doloribus odio qui non labore'
+ },
+ {
+ postId: 4,
+ id: 18,
+ email: 'Vincenza_Klocko@albertha.name',
+ body: 'veritatis voluptates necessitatibus maiores corrupti\n' +
+ 'neque et exercitationem amet sit et\n' +
+ 'ullam velit sit magnam laborum\n' +
+ 'magni ut molestias'
+ },
+ {
+ postId: 4,
+ id: 19,
+ email: 'Madelynn.Gorczany@darion.biz',
+ body: 'doloribus est illo sed minima aperiam\n' +
+ 'ut dignissimos accusantium tempore atque et aut molestiae\n' +
+ 'magni ut accusamus voluptatem quos ut voluptates\n' +
+ 'quisquam porro sed architecto ut'
+ },
+ {
+ postId: 4,
+ id: 20,
+ email: 'Mariana_Orn@preston.org',
+ body: 'qui harum consequatur fugiat\n' +
+ 'et eligendi perferendis at molestiae commodi ducimus\n' +
+ 'doloremque asperiores numquam qui\n' +
+ 'ut sit dignissimos reprehenderit tempore'
+ }
+ ]);
}
-test(1);
+test(1);
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex7.ts" "b/\354\236\204\354\210\230\354\247\204/ex7.ts"
index 62812ac..c579ebb 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex7.ts"
+++ "b/\354\236\204\354\210\230\354\247\204/ex7.ts"
@@ -1,3 +1,63 @@
const POST_URL = 'https://jsonplaceholder.typicode.com/posts';
-export async function getPosts(userId: number | string) {}
+interface Post {
+ userId: number;
+ id: number;
+ title: String;
+ body: String;
+}
+
+interface Comment {
+ postId: number;
+ id: number;
+ email: String;
+ body: String;
+}
+
+interface CommentResponse extends Comment {
+ name: String;
+}
+
+interface PostComment {
+ postId: number;
+ title: String;
+ comments: Comment[];
+}
+
+export async function getPosts(userId: number | string): Promise {
+ try {
+ // userId로 게시글 조회
+ const postResponse = await fetch(`${POST_URL}?userId=${userId}`);
+ const posts: Post[] = (await postResponse.json()) as Post[];
+
+ // 결과 반환할 객체 배열
+ const postComments: PostComment[] = await Promise.all(
+ posts.map(async (post: Post) => {
+ const commentResponse = await fetch(`${POST_URL}/${post.id}/comments`);
+ const comments: CommentResponse[] =
+ (await commentResponse.json()) as CommentResponse[];
+
+ // 결과 반환할 객체
+ let postComment: PostComment = {
+ postId: post.id,
+ title: post.title,
+ comments: [],
+ };
+
+ comments.forEach((comment: Comment) => {
+ postComment.comments.push({
+ postId: comment.postId,
+ id: comment.id,
+ email: comment.email,
+ body: comment.body,
+ });
+ });
+ return postComment;
+ })
+ );
+ return postComments;
+ } catch (error) {
+ console.error('에러 나요ㅠ.ㅠ:', error);
+ return [];
+ }
+}
\ No newline at end of file
diff --git "a/\354\236\204\354\210\230\354\247\204/ex8.ts" "b/\354\236\204\354\210\230\354\247\204/ex8.ts"
index a67a2d2..08397e8 100644
--- "a/\354\236\204\354\210\230\354\247\204/ex8.ts"
+++ "b/\354\236\204\354\210\230\354\247\204/ex8.ts"
@@ -1,8 +1,25 @@
-// dummy(mock)입니다. 올바르게 수정하세요.
-const debounce = (cb: any, delay: number) => (i: number) => {};
-const throttle = (cb: any, delay: number) => (i: number) => {};
+// debounce 함수
+const debounce = (cb: (i: number) => void, delay: number) => {
+ let timeoutId: NodeJS.Timeout | null = null;
+ return (i: number) => {
+ if (timeoutId) clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => cb(i), delay);
+ };
+};
-// function throttle...
+// throttle 함수
+const throttle = (cb: (i: number) => void, delay: number) => {
+ let isThrottled = false;
+ return (i: number) => {
+ if (!isThrottled) {
+ cb(i);
+ isThrottled = true;
+ setTimeout(() => {
+ isThrottled = false;
+ }, delay);
+ }
+ };
+};
const debo = debounce((a: number) => console.log(a + 1), 500);
for (let i = 10; i < 15; i++) debo(i); // 15 출력