Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[문자열 덧셈 계산기] 박정현 미션 제출합니다. #1893

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# java-calculator-precourse
# 🐣문자열 덧셈 계산기
- 입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다.
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.
- 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.

## ✅ 기능 목록
**사용자에게 문자열 입력**
- [X] 가이드 문구 출력한다.
- [X] `camp.nextstep.edu.missionutils.Console`의 `readLine()` 사용

**구분자로 숫자 추출 후 계산**
- [X] 기본 구분자(쉼표`,` , 콜론`:`)으로 분리한다.
- [X] "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
- [X] 구분자를 사용해 문자열을 나눈다.
- [X] 나눈 문자열을 숫자로 변환 후 합을 출력한다.

**입력 예외처리 (`IllegalArgumentException`)**
- [X] 입력한 숫자가 음수를 포함할 때 예외로 처리한다.
- [X] 구분자가 두 개 연속으로 나오면 예외로 처리한다.
- [X] 구분자가 숫자면 예외로 처리한다.
6 changes: 4 additions & 2 deletions src/main/java/calculator/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package calculator;
import controller.CalculatorController;


public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
new CalculatorController().calculate();
}
}
}
34 changes: 34 additions & 0 deletions src/main/java/controller/CalculatorController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package controller;

import model.Calculator;
import view.InputView;
import view.OutputView;

public class CalculatorController {
//의존성 추가


private final InputView inputView;
private final OutputView outputView;

public CalculatorController() {
this.inputView = new InputView();
this.outputView = new OutputView();
}


/**
* 문자열 계산기를 실행하는 메서드
**/
public void calculate() {
// 문자열 입력 받기
String input = inputView.input();

// Calculator를 사용하여 입력 문자열의 합을 계산
Calculator calculator = Calculator.from(input); // 수정된 부분
Long sum = calculator.sum();

// 결과 출력
outputView.output(sum.intValue());
}
}
20 changes: 20 additions & 0 deletions src/main/java/exception/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package exception;

public enum ErrorMessage {
NEGATIVE_NUMBER_NOT_ALLOWED("양수만 계산할 수 있습니다."),
INVALID_NUMBERS_RANGE("정수의 범위가 정상적이지 않습니다."),
INPUT_MUST_BE_NUMERIC("숫자만 계산할 수 있습니다."),
DELIMITER_LENGTH_EXCEEDS_ONE("구분자의 길이는 1이어야 합니다."),
INVALID_DELIMITER_TYPE("문자가 아닌 구분자가 입력되었습니다."),
DUPLICATE_WITH_DEFAULT_DELIMITER("기본 구분자(쉼표, 콜론)와 중복되는 커스텀 구분자입니다.");

private final String message;

ErrorMessage(String message) {
this.message = message;
}

public String getMessage() {
return this.message;
}
}
19 changes: 19 additions & 0 deletions src/main/java/model/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package model;

import java.util.ArrayList;

public class Calculator {
private final Numbers numbers;

private Calculator(String input) {
numbers = NumberSeparator.from(input).separate();
}

public static Calculator from(String input) {
return new Calculator(input);
}

public Long sum() {
return numbers.sum();
}
}
60 changes: 60 additions & 0 deletions src/main/java/model/Delimiter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package model;

import exception.ErrorMessage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

public class Delimiter {
private static final int DELIMITER_LENGTH = 1;
private static final String DEFAULT_DELIMITER = ",|:";
private static final Pattern SINGLE_NUMERIC_PATTERN = Pattern.compile("\\d");

private final String input;

private Delimiter(String input) {
validateLength(input);
validateDelimiterCharacter(input);

this.input = input;
}

// 구분자의 길이를 확인하는 함수
private void validateLength(String input) {
if (input.isBlank() || input.length() > DELIMITER_LENGTH) {
throw new IllegalArgumentException(ErrorMessage.DELIMITER_LENGTH_EXCEEDS_ONE.getMessage());
}
}

private void validateDelimiterCharacter(String input) {
if (SINGLE_NUMERIC_PATTERN.matcher(input).matches()) {
throw new IllegalArgumentException(ErrorMessage.INVALID_DELIMITER_TYPE.getMessage());
}
}

/**
* 기본 구분자를 반환하는 메서드 (Delimiter 객체 리스트로)
**/
public static List<Delimiter> getDefaultDelimiter() {
List<Delimiter> delimiters = new ArrayList<>();
// 기본 구분자 (",", ":")를 Delimiter 객체로 변환하여 리스트에 추가
Arrays.asList(DEFAULT_DELIMITER.split("\\|")).forEach(delim -> delimiters.add(new Delimiter(delim)));
return delimiters;
}

/**
* 문자열을 Delimiter 객체로 변환하는 정적 메서드
**/
public static Delimiter from(String delimiter) {
return new Delimiter(delimiter);
}

public static String toRegex(List<Delimiter> delimiters) {
return delimiters.stream()
.map(delim -> Pattern.quote(delim.input)) // 구분자 값을 안전하게 사용하기 위해 quote 처리
.reduce((d1, d2) -> d1 + "|" + d2)
.orElse(DEFAULT_DELIMITER); // 기본 구분자를 기본값으로 사용
}
}
77 changes: 77 additions & 0 deletions src/main/java/model/NumberSeparator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package model;

