Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d7d51cd
style : README 요구사항 작성
inti0 Oct 19, 2024
6df2587
style : README 기능목록 작성
inti0 Oct 19, 2024
1b486a6
feat : 문자열을 구분하는 StringSplitter와 DefaultStringSplitter 객체 생성
inti0 Oct 19, 2024
23c539d
refactor : StringSplitter의 메소드의 인자를 1개로 수정하고 멤버변수로 만듬
inti0 Oct 19, 2024
f7b1c47
feat : CustomStringSplitter객체 생성
inti0 Oct 19, 2024
d33c78e
test : DefaultStringSplitter 테스트 메소드 생성 및 실패 확인
inti0 Oct 19, 2024
3e98caa
refactor : DefaultStringSplitter의문자열 나누는 메소드 StringTokenizer를 이용하도록 리팩토링
inti0 Oct 20, 2024
2197cd6
refactor : CustomStringSplitter의 문자열 나누는 메소드 StringTokenizer를 이용하도록 리팩토링
inti0 Oct 20, 2024
8adbdd0
test : CustomStringSplitter 테스트코드 작성
inti0 Oct 20, 2024
6ad9d6f
refactor: INVALID_DELIMITER +-.에서 -.로 변경
inti0 Oct 20, 2024
a69c23d
feat : SumCalculator 객체 추가 문자열 전환 후 계산
inti0 Oct 21, 2024
bf5c908
refactor : StringSplitter의 메소드 canSupport추, 구현체들 canSupport구현
inti0 Oct 21, 2024
d2e1359
test : 나누어진 크기가 1인 테스트 추가
inti0 Oct 21, 2024
af791a1
refactor : 허용되지 않는 커스텀구분자2 메소드 수정
inti0 Oct 21, 2024
ca48bca
refactor : 허용되지 않는 구분자에 +추가
inti0 Oct 21, 2024
2d88125
feat : CalculatorView 추가
inti0 Oct 21, 2024
fa86e7a
style : 클래스 줄바꿈 정리
inti0 Oct 21, 2024
99716b5
style : 클래스 줄바꿈 정리
inti0 Oct 21, 2024
b3298c0
feat : StringSplitter를 적절히 선택하는 StringSplitterManager객체 추가
inti0 Oct 21, 2024
4ba1c7a
refactor : StringSplitterManager의 멤버변수 제거 후 메소드 매개인자로 변경
inti0 Oct 21, 2024
948013c
feat : 컨트롤러 역할의 Calculator 추가
inti0 Oct 21, 2024
8b4010a
refactor : 패키지 정리
inti0 Oct 21, 2024
90d4e57
debug : CustomStringSplitter의 정규식 변경
inti0 Oct 21, 2024
325445a
test : SumCalculator 추가
inti0 Oct 21, 2024
c9f6522
refactor : 최종 제출 전 확인
inti0 Oct 21, 2024
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
153 changes: 152 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,152 @@
# java-calculator-precourse
# 기능목록

## 도메인

입력받은 문자열을 구분자를 이용해 나눈다.
- 구분자는 기본적으로 쉼표(,)와 콜론(:)이 포함되어야 한다.
- 문자열 앞부분의 //와 \n사이에 위치하는 문자를 커스텀 구분자로 추가해야한다.
- 커스텀 구분자는 숫자의 특성상 부호를 표기하는 +, -와 소숫점을 표현하는 .은 잘못된 것으로 처리하여 예외를 던진다.
- 구분자라는 특성상 커스텀 구분자의 속하는 문자열의 길이가 2이상일 경우 각각 하나의 문자를 커스텀 구분자로 사용한다.
- (예시) "//;^$\n1;2^3" 의 경우 ;^$라는 길이 3짜리 구분자를 사용하는 것이 아닌 ; ^ $ 이 셋을 각각 하나의 구분자로 사용한다.
- 기본 구분자 쉼표(,)와 콜론(:) 그리고 추가된 커스텀 문자를 제외한 문자열이 나타난 경우 잘못된 것으로 처리하여 예외를 던진다.
- 커스텀 구분자를 등록한 이후 숫자와 구분자가 있어야할 부분에서 숫자와 구분자가 번갈아 등장하지 않고 연속으로 등장한 경우 예외를 던진다.
- 이는 공백문자열로 나타날 것이므로, 나눠진 문자열의 목록을 숫자로 변환하는 부분에서 예외를 던진다.

