Skip to content

[4기 - 한승원] 1~2주차 과제 : 계산기 구현 미션 제출합니다(사칙연산) #168

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

Open
wants to merge 49 commits into
base: seungwon
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2f8bb46
feat : 입출력 인터페이스
SW-H Jun 11, 2023
5a6deed
feat : 예외 처리 문구
SW-H Jun 11, 2023
c4c3e88
feat : 계산기 - main문/계산
SW-H Jun 11, 2023
e2dcb42
feat : 입출력 인터페이스 구현
SW-H Jun 11, 2023
bd3aee3
feat : 연산자 enum으로 관리
SW-H Jun 11, 2023
52a1300
feat : 사용자 메뉴 enum
SW-H Jun 11, 2023
ee4d032
docs : java버전 명시
SW-H Jun 11, 2023
842467d
docs : 미사용중인 종속 삭제
SW-H Jun 11, 2023
c9c80e0
refactor : 코드 중복 제거
SW-H Jun 11, 2023
25a513e
refactor : 메인 함수를 포함한 클래스 추가
SW-H Jun 11, 2023
6d94091
refactor : 불필요한 개행 삭제
SW-H Jun 11, 2023
4d764ca
feat : 수식
SW-H Jun 15, 2023
882c684
feat : 삭제
SW-H Jun 15, 2023
1a150a5
feat : input/outputView 주입, 선택 메뉴별 수행 메소드를 HashMap으로 분리
SW-H Jun 15, 2023
a3901a5
feat : 출력 관련 내용 CalculatorInputView -> CalculatorOutputView 이동
SW-H Jun 15, 2023
451742e
refactor
SW-H Jun 15, 2023
775466d
feat : 수식 입력시 Expression 클래스 반환
SW-H Jun 15, 2023
5e41f40
feat : 불필요한 문법요소 제거, 연산자 우선순위 비교 메소드(hasLowerPrecedence) 추가
SW-H Jun 15, 2023
2590e8c
feat : 계산 결과 출력을 위해 인자->Integer
SW-H Jun 15, 2023
6b66bc0
feat : 불필요한 Optional 삭제, 메뉴-Integer 대응하도록 수정
SW-H Jun 14, 2023
2f88ba9
feat : main 함수 별도 분리
SW-H Jun 15, 2023
f154103
feat : 계산 인자 -> Double 형으로 변환(나눗셈)
SW-H Jun 15, 2023
ddc919c
feat : 함수 매핑 HashMap 삭제
SW-H Jun 15, 2023
9b34581
feat : CalculatorInputView 내에서 출력 함수 호출 삭제
SW-H Jun 15, 2023
9b943b9
feat : 계산 결과 Integer -> Double(나눗셈)
SW-H Jun 15, 2023
c6c5a68
feat : 문자열 자르기
SW-H Jun 15, 2023
6d43a13
feat : 계산 인자 Integer -> Double (나눗셈)
SW-H Jun 15, 2023
97b393a
feat : 계산 인자 Integer -> Double (나눗셈)
SW-H Jun 15, 2023
84540a2
feat : main 함수
SW-H Jun 15, 2023
eeb3796
feat : 계산 로직 수정(후위연산식 변환없이 연산자 우선 순위에 따라 계산)
SW-H Jun 15, 2023
c37cdad
feat : 수식 내의 공백 제거, split
SW-H Jun 15, 2023
50cabb1
refactor : list 타입 ArrayList -> List
SW-H Jun 15, 2023
0ac007e
feat : 연산자 우선 순위 계산
SW-H Jun 15, 2023
8b4ec3a
refactor : 미사용 메소드 삭제
SW-H Jun 15, 2023
a3544d8
feat : 없는 메뉴선택시 처리 추가
SW-H Jun 15, 2023
ea158fe
feat : 메시지 출력 메소드
SW-H Jun 15, 2023
6f1519d
feat : 메시지 출력 메소드
SW-H Jun 15, 2023
7fe46d5
feat : 없는 메뉴 선택시 처리 방법 수정
SW-H Jun 15, 2023
aa52739
refactor
SW-H Jun 15, 2023
202ce5d
feat : 계산 로직 버그 수정
SW-H Jun 15, 2023
716fb12
feat : 입력된 수식 null인 경우 처리
SW-H Jun 15, 2023
c2351bd
feat : 입력된 수식 null인 경우 처리
SW-H Jun 15, 2023
1f42c10
feat : 입력된 수식이 빈 문자열인 경우 처리
SW-H Jun 15, 2023
358117c
feat : 계산 기록 저장을 위한 저장소 추가
SW-H Jun 15, 2023
f635ecb
feat : 계산 기록 저장을 위한 저장소 추가, 조회 기능
SW-H Jun 15, 2023
06b76d3
feat : 계산 기록 출력
SW-H Jun 15, 2023
84ff197
feat : 수식 getter
SW-H Jun 15, 2023
49675ef
feat : 계산 기록 저장을 위한 저장소 추가
SW-H Jun 15, 2023
62afa70
feat : 계산 기록 저장을 위한 저장소 인터페이스 추가
SW-H Jun 16, 2023
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
11 changes: 6 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ plugins {
group = 'co.programmers'
version = '1.0-SNAPSHOT'

repositories {
mavenCentral()
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
}

dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
repositories {
mavenCentral()
}

test {
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/co/programmers/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package co.programmers;

import co.programmers.domain.CalculatorApp;
import co.programmers.repository.CalculatorRepository;
import co.programmers.repository.Repository;
import co.programmers.view.CalculatorInputView;
import co.programmers.view.CalculatorOutputView;
import co.programmers.view.InputView;
import co.programmers.view.OutputView;

public class App {

public static void main(String[] args) {
InputView inputView = new CalculatorInputView();
OutputView outputView = new CalculatorOutputView();
Repository repository = new CalculatorRepository();
CalculatorApp calculatorApp = new CalculatorApp(inputView, outputView, repository);
calculatorApp.run();
}
}
59 changes: 59 additions & 0 deletions src/main/java/co/programmers/domain/Calculation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package co.programmers.domain;

import java.util.ArrayList;
import java.util.List;

public class Calculation {

private List<String> parsedExpression;
private List<String> operators;
private Expression expression;

public Calculation(Expression expression) {
this.expression = expression;
parsedExpression = new ArrayList<>();
operators = new ArrayList<>();
}

public Double calculate() throws ArithmeticException {
parsedExpression = expression.split();
operators = extractOperators();
Comment on lines +19 to +20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게만 사용할거라면 굳이 미리 클래스에 선언해야할까요??

Operator.decideCalculationOrder(operators);
for (String operator : operators) {
int operatorPosition = parsedExpression.indexOf(operator);
Double[] operands = extractOperands(operator);
Double calculationRes = Operator.calculate(operator, operands[0], operands[1]);
storeIntermediateResult(operatorPosition, calculationRes);
removeCompletedExpression(operatorPosition, operands.length);
}
Comment on lines +22 to +28

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

더 단순하게 정리해볼까요?

return calcFinalResult();
}

private void removeCompletedExpression(int operatorPosition, int count) {
for (int cnt = 0; cnt <= count; cnt++) {
parsedExpression.remove(operatorPosition);
}
Comment on lines +32 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cnt와 count가 동일한 의미를 가지고 있어서 이름을 바꾸는 편이 좋아보여요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 그 부분 수정하면서
두번째 인자도 삭제할 수식의 길이를 의미함을 알아보기 좋도록 length로 변경했습니다

private void removeCompletedExpression(int operatorPosition, int length) {
  for (int i = 0; i <= length; i++) {
	  parsedExpression.remove(operatorPosition);
  }
}

}

private void storeIntermediateResult(int operatorPosition, Double calculationRes) {
parsedExpression.add(operatorPosition - 1, String.valueOf(calculationRes));
}

private Double[] extractOperands(String operator) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractOperands는 어떤 기능을 하는 메서드 인가요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

매개변수로 주어진 Operator에 해당하는 피연산자를 뽑아내는 메서드입니다!

int operatorIdx = parsedExpression.indexOf(operator);
Double operand1 = Double.parseDouble(parsedExpression.get(operatorIdx - 1));
Double operand2 = Double.parseDouble(parsedExpression.get(operatorIdx + 1));
Comment on lines +44 to +45
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parsedExpression.get(operatorIdx - 1)은 따로 선언해 준 후 넘겨주면 좀 더 가독성 좋게 읽힐 것 같아요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int operatorIdx = parsedExpression.indexOf(operator);
String operand1 = parsedExpression.get(operatorIdx - 1);
String operand2 = parsedExpression.get(operatorIdx + 1);
return new Double[] {Double.parseDouble(operand1), Double.parseDouble(operand2)};

이렇게 수정하는 방향은 어떨까요?

return new Double[] {operand1, operand2};
}

private List<String> extractOperators() {
expression.eliminateWhiteSpace();
List<String> parsed = expression.split("\\d+");
parsed.removeIf(String::isEmpty);
return parsed;
}

private Double calcFinalResult() {
return Double.parseDouble(parsedExpression.get(0));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parsedExpression의 상태에 따라서 해당 메소드가 결과가 다르고 심지어 에러도 발생할 수 있겠네요

}
}
62 changes: 62 additions & 0 deletions src/main/java/co/programmers/domain/CalculatorApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package co.programmers.domain;

import co.programmers.exception.ExceptionMessage;
import co.programmers.repository.Repository;
import co.programmers.view.CalculatorOutputView;
import co.programmers.view.InputView;
import co.programmers.view.OutputView;

public class CalculatorApp {

private final InputView inputView;
private final OutputView outputView;
private final Repository repository;

public CalculatorApp(InputView inputView, OutputView outputView, Repository repository) {
this.inputView = inputView;
this.outputView = outputView;
this.repository = repository;
}

public void run() {
UserMenu userMenu;
do {
CalculatorOutputView.printMenuChoiceGuide();
userMenu = UserMenu.get(inputView.inputUserMenu());
executeSelectedMenu(userMenu);
} while (userMenu != UserMenu.TERMINATE);
}

private void executeSelectedMenu(UserMenu userMenu) {
switch (userMenu) {
case INQUIRY:
inquiry();
break;
case CALCULATE:
calculate();
break;
case TERMINATE:
break;
default:
outputView.printMessage(ExceptionMessage.INVALID_INPUT);
break;
}
}

public void inquiry() {
outputView.printCalculationHistory(repository.read());
}

public void calculate() {
try {
CalculatorOutputView.printCalculationGuide();
Expression expression = inputView.inputExpression();
Calculation calculator = new Calculation(expression);
Double output = calculator.calculate();
outputView.printCalculationResult(output);
repository.save(expression.getExpression(), output);
} catch (ArithmeticException arithmeticException) {
System.out.println(arithmeticException.getMessage());
}
Comment on lines +51 to +60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArithmeticException이 일어 날 수 있는 곳만 try-catch로 잡는 편이 좋지 않을까요?

}
}
47 changes: 47 additions & 0 deletions src/main/java/co/programmers/domain/Expression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package co.programmers.domain;

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

import co.programmers.exception.ExceptionMessage;

public class Expression {

private static final Pattern EXPRESSION_FORMAT = Pattern.compile("(\\d+\\s[\\+\\-\\*\\/]\\s)+\\d+");
private static final String DELIMITER = " ";
private String expression;

public Expression(String expression) {
this.expression = expression;
if (expression == null || expression.isEmpty()) {
throw new IllegalArgumentException(ExceptionMessage.EMPTY_INPUT);
}
if (!validate()) {
throw new ArithmeticException(ExceptionMessage.INVALID_EXPRESSION);
}
}

private boolean validate() {

Matcher matcher = EXPRESSION_FORMAT.matcher(expression);
return matcher.matches();
}
Comment on lines +26 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private boolean validate() {
Matcher matcher = EXPRESSION_FORMAT.matcher(expression);
return matcher.matches();
}
private boolean isNotValidate() {
Matcher matcher = EXPRESSION_FORMAT.matcher(expression);
return !matcher.matches();
}

!를 쓰는 대신 메서드에 부정을 담으면 좀 더 명시적으로 읽힐 수 있어요!

Copy link
Author

@SW-H SW-H Jun 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

깃헙에 업로드 하면서 오류가 있었나봐요... 아래처럼 작성하였습니다! 혹시 !를 쓸일이 있으면 알려주신대로 부정문을 사용하겠습니다!

private boolean validate() {
  Matcher matcher = EXPRESSION_FORMAT.matcher(expression);
  return matcher.matches();
}


public List<String> split() {
return new ArrayList<>(List.of(expression.split(DELIMITER)));
}

public List<String> split(String delimiter) {
return new ArrayList<>(List.of(expression.split(delimiter)));
}

public void eliminateWhiteSpace() {
expression = expression.replaceAll("\\s", "");
}

public String getExpression() {
return expression;
}
}
51 changes: 51 additions & 0 deletions src/main/java/co/programmers/domain/Operator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package co.programmers.domain;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;

import co.programmers.exception.ExceptionMessage;

public enum Operator {
ADDITION("+", 2, (operand1, operand2) -> operand1 + operand2),
SUBTRACTION("-", 2, (operand1, operand2) -> operand1 - operand2),
MULTIPLICATION("*", 1, (operand1, operand2) -> operand1 * operand2),
DIVISION("/", 1, (operand1, operand2) -> {
if (operand2 == 0) {
throw new ArithmeticException(ExceptionMessage.DIVIDED_BY_ZERO);
}
return operand1 / operand2;
});

private final String symbol;
private final int priority;
private final BiFunction<Double, Double, Double> operation;

private Operator(String symbol, int priority, BiFunction<Double, Double, Double> operation) {
this.symbol = symbol;
this.priority = priority;
this.operation = operation;
}

public static Double calculate(String operator, Double operand1, Double operand2) {
return getSymbol(operator).operation.apply(operand1, operand2);
}
Comment on lines +31 to +33

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operator를 받아와야하나요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String으로 받은 연산자에 해당하는 함수를 실행하기 위해 위에처럼 사용했습니다..


public static Operator getSymbol(Object operator) {
return Arrays.stream(values())
.filter(o -> o.symbol.equals(operator))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.INVALID_INPUT));
}

