Skip to content

Commit 5290a8b

Browse files
authored
Step1 - 문자열 계산기 (#4197)
* feat: 1단계 문자열 계산기 - 초안 구성 * feat: 1단계 문자열 계산기 - 입력값 유효성검사 구현 및 테스트 코드 구현 * feat: 1단계 문자열 계산기 - 관련 내용 구현 및 테스트
1 parent 90905b7 commit 5290a8b

File tree

8 files changed

+349
-0
lines changed

8 files changed

+349
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package calculator;
2+
3+
public class CalculatorFactory {
4+
private static final String ADD = "+";
5+
private static final String SUB = "-";
6+
private static final String MULTI = "*";
7+
private static final String DIV = "/";
8+
9+
10+
public int handleWayByOperator(String operator, int left, int right) {
11+
if (operator.equals(ADD)) {
12+
return add(left, right);
13+
}
14+
15+
if (operator.equals(SUB)) {
16+
return sub(left, right);
17+
}
18+
19+
if (operator.equals(MULTI)) {
20+
return multi(left, right);
21+
}
22+
23+
if(operator.equals(DIV)) {
24+
return div(left, right);
25+
}
26+
27+
throw new IllegalArgumentException("사칙연산 기호만 입력 할 수 있습니다");
28+
}
29+
30+
public int add(int left, int right) {
31+
return left + right;
32+
}
33+
34+
public int sub(int left, int right) {
35+
return left - right;
36+
}
37+
38+
public int multi(int left, int right) {
39+
return left * right;
40+
}
41+
42+
public int div(int left, int right) {
43+
return left / right;
44+
}
45+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package calculator;
2+
3+
import java.util.LinkedList;
4+
import java.util.List;
5+
import java.util.Queue;
6+
7+
public class CalculatorTokens {
8+
private Queue<String> calculateTokens;
9+
private final String firstTokenNum;
10+
11+
public CalculatorTokens(List<String> calculateTokens) {
12+
if (calculateTokens.isEmpty()) {
13+
throw new IllegalArgumentException("계산토큰목록을 필수 입니다.");
14+
}
15+
16+
this.calculateTokens = new LinkedList<>(calculateTokens);
17+
this.firstTokenNum = this.calculateTokens.poll();
18+
}
19+
20+
public int firstTokenNum() {
21+
return Integer.parseInt(firstTokenNum);
22+
}
23+
24+
public OneTokenBundle getOneTokenBundle() {
25+
if (calculateTokens.isEmpty()) {
26+
throw new RuntimeException("계산할 수 있는 토큰이 없습니다");
27+
}
28+
29+
return new OneTokenBundle(
30+
calculateTokens.poll(),
31+
calculateTokens.poll()
32+
);
33+
}
34+
35+
public boolean hasNext() {
36+
return !calculateTokens.isEmpty();
37+
}
38+
39+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package calculator;
2+
3+
import static java.util.Objects.isNull;
4+
5+
import java.util.List;
6+
import java.util.Set;
7+
import java.util.regex.Pattern;
8+
9+
public class InputValidator {
10+
11+
private static final String NUM_AND_OPERATOR_DELIMITER = " ";
12+
private static final Pattern POSITIVE_NUMBER_PATTERN = Pattern.compile("[-+]?\\d*\\.?\\d+");
13+
private static final Set<String> OPERATORS = Set.of("+", "-", "*", "/");
14+
15+
public List<String> validAndSplitInput(String input) {
16+
if (isNull(input) || input.isBlank()) {
17+
throw new IllegalArgumentException("입력값이 없거나 빈 문자열 입니다");
18+
}
19+
20+
String[] inputArrays = trimAndSplit(input);
21+
if (inputArrays.length < 2) {
22+
throw new IllegalArgumentException("입력값이 하나이거나 비정상 문자열 입니다");
23+
}
24+
25+
for (int index = 0; index < inputArrays.length; index++) {
26+
validInputByIndex(index, inputArrays);
27+
}
28+
29+
return List.of(inputArrays);
30+
}
31+
32+
private String[] trimAndSplit(String input) {
33+
return input.trim().split(NUM_AND_OPERATOR_DELIMITER);
34+
}
35+
36+
private boolean isEvenNum(int number) {
37+
return number % 2 == 0;
38+
}
39+
40+
private boolean isOddNum(int number) {
41+
return number % 2 == 1;
42+
}
43+
44+
private void validInputByIndex(int index, String[] inputArrays) {
45+
if (isEvenNum(index)) {
46+
validNumber(inputArrays[index]);
47+
return;
48+
}
49+
50+
if (isOddNum(index)) {
51+
validOperator(inputArrays[index]);
52+
}
53+
}
54+
55+
private void validNumber(String inputArrays) {
56+
if (!POSITIVE_NUMBER_PATTERN.matcher(inputArrays).matches()) {
57+
throw new IllegalArgumentException("입력값이 정수가 아닙니다. 정해진 입력값 양식에 맞춰 작성해주세요");
58+
}
59+
}
60+
61+
private void validOperator(String inputArrays) {
62+
if (!OPERATORS.contains(inputArrays)) {
63+
throw new IllegalArgumentException("입력값이 연산자가 아닙니다. 정해진 입력값 양식에 맞춰 작성해주세요");
64+
}
65+
}
66+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package calculator;
2+
3+
public class OneTokenBundle {
4+
private final String operator;
5+
private final String rightNumber;
6+
7+
public OneTokenBundle(
8+
String operator,
9+
String rightNumber
10+
) {
11+
this.operator = operator;
12+
this.rightNumber = rightNumber;
13+
}
14+
15+
public String operator() {
16+
return operator;
17+
}
18+
19+
public int rightNumber() {
20+
return Integer.parseInt(rightNumber);
21+
}
22+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package calculator;
2+
3+
import java.util.List;
4+
5+
/**
6+
*
7+
* 기능 요구사항
8+
* 사용자가 입력한 문자열 값에 따라 사칙연산을 수행할 수 있는 계산기를 구현해야 한다.
9+
* 입력 문자열의 숫자와 사칙 연산 사이에는 반드시 빈 공백 문자열이 있다고 가정한다.
10+
* 나눗셈의 경우 결과 값을 정수로 떨어지는 값으로 한정한다.
11+
* 문자열 계산기는 사칙연산의 계산 우선순위가 아닌 입력 값에 따라 계산 순서가 결정된다. 즉, 수학에서는 곱셈, 나눗셈이 덧셈, 뺄셈 보다 먼저 계산해야 하지만 이를 무시한다.
12+
* 예를 들어 2 + 3 * 4 / 2와 같은 문자열을 입력할 경우 2 + 3 * 4 / 2 실행 결과인 10을 출력해야 한다.
13+
*
14+
* */
15+
16+
17+
public class StringCalculator {
18+
19+
private static final String NUM_AND_OPERATOR_DELIMITER = " ";
20+
21+
private final CalculatorFactory calculatorFactory;
22+
23+
public StringCalculator(CalculatorFactory calculatorFactory) {
24+
this.calculatorFactory = calculatorFactory;
25+
}
26+
27+
public int calculate(String input) {
28+
CalculatorTokens calculateTokens = new CalculatorTokens(
29+
new InputValidator().validAndSplitInput(input)
30+
);
31+
32+
int result = calculateTokens.firstTokenNum();
33+
34+
while (calculateTokens.hasNext()) {
35+
OneTokenBundle oneTokenBundle = calculateTokens.getOneTokenBundle();
36+
37+
result = calculatorFactory.handleWayByOperator(
38+
oneTokenBundle.operator(),
39+
result,
40+
oneTokenBundle.rightNumber()
41+
);
42+
}
43+
44+
return result;
45+
}
46+
47+
48+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package calculator;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
class CalculatorFactoryTest {
8+
9+
10+
@Test
11+
void 덧셈() {
12+
CalculatorFactory calculator = new CalculatorFactory();
13+
14+
assertThat(
15+
calculator.handleWayByOperator(
16+
"+",
17+
1,
18+
2
19+
)
20+
).isEqualTo(3);
21+
}
22+
23+
@Test
24+
void 뺄셈() {
25+
CalculatorFactory calculator = new CalculatorFactory();
26+
27+
assertThat(
28+
calculator.handleWayByOperator(
29+
"-",
30+
2,
31+
1
32+
)
33+
).isEqualTo(1);
34+
}
35+
36+
@Test
37+
void 곱셈() {
38+
CalculatorFactory calculator = new CalculatorFactory();
39+
40+
assertThat(
41+
calculator.handleWayByOperator(
42+
"*",
43+
2,
44+
2
45+
)
46+
).isEqualTo(4);
47+
}
48+
49+
@Test
50+
void 나눗셈_나머지가_있는경우도_몫만_반환할_수_있다() {
51+
CalculatorFactory calculator = new CalculatorFactory();
52+
53+
assertThat(
54+
calculator.handleWayByOperator(
55+
"/",
56+
5,
57+
2
58+
)
59+
).isEqualTo(2);
60+
}
61+
62+
@Test
63+
void 나눗셈_나머지가_없는경우도_몫만_반환할_수_있다() {
64+
CalculatorFactory calculator = new CalculatorFactory();
65+
66+
assertThat(
67+
calculator.handleWayByOperator(
68+
"/",
69+
4,
70+
2
71+
)
72+
).isEqualTo(2);
73+
}
74+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package calculator;
2+
3+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.ValueSource;
8+
9+
class InputValidatorTest {
10+
11+
@Test
12+
void 계산기의_입력값이_없는경우_예외처리를_할_수_있다() {
13+
assertThatThrownBy(() -> new InputValidator().validAndSplitInput(""))
14+
.isInstanceOf(IllegalArgumentException.class);
15+
}
16+
17+
@ParameterizedTest
18+
@ValueSource(strings = {"1", "1 2", "&"})
19+
void 구분자로_나눈_입력배열의_길이가_하나이면_예외처리_할_수_있다(String input) {
20+
assertThatThrownBy(
21+
() -> new InputValidator().validAndSplitInput(input)
22+
).isInstanceOf(IllegalArgumentException.class);
23+
}
24+
25+
@Test
26+
void 구분자로_나눈_입력배열중_짝수인덱스가_정수가_아니면_예외처리_할_수_있다() {
27+
assertThatThrownBy(
28+
() -> new InputValidator().validAndSplitInput("4 + 5 * /")
29+
).isInstanceOf(IllegalArgumentException.class);
30+
}
31+
32+
@Test
33+
void 구분자로_나눈_입력배열중_홀수인덱스가_연산자가_아니면_예외처리_할_수_있다() {
34+
assertThatThrownBy(
35+
() -> new InputValidator().validAndSplitInput("4 + 5 6 8")
36+
).isInstanceOf(IllegalArgumentException.class);
37+
}
38+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package calculator;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
class StringCalculatorTest {
8+
9+
@Test
10+
void 사칙연산을_계산할_수_있다() {
11+
StringCalculator calculator = new StringCalculator(new CalculatorFactory());
12+
13+
assertThat(
14+
calculator.calculate("2 + 3 * 4 / 2")
15+
).isEqualTo(10);
16+
}
17+
}

0 commit comments

Comments
 (0)