diff --git a/README.md b/README.md index 13420b29..13fcee10 100644 --- a/README.md +++ b/README.md @@ -1 +1,23 @@ -# javascript-calculator-precourse \ No newline at end of file +# javascript-calculator-precourse + +## 기능 요구 사항 + +입력한 문자열에서 구분자를 기준으로 숫자를 추출하여 덧셈을 수행합니다. + +1. 문자열 입력 받기 +2. 입력받은 문자열에서 구분자와 숫자 문자열 추출 +3. 숫자 문자열을 구분자를 기준으로 분리한 후 값 검증 +4. 덧셈 수행 후 결과 출력 + +**구분자** +구분자를 제외하고 숫자가 입력되지 않은 경우는 0으로 처리 + +- **쉼표(,)**, **콜론( : )** +- 커스텀 구분자 : **문자열 앞 //와 \n 사이**에 위치하는 문자 + +### 에러처리 + +잘못된 값이 입력된 경우 "[Error]"로 시작하는 메세지와 함꼐 `Error`를 발생시킵니다. + +1. 구분자를 제외하고 숫자가 아닌 값이 입력된 경우 +2. 양수가 아닌 숫자가 입력된 경우 diff --git a/src/App.js b/src/App.js index 091aa0a5..68eb55ef 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,28 @@ +import { Calculator } from "./calculator/calculator"; +import { + CUSTOM_SEPARATOR_CONFIG, + DEFAULT_SEPARATOR, +} from "./calculator/constant"; +import { Helper } from "./utils/helper"; + class App { - async run() {} + async run() { + const helper = new Helper(); + const calculator = new Calculator( + DEFAULT_SEPARATOR, + CUSTOM_SEPARATOR_CONFIG + ); + + try { + const input = await helper.handleInput( + "덧셈할 문자열을 입력해 주세요.\n" + ); + const result = calculator.calculate(input); + helper.handleOutput(`결과 : ${result}`); + } catch (error) { + helper.handleError(error); + } + } } export default App; diff --git a/src/calculator/calculator.js b/src/calculator/calculator.js new file mode 100644 index 00000000..54f958d0 --- /dev/null +++ b/src/calculator/calculator.js @@ -0,0 +1,136 @@ +export class Calculator { + #defaultSeparator; + #customSeparatorConfig; + + constructor(defaultSeparator, customSeparatorConfig) { + this.#defaultSeparator = defaultSeparator; + this.#customSeparatorConfig = customSeparatorConfig; + } + + /** + * 커스텀 구분자로부터 숫자 문자열 부분만 추출합니다. + * @param {string} input + * @returns {string} + */ + #extractNumberInput(input) { + return input.substring( + input.indexOf(this.#customSeparatorConfig.END) + + this.#customSeparatorConfig.END.length + ); + } + + /** + * 커스텀 구분자가 존재하는지 검사합니다. + * @param {string} input 입력 문자열 + * @returns {boolean} + */ + #hasCustomSeparator(input) { + return ( + input.startsWith(this.#customSeparatorConfig.START) && + input.includes(this.#customSeparatorConfig.END) + ); + } + + /** + * 커스텀 구분자를 추출합니다. + * @param {string} input 입력 문자열 + * @returns {string} + */ + #extractCustomSeparator(input) { + return input.substring( + this.#customSeparatorConfig.START.length, + input.indexOf(this.#customSeparatorConfig.END) + ); + } + + /** + * 숫자 문자열을 구분자로 분리합니다. + * @param {string} numberInput 숫자 문자열 + * @param {string[] | string} separator 구분자 배열 + * @returns {string[]} + */ + static #splitBySeparator(numberInput, separator) { + const escapedSeparators = Array.isArray(separator) + ? separator + .map((sep) => sep.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) + .join("|") + : separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + + const regex = new RegExp(escapedSeparators); + return numberInput.split(regex); + } + + /** + * 입력값을 양수로 변환합니다. + * @param {string} number + * @returns {number} + */ + static #toPositiveNumber(number) { + const num = Number(number); + if (isNaN(num)) { + throw new Error(`숫자가 아닌 값이 있어요: ${number}`); + } + if (num < 0) { + throw new Error(`양수가 아닌 값이 있어요: ${number}`); + } + + return num; + } + + /** + * 숫자 배열의 합을 계산합니다. + * @param {number[]} numbers 숫자 배열 + */ + static #calculateSum(numbers) { + if (numbers.length === 0) { + return 0; + } + + return numbers.reduce((acc, num) => acc + num, 0); + } + + /** + * 입력값을 검증합니다. + * @param {string} input 입력 문자열 + * @throws {Error} 유효하지 않은 입력값인 경우 + */ + static #validateInput(input) { + if (typeof input !== "string") { + throw new Error("입력값은 문자열이어야 합니다."); + } + + if (input.trim().length === 0) { + throw new Error("입력값이 비어있습니다."); + } + } + + /** + * 문자열을 파싱하고 합계를 계산합니다 + * @param {string} input 입력 문자열 + * @returns {number} 계산 결과 + */ + calculate(input) { + Calculator.#validateInput(input); + + let separator; + let numberInput; + + if (this.#hasCustomSeparator(input)) { + separator = this.#extractCustomSeparator(input); + numberInput = this.#extractNumberInput(input); + } else { + separator = this.#defaultSeparator; + numberInput = input; + } + + if (numberInput.trim().length === 0) { + return 0; + } + + const numbers = Calculator.#splitBySeparator(numberInput, separator).map( + (num) => Calculator.#toPositiveNumber(num) + ); + + return Calculator.#calculateSum(numbers); + } +} diff --git a/src/calculator/constant.js b/src/calculator/constant.js new file mode 100644 index 00000000..719e8c0f --- /dev/null +++ b/src/calculator/constant.js @@ -0,0 +1,5 @@ +export const DEFAULT_SEPARATOR = [",", ":"]; +export const CUSTOM_SEPARATOR_CONFIG = { + START: "//", + END: "\\n", +}; diff --git a/src/utils/helper.js b/src/utils/helper.js new file mode 100644 index 00000000..a846262c --- /dev/null +++ b/src/utils/helper.js @@ -0,0 +1,27 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; + +export class Helper { + /** + * 사용자로부터 입력을 받습니다. + * @param {string} format 입력 형식 + */ + handleInput(format) { + return MissionUtils.Console.readLineAsync(format); + } + + /** + * 에러를 처리합니다. + * @param {Error} error 에러 객체 + */ + handleError(error) { + throw new Error(`[ERROR] ${error.message}`); + } + + /** + * 출력을 처리합니다. + * @param {string} format 출력 형식 + */ + handleOutput(format) { + MissionUtils.Console.print(format); + } +}