public static void decideCalculationOrder(List<String> operators) {
Collections.sort(operators, (operator1, operator2) ->
Integer.compare(getSymbol(operator1).getPriority(), getSymbol(operator2).getPriority())
);
}
Comment on lines +42 to +46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public static void decideCalculationOrder(List<String> operators) {
Collections.sort(operators, (operator1, operator2) ->
Integer.compare(getSymbol(operator1).getPriority(), getSymbol(operator2).getPriority())
);
}
public static void decideCalculationOrder(List<String> operators) {
operators.sort(Comparator.comparingInt(Operator::getPriority));
}

메서드 레퍼런스를 사용하면 좀 더 가독성 있게 읽혀요


public int getPriority() {
return priority;
}
}
20 changes: 20 additions & 0 deletions src/main/java/co/programmers/domain/UserMenu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package co.programmers.domain;

import java.util.Arrays;

public enum UserMenu {
INQUIRY(1), CALCULATE(2), TERMINATE(3), ERROR(-9999);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TERMINATE와 ERROR를 분리하셨네요 👍


private final Integer menu;

private UserMenu(Integer value) {
this.menu = value;
}

public static UserMenu get(Integer input) {
return Arrays.stream(values())
.filter(menuNum -> menuNum.menu.equals(input))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메소드 레퍼런스를 사용해보시는 건 어떨까요?

.findFirst()
.orElse(ERROR);
}
}
12 changes: 12 additions & 0 deletions src/main/java/co/programmers/exception/ExceptionMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package co.programmers.exception;