import exception.ErrorMessage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NumberSeparator {
private static final String NUMBER_PATTERN = "^-?[0-9]+$";
private static final Pattern CUSTOM_DELIMITER_PATTERN = Pattern.compile("^//(.*?)\\\\n(.*?)$");
private static final String DEFAULT_OPERATION = ",|:";
private final List<Delimiter> delimiters = new ArrayList<>();
private String equation;

/**
* 구분자에 따라 문자열을 분리하는 함수
**/
private NumberSeparator(String input) {
delimiters.addAll(Delimiter.getDefaultDelimiter());

Matcher matcher = getMatcher(input);
if (hasCustomDelimiter(matcher)) {
delimiters.add(extractCustomDelimiter(matcher));
equation = extractEquation(matcher);
return;
}

equation = input;
}

public static NumberSeparator from(String input) {
return new NumberSeparator(input);
}

private void validateNotDefaultDelimiter(Delimiter delimiter) {
Delimiter.getDefaultDelimiter().stream()
.filter(defaultDelimiter -> defaultDelimiter.equals(delimiter))
.findAny()
.ifPresent(matched -> {
throw new IllegalArgumentException(
ErrorMessage.DUPLICATE_WITH_DEFAULT_DELIMITER.getMessage());
});
}

private Matcher getMatcher(String input) {
return CUSTOM_DELIMITER_PATTERN.matcher(input);
}

private boolean hasCustomDelimiter(Matcher matcher) {
return matcher.find();
}

private Delimiter extractCustomDelimiter(Matcher matcher) {
Delimiter customDelimiter = Delimiter.from(matcher.group(1));
validateNotDefaultDelimiter(customDelimiter);
return customDelimiter;
}

private String extractEquation(Matcher matcher) {
return matcher.group(2).trim();
}

public Numbers separate() {
if (equation == null ||equation.isEmpty()) {
return Numbers.parseNumbers(new ArrayList<>());
}

return Numbers.parseNumbers(
Arrays.stream(
equation.split(Delimiter.toRegex(delimiters))
).map(String::trim).toList()
);
}
}
59 changes: 59 additions & 0 deletions src/main/java/model/Numbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package model;

import exception.ErrorMessage;

import java.util.List;
import java.util.regex.Pattern;

public class Numbers {
private final static long MIN = 1L;
private final static long SUM_BASE = 0L;
private static final Pattern NUMBER_PATTERN = Pattern.compile("^0|[1-9]+[0-9]*$");

private final List<Long> numbers;


public Numbers(List<Long> numbers) {
validatePositive(numbers);
this.numbers = numbers;
}

public static Numbers parseNumbers(List<String> stringNumbers) {
validateNumber(stringNumbers);

try {
List<Long> numbers = stringNumbers.stream()
.map(Long::parseLong)
.toList();

return new Numbers(numbers);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(ErrorMessage.INVALID_NUMBERS_RANGE.getMessage());
}
}

private void validatePositive(List<Long> numbers) {
numbers.stream()
.filter(number -> number < MIN)
.findAny()
.ifPresent(number -> {
throw new IllegalArgumentException(ErrorMessage.NEGATIVE_NUMBER_NOT_ALLOWED.getMessage());
});
}

private static void validateNumber(List<String> stringNumbers) {
// 리스트의 내용 출력 (디버깅용)
System.out.println("Checking numbers: " + stringNumbers);

if (!stringNumbers.stream().allMatch(
stringNumber -> NUMBER_PATTERN.matcher(stringNumber).matches()
)) {
throw new IllegalArgumentException(ErrorMessage.INPUT_MUST_BE_NUMERIC.getMessage());
}
}

Long sum() {
return numbers.stream()
.reduce(SUM_BASE, Long::sum);
}
}
14 changes: 14 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package view;

import camp.nextstep.edu.missionutils.Console;

public class InputView {

/**
* 덧셈할 문자열 입력받는 함수
* **/
public String input() {
System.out.println("덧셈할 문자열을 입력해 주세요. ");
return Console.readLine();
}
}
12 changes: 12 additions & 0 deletions src/main/java/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package view;

public class OutputView {


/**
* 결과 출력 함수
* **/
public void output(int sum) {
System.out.printf("%s %d", "결과 :", sum);
}
}