From 29125ac38e4b0077bfc456ba1e1ffb660e9dad29 Mon Sep 17 00:00:00 2001 From: Hyeok Lee Date: Sat, 18 Oct 2025 17:11:28 +0900 Subject: [PATCH 1/9] =?UTF-8?q?docs:=201=EC=A3=BC=EC=B0=A8=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EA=B8=B0=EB=8A=A5=20=EB=AA=85=EC=84=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 13420b29..43fe18c0 100644 --- a/README.md +++ b/README.md @@ -1 +1,31 @@ -# javascript-calculator-precourse \ No newline at end of file +# 1주차 미션: 문자열 덧셈 계산기 + +### 기능 요구 사항 + +--- + +입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다. + +- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다. + + - 예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6 + +- 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다. + + - 예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다. + +- 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 `Error`를 발생시킨 후 애플리케이션은 종료되어야 한다. + +--- + +### 기능 구현 목록 + +--- + +- [ ] 입력 후 정규 표현식을 통해 커스텀 구분자를 확인하는 로직 +- [ ] 구분자와 숫자를 구분하여 합산 구하는 로직 + +- [ ] 입력 전후 요구 사항에 맞게 결과 출력하는 로직 +- [ ] 에러 발생 시 에러 메시지 출력하는 로직 + +--- From 236c201db257a44475542fdcab1442e3f84cb629 Mon Sep 17 00:00:00 2001 From: Hyeok Lee Date: Sat, 18 Oct 2025 17:45:33 +0900 Subject: [PATCH 2/9] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/ApplicationTest.js | 90 +++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index 7c6962dd..726eebf5 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -17,6 +17,7 @@ const getLogSpy = () => { }; describe("문자열 계산기", () => { + // 정답 케이스 test("커스텀 구분자 사용", async () => { const inputs = ["//;\\n1"]; mockQuestions(inputs); @@ -32,7 +33,68 @@ describe("문자열 계산기", () => { }); }); - test("예외 테스트", async () => { + test("다중 커스텀 구분자", async () => { + const inputs = ["//@#\\n1@2#3"]; + mockQuestions(inputs); + + const logSpy = getLogSpy(); + const outputs = ["결과 : 6"]; + + const app = new App(); + await app.run(); + + outputs.forEach((output) => { + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output)); + }); + }); + + test("16진수 덧셈", async () => { + const inputs = ["0x01,2,3"]; + mockQuestions(inputs); + + const logSpy = getLogSpy(); + const outputs = ["결과 : 6"]; + + const app = new App(); + await app.run(); + + outputs.forEach((output) => { + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output)); + }); + }); + + test("커스텀 구분자 x와 16진수", async () => { + const inputs = ["//x\\n0x01,2,3"]; + mockQuestions(inputs); + + const logSpy = getLogSpy(); + const outputs = ["결과 : 6"]; + + const app = new App(); + await app.run(); + + outputs.forEach((output) => { + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output)); + }); + }); + + test("마이너스 커스텀 구분자", async () => { + const inputs = ["//-\\n1,-2,3"]; + mockQuestions(inputs); + + const logSpy = getLogSpy(); + const outputs = ["결과 : 6"]; + + const app = new App(); + await app.run(); + + outputs.forEach((output) => { + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output)); + }); + }); + + // 예외 테스트 + test("음수 입력", async () => { const inputs = ["-1,2,3"]; mockQuestions(inputs); @@ -40,4 +102,30 @@ describe("문자열 계산기", () => { await expect(app.run()).rejects.toThrow("[ERROR]"); }); + + test("구분자 마무리", async () => { + const inputs = ["1,2,3,"]; + mockQuestions(inputs); + + const app = new App(); + + await expect(app.run()).rejects.toThrow("[ERROR]"); + }); + + test("숫자 커스텀 구분자", async () => { + const inputs = ["//1\\n1,2,3"]; + mockQuestions(inputs); + + const app = new App(); + + await expect(app.run()).rejects.toThrow("[ERROR]"); + }); + test("커스텀 구분자 선언 문제", async () => { + const inputs = ["//#\n1#2#3"]; + mockQuestions(inputs); + + const app = new App(); + + await expect(app.run()).rejects.toThrow("[ERROR]"); + }); }); From fbd1eebc3c9932f50ce8e343e8833957451deec3 Mon Sep 17 00:00:00 2001 From: Hyeok Lee Date: Mon, 20 Oct 2025 23:27:14 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EA=B8=B0=EB=B3=B8=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/ApplicationTest.js | 6 +++--- src/App.js | 17 ++++++++++++++- src/function.js | 42 ++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/function.js diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index 726eebf5..d450523c 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -63,7 +63,7 @@ describe("문자열 계산기", () => { }); }); - test("커스텀 구분자 x와 16진수", async () => { + test("커스텀 구분자 x와 16진수", async () => { const inputs = ["//x\\n0x01,2,3"]; mockQuestions(inputs); @@ -75,7 +75,7 @@ describe("문자열 계산기", () => { outputs.forEach((output) => { expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output)); - }); + });ㅇ }); test("마이너스 커스텀 구분자", async () => { @@ -90,7 +90,7 @@ describe("문자열 계산기", () => { outputs.forEach((output) => { expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output)); - }); + }); }); // 예외 테스트 diff --git a/src/App.js b/src/App.js index 091aa0a5..3ff759ac 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,20 @@ +import { Console } from "@woowacourse/mission-utils"; +import { + getCustomSeparator, + getExpression, + executeExpression, +} from "./function.js"; + class App { - async run() {} + async run() { + const input = await Console.readLineAsync("덧셈할 문자열을 입력해 주세요."); + + const separators = getCustomSeparator(input); + const expressions = getExpression(input); + const result = executeExpression(expressions, separators); + + Console.print(`결과 : ${result}`); + } } export default App; diff --git a/src/function.js b/src/function.js new file mode 100644 index 00000000..ae98f746 --- /dev/null +++ b/src/function.js @@ -0,0 +1,42 @@ +const pattern = /\/\/(.*)\\n/; + +function hasCustomSeparator(input) { + // 커스텀 구분자를 선언하는 데 이용되는 패턴 선언 + return pattern.test(input); +} + +export function getCustomSeparator(input) { + let separators = [":", ","]; + if (hasCustomSeparator(input)) { + const customSeparators = input.match(pattern)[1]; + + for (const separator of customSeparators) { + separators.push(separator); + } + } + return separators; +} + +export function getExpression(input) { + if (hasCustomSeparator(input)) { + return input.replace(pattern, ""); + } else { + return input; + } +} + +export function executeExpression(expressions, separators) { + let stack = ""; + let result = 0; + console.log(expressions); + console.log(separators); + for (const char of expressions) { + if (separators.includes(char)) { + result += Number(stack); + stack = ""; + } else stack += char; + } + result += Number(stack); + + return result; +} From eb3c9cf4e5c9c1f9a11d510e4768fd2ffe3616ab Mon Sep 17 00:00:00 2001 From: Hyeok Lee Date: Mon, 20 Oct 2025 23:39:24 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/ApplicationTest.js | 6 +++--- src/function.js | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index d450523c..726eebf5 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -63,7 +63,7 @@ describe("문자열 계산기", () => { }); }); - test("커스텀 구분자 x와 16진수", async () => { + test("커스텀 구분자 x와 16진수", async () => { const inputs = ["//x\\n0x01,2,3"]; mockQuestions(inputs); @@ -75,7 +75,7 @@ describe("문자열 계산기", () => { outputs.forEach((output) => { expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output)); - });ㅇ + }); }); test("마이너스 커스텀 구분자", async () => { @@ -90,7 +90,7 @@ describe("문자열 계산기", () => { outputs.forEach((output) => { expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output)); - }); + }); }); // 예외 테스트 diff --git a/src/function.js b/src/function.js index ae98f746..44c743a4 100644 --- a/src/function.js +++ b/src/function.js @@ -28,11 +28,12 @@ export function getExpression(input) { export function executeExpression(expressions, separators) { let stack = ""; let result = 0; - console.log(expressions); - console.log(separators); + for (const char of expressions) { if (separators.includes(char)) { - result += Number(stack); + number = Number(stack); + isLegalNumber(number); + result += number; stack = ""; } else stack += char; } @@ -40,3 +41,9 @@ export function executeExpression(expressions, separators) { return result; } + +function isLegalNumber(number) { + if (number < 0) throw new Error("[ERROR] 양수를 입력해주세요."); + if (Number.isNaN(number)) + throw new Error("[ERROR] 올바른 숫자를 입력해주세요."); +} From 2c304899baae9e3bba7cfa306542ca63ed80e97a Mon Sep 17 00:00:00 2001 From: Hyeok Lee Date: Mon, 20 Oct 2025 23:41:17 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80(=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=EC=9E=90=20=EC=84=A0=EC=96=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/function.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/function.js b/src/function.js index 44c743a4..7b94c9cf 100644 --- a/src/function.js +++ b/src/function.js @@ -9,7 +9,7 @@ export function getCustomSeparator(input) { let separators = [":", ","]; if (hasCustomSeparator(input)) { const customSeparators = input.match(pattern)[1]; - + isLegelSeparator(customSeparators); for (const separator of customSeparators) { separators.push(separator); } @@ -47,3 +47,8 @@ function isLegalNumber(number) { if (Number.isNaN(number)) throw new Error("[ERROR] 올바른 숫자를 입력해주세요."); } + +function isLegelSeparator(separators) { + if (separators.length < 3) + throw new Error("[ERROR] 구분자를 올바르게 입력해주세요."); +} From b1b95a53da61eeb994b794b09b42949aad26d454 Mon Sep 17 00:00:00 2001 From: Hyeok Lee Date: Mon, 20 Oct 2025 23:41:47 +0900 Subject: [PATCH 6/9] =?UTF-8?q?fix:=20=EC=97=90=EB=9F=AC=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=88=98=EC=A0=95(=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=EC=9E=90=20=EC=84=A0=EC=96=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/function.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/function.js b/src/function.js index 7b94c9cf..a708fd48 100644 --- a/src/function.js +++ b/src/function.js @@ -49,6 +49,6 @@ function isLegalNumber(number) { } function isLegelSeparator(separators) { - if (separators.length < 3) + if (separators.length < 1) throw new Error("[ERROR] 구분자를 올바르게 입력해주세요."); } From ed149af6ce0fa4dd7b0e663e5abd93cb677a7ad3 Mon Sep 17 00:00:00 2001 From: Hyeok Lee Date: Mon, 20 Oct 2025 23:43:15 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80(=EC=88=AB=EC=9E=90=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/function.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/function.js b/src/function.js index a708fd48..c914b1ce 100644 --- a/src/function.js +++ b/src/function.js @@ -51,4 +51,6 @@ function isLegalNumber(number) { function isLegelSeparator(separators) { if (separators.length < 1) throw new Error("[ERROR] 구분자를 올바르게 입력해주세요."); + if (separators.test(/.*\d.*/)) + throw new Error("[ERROR] 숫자를 구분자로 이용할 수 없어요."); } From 3337c48d911096e0612b1384bc25e889f16414c9 Mon Sep 17 00:00:00 2001 From: Hyeok Lee Date: Mon, 20 Oct 2025 23:44:04 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20=EC=97=90=EB=9F=AC=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=88=98=EC=A0=95(=EC=88=AB=EC=9E=90=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=EC=9E=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/function.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/function.js b/src/function.js index c914b1ce..756ee0cd 100644 --- a/src/function.js +++ b/src/function.js @@ -51,6 +51,6 @@ function isLegalNumber(number) { function isLegelSeparator(separators) { if (separators.length < 1) throw new Error("[ERROR] 구분자를 올바르게 입력해주세요."); - if (separators.test(/.*\d.*/)) + if (/.*\d.*/.test(separators)) throw new Error("[ERROR] 숫자를 구분자로 이용할 수 없어요."); } From 21b6f02511b0c6748935ab26f65bacf11132d1d1 Mon Sep 17 00:00:00 2001 From: Hyeok Lee Date: Mon, 20 Oct 2025 23:45:39 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80(=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=EC=9E=90=20=EB=A7=88=EB=AC=B4=EB=A6=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/function.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/function.js b/src/function.js index 756ee0cd..229fd712 100644 --- a/src/function.js +++ b/src/function.js @@ -37,6 +37,11 @@ export function executeExpression(expressions, separators) { stack = ""; } else stack += char; } + + if (stack === "") throw new Error("[ERROR] 구분자로 마무리될 수 없어요."); + + number = Number(stack); + isLegalNumber(number); result += Number(stack); return result;