public class ExceptionMessage {

public static final String DIVIDED_BY_ZERO = "0으로 나눌 수 없습니다.";
public static final String INVALID_INPUT = "잘못된 입력입니다";
public static final String INVALID_EXPRESSION = "잘못된 수식입니다";
public static final String EMPTY_INPUT = "입력이 비어있습니다";

private ExceptionMessage() {
}
}
18 changes: 18 additions & 0 deletions src/main/java/co/programmers/repository/CalculatorRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package co.programmers.repository;

import java.util.LinkedHashMap;
import java.util.Map;

public class CalculatorRepository implements Repository {
private static final Map<String, Double> storage = new LinkedHashMap<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 구조면 LinkedHashMap이 아니게 될 때는 저장 이력을 순서대로 저장하지 못하겠군요?


@Override
public void save(String expression, Double result) {
storage.put(expression, result);
}

@Override
public Map<String, Double> read() {
return storage;
}
}
22 changes: 22 additions & 0 deletions src/main/java/co/programmers/view/CalculatorInputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package co.programmers.view;

import java.util.Scanner;

import co.programmers.domain.Expression;

public class CalculatorInputView implements InputView {

private static final Scanner SCANNER = new Scanner(System.in);

@Override
public Integer inputUserMenu() {
Integer userInput = SCANNER.nextInt();
SCANNER.nextLine();
return userInput;
}

@Override
public Expression inputExpression() throws ArithmeticException {
return new Expression(SCANNER.nextLine());
}
}
36 changes: 36 additions & 0 deletions src/main/java/co/programmers/view/CalculatorOutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package co.programmers.view;

import java.util.Map;

public class CalculatorOutputView implements OutputView {
public static void printCalculationGuide() {
System.out.println("1 + 2 * 3와 같은 형식으로 계산하고자 하는 식을 입력하세요.");
System.out.print("> ");
}

public static void printMenuChoiceGuide() {
System.out.println("\n[다음 중 원하시는 항목을 숫자로 입력하세요]");
System.out.println("1. 조회");
System.out.println("2. 계산");
System.out.println("3. 종료");
System.out.print("> 선택 : ");
}

@Override
public void printCalculationResult(Double result) {
System.out.println(">> 결과 : " + result);
}

@Override
public void printMessage(String message) {
System.out.println(message);
}

@Override
public void printCalculationHistory(Map<String, Double> history) {
System.out.println(">> 계산 기록 조회");
for (Map.Entry<String, Double> oneExpression : history.entrySet()) {
System.out.println(oneExpression.getKey() + " = " + oneExpression.getValue());
}
}
}
10 changes: 10 additions & 0 deletions src/main/java/co/programmers/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package co.programmers.view;

import co.programmers.domain.Expression;

public interface InputView {

Integer inputUserMenu();

Expression inputExpression();
}
Loading