Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
# javascript-calculator-precourse
# javascript-calculator-precourse

## 기능 요구 사항

입력한 문자열에서 구분자를 기준으로 숫자를 추출하여 덧셈을 수행합니다.

1. 문자열 입력 받기
2. 입력받은 문자열에서 구분자와 숫자 문자열 추출
3. 숫자 문자열을 구분자를 기준으로 분리한 후 값 검증
4. 덧셈 수행 후 결과 출력

**구분자**
구분자를 제외하고 숫자가 입력되지 않은 경우는 0으로 처리

- **쉼표(,)**, **콜론( : )**
- 커스텀 구분자 : **문자열 앞 //와 \n 사이**에 위치하는 문자

### 에러처리

잘못된 값이 입력된 경우 "[Error]"로 시작하는 메세지와 함꼐 `Error`를 발생시킵니다.

1. 구분자를 제외하고 숫자가 아닌 값이 입력된 경우
2. 양수가 아닌 숫자가 입력된 경우
25 changes: 24 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -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;
136 changes: 136 additions & 0 deletions src/calculator/calculator.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
5 changes: 5 additions & 0 deletions src/calculator/constant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const DEFAULT_SEPARATOR = [",", ":"];
export const CUSTOM_SEPARATOR_CONFIG = {
START: "//",
END: "\\n",
};
27 changes: 27 additions & 0 deletions src/utils/helper.js
Original file line number Diff line number Diff line change
@@ -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);
}
}