<br>

숫자의 제한조건은 양수라는 것 외에는 없다. 따라서 확장성을 위하여 문자를 숫자로 변환하는 객체를 따로 관리한다.
- 나눠진 문자열을 숫자로 변환한다.
- 변환한 숫자들을 더하여 문자로 반환한다.

## 출력
- 프로그램 실행시 안내메시지를 출력한다.
- 결과를 출력한다.

## 테스트
- 기본 구분자를 통해 문자열이 잘 나누어지는지 확인한다.
- 커스텀 구분자가 추가된 경우 문자열이 잘 나누어지는지 확인한다.
- 여러개의 커스텀 구분자가 추가된 경우도 확인한다.
- +, -, .이 커스텀 구분자로 포함될 경우 예외를 던진다.
- 구분자가 연속으로 등장한 경우 예외를 던진다.

<br><br><br><br><br>










---



## 학습 목표
- Git, GitHub, IDE 등 실제 개발을 위한 환경에 익숙해진다.
- 교육 분야에 맞는 프로그래밍 언어를 사용하여 간단한 문제를 해결한다.


# 프리코스 진행 방식

## 진행 방식

- 미션은 **과제 진행 요구 사항, 기능 요구 사항, 프로그래밍 요구 사항** 세 가지로 구성되어 있다.
- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.
- 매주 진행할 미션은 화요일 오후 3시부터 확인할 수 있으며, 다음 주 월요일까지 구현을 완료하여 제출해야 한다. 제출은 일요일 오후 3시부터 가능하다.
- 정해진 시간을 지키지 않을 경우 미션을 제출하지 않은 것으로 간주한다.
- 종료 일시 이후에는 추가 푸시를 허용하지 않는다.

---



## 미션 제출 방법

- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다.
- GitHub을 활용한 제출 방법은 [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 를 참고해 제출한다.
- GitHub에 미션을 제출한 후 [우아한테크코스 지원 플랫폼](https://apply.techcourse.co.kr) 에 접속하여 프리코스 과제를 제출한다.
- 자세한 방법은 [링크](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) 를 참고한다.
- 과제를 수행하면서 느낀 점, 배운 점, 많은 시간을 투자한 부분 등 자유롭게 작성한다.

## 과제 제출 전 체크리스트

- 기능을 올바르게 구현했더라도 **요구 사항에 명시된 출력 형식**을 따르지 않으면 0점을 받게 된다.
- 기능 구현을 완료한 후 아래 가이드에 따라 모든 테스트가 성공적으로 실행되는지 확인한다.
- **테스트가 실패하면 점수가 0점**이 되므로 제출하기 전에 반드시 확인한다.

## 테스트 실행 가이드
- 터미널에서 `java -version`을 실행하여 Java 버전이 21인지 확인한다. Eclipse 또는 IntelliJ IDEA와 같은 IDE에서 Java 21로 실행되는지 확인한다.
- 터미널에서 Mac 또는 Linux 사용자의 경우 `./gradlew clean test` 명령을 실행하고, Windows 사용자의 경우 `gradlew.bat clean test` <br>
또는 .\gradlew.bat clean test 명령을 실행할 때 모든 테스트가 아래와 같이 통과하는지 확인한다.

```
BUILD SUCCESSFUL in 0s
```



<br>

# 문자열 덧셈 계산기

---

## 과제 진행 요구 사항

---
- 미션은 [문자열 덧셈 계산기 저장소](https://github.com/woowacourse-precourse/java-calculator-7)를 포크하고 클론하는 것으로 시작한다.
- **기능을 구현하기 전 `README.md`에 구현할 기능 목록을 정리**해 추가한다.
- Git의 커밋 단위는 앞 단계에서 `README.md`에 정리한 기능 목록 단위로 추가한다.
- [AngularJS Git Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153)을 참고해 커밋 메시지를 작성한다.
- 자세한 과제 진행 방법은 프리코스 진행 가이드 문서를 참고한다.



## 기능 요구 사항
입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다.
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.
- 예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6
- 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
- 예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다.
- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다.

### 입출력 요구 사항
#### 입력
- 구분자와 양수로 구성된 문자열

#### 출력
- 덧셈 결과
```
결과 : 6
```
#### 실행 결과 예시
```
덧셈할 문자열을 입력해 주세요.
1,2:3
결과 : 6
```


## 프로그래밍 요구 사항

---

- JDK 21 버전에서 실행 가능해야 한다.
- 프로그램을 실행하는 시작점은 `Application`의 `main()`이다.
- build.gradle 파일은 변경할 수 없으며, **제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.**
- 프로그램 종료 시 System.exit()를 호출하지 않는다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Java Style Guide를 원칙으로 한다.

### 라이브러리

- `camp.nextstep.edu.missionutils`에서 제공하는 Console API를 사용하여 구현해야 한다.
- 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다.

<br>

3 changes: 2 additions & 1 deletion src/main/java/calculator/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

import calculator.domain.SumCalculator;
import calculator.domain.stringsplitter.CustomStringSplitter;
import calculator.domain.stringsplitter.DefaultStringSplitter;
import calculator.domain.StringSplitterManager;
import camp.nextstep.edu.missionutils.Console;

import java.util.List;

public class Calculator {

private final CalculatorView view = new CalculatorView();
private final StringSplitterManager splitterManager = new StringSplitterManager(
List.of(
new DefaultStringSplitter(),
new CustomStringSplitter()
)
);

public void run() {
String input = view.readInput();

List<String> splitStrings = splitterManager.getSplitStrings(input);
SumCalculator sumCalculator = new SumCalculator(splitStrings);
String result = sumCalculator.calculateSum();

view.printOutput(result);

Console.close();
}
}
15 changes: 15 additions & 0 deletions src/main/java/calculator/CalculatorView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package calculator;

import camp.nextstep.edu.missionutils.Console;

public class CalculatorView {

public String readInput() {
System.out.println("덧셈할 문자열을 입력해 주세요.");
return Console.readLine();
}

public void printOutput(String str) {
System.out.println("결과 : " + str);
}
}
23 changes: 23 additions & 0 deletions src/main/java/calculator/domain/StringSplitterManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package calculator.domain;

import calculator.domain.stringsplitter.StringSplitter;

import java.util.List;

public class StringSplitterManager {

private final List<StringSplitter> stringSplitters;

public StringSplitterManager(List<StringSplitter> stringSplitters) {
this.stringSplitters = stringSplitters;
}

public List<String> getSplitStrings(String str) {
StringSplitter stringSplitter = stringSplitters.stream()
.filter(splitter -> splitter.canSupport(str))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(str));

return stringSplitter.splitString(str);
}
}
34 changes: 34 additions & 0 deletions src/main/java/calculator/domain/SumCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package calculator.domain;

import java.math.BigDecimal;
import java.util.List;

public class SumCalculator {

private final List<String> strings;

public SumCalculator(List<String> strings) {
this.strings = strings;
}

public String calculateSum() {
BigDecimal sum = strings.stream()
.map(str -> convertToBigDecimal(str))
.reduce(new BigDecimal("0"), (o1, o2) -> o1.add(o2));

return sum.toString();
}

private BigDecimal convertToBigDecimal(String str) {
if (str.equals("0")) {
throw new IllegalArgumentException(strings.toString());
}

String positiveNumberPattern = "^[+]?[0-9]+([.][0-9]+)?";
if (!str.matches(positiveNumberPattern)) {
throw new IllegalArgumentException(strings.toString());
}

return new BigDecimal(str);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package calculator.domain.stringsplitter;

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

public class CustomStringSplitter implements StringSplitter {

private static final String CUSTOM_DELIMITER_HEADER = "\\/\\/";
private static final String CUSTOM_DELIMITER_FOOTER = "[(\n)(\\\\n)]";

@Override
public boolean canSupport(String str) {
String patternRegex = generatePatternRegex();
Pattern pattern = Pattern.compile(patternRegex);
Matcher matcher = pattern.matcher(str);
boolean isFound = matcher.matches();

if (isFound) {
return true;
}
return false;
}

@Override
public List<String> splitString(String str) {
String patternRegex = generatePatternRegex();
Pattern pattern = Pattern.compile(patternRegex);
Matcher matcher = pattern.matcher(str);
matcher.matches();

String customDelimiter = matcher.group(1);
validateCustomDelimiter(customDelimiter, str);
String conjoinedDelimiter = DEFAULT_DELIMITER + customDelimiter;

String numbersAndDelimiter = matcher.group(2);
numbersAndDelimiter = replaceBackSlash(customDelimiter, numbersAndDelimiter);
List<String> result = splitSubstring(conjoinedDelimiter, numbersAndDelimiter);
validateContinuousDelimiter(result, str);

return result;
}

private String replaceBackSlash(String customDelimiter, String numbersAndDelimiter) {
if (customDelimiter.contains("\\")) {
numbersAndDelimiter = numbersAndDelimiter.replace("\\", String.valueOf(DEFAULT_DELIMITER.charAt(0)));
}
return numbersAndDelimiter;
}

private String generatePatternRegex() {
StringBuilder delimiterBuilder = new StringBuilder();

delimiterBuilder.append("^")
.append(CUSTOM_DELIMITER_HEADER)
.append("([^0-9]+)")
.append(CUSTOM_DELIMITER_FOOTER)
.append("(.+)");

return delimiterBuilder.toString();
}

private void validateCustomDelimiter(String customDelimiter, String str) {
boolean hasInvalidDelimiter = Arrays.stream(customDelimiter.split(""))
.anyMatch(s -> INVALID_DELIMITER.contains(s));

if (hasInvalidDelimiter) {
throw new IllegalArgumentException(str);
}
}

private List<String> splitSubstring(String conjoinedDelimiter, String numbersAndDelimiter) {
StringTokenizer stringTokenizer = new StringTokenizer(numbersAndDelimiter, conjoinedDelimiter);

List<String> strings = new ArrayList<>();
while (stringTokenizer.hasMoreTokens()) {
strings.add(stringTokenizer.nextToken());
}
return strings;
}

private void validateContinuousDelimiter(List<String> split, String str) {
boolean hasContinuousDelimiter = split.stream()
.anyMatch(s -> s.isEmpty());

if (hasContinuousDelimiter) {
throw new IllegalArgumentException(str);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package calculator.domain.stringsplitter;

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

public class DefaultStringSplitter implements StringSplitter {

@Override
public boolean canSupport(String str) {
Pattern pattern = Pattern.compile("^[0-9]+([" + DEFAULT_DELIMITER + "][0-9]+)*$");
Matcher matcher = pattern.matcher(str);
return matcher.find();
}

@Override
public List<String> splitString(String str) {
StringTokenizer stringTokenizer = new StringTokenizer(str, DEFAULT_DELIMITER);
List<String> strings = new ArrayList<>();
while (stringTokenizer.hasMoreTokens()) {
strings.add(stringTokenizer.nextToken());
}
return strings;
}
}
Loading