From 7b9cd09c2ef67006e824704d36019dd0606f0a32 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:35:37 +0900 Subject: [PATCH 01/19] chore: initial setting --- .gitattributes | 9 + .gitignore | 182 +++++++++++++ app/build.gradle | 35 +++ .../test/java/com/programmers/AppTest.java | 14 + gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 244 ++++++++++++++++++ gradlew.bat | 92 +++++++ settings.gradle | 11 + 8 files changed, 593 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 app/build.gradle create mode 100644 app/src/test/java/com/programmers/AppTest.java create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..097f9f98d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..75dbe68c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,182 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +# Created by https://www.toptal.com/developers/gitignore/api/macos,java,intellij +# Edit at https://www.toptal.com/developers/gitignore?templates=macos,java,intellij + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +# End of https://www.toptal.com/developers/gitignore/api/macos,java,intellij \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 000000000..df7b83fc3 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,35 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java application project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + +plugins { + // Apply the application plugin to add support for building a CLI application in Java. + id 'application' +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + // Use JUnit Jupiter for testing. + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' + + // This dependency is used by the application. + implementation 'com.google.guava:guava:31.1-jre' +} + +application { + // Define the main class for the application. + mainClass = 'com.programmers.App' +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/app/src/test/java/com/programmers/AppTest.java b/app/src/test/java/com/programmers/AppTest.java new file mode 100644 index 000000000..979944084 --- /dev/null +++ b/app/src/test/java/com/programmers/AppTest.java @@ -0,0 +1,14 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package com.programmers; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class AppTest { + @Test void appHasAGreeting() { + App classUnderTest = new App(); + // assertNotNull(classUnderTest.getGreeting(), "app should have a greeting"); + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..bdc9a83b1 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..79a61d421 --- /dev/null +++ b/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..93e3f59f1 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..97ca62449 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/8.0.2/userguide/multi_project_builds.html + */ + +rootProject.name = 'java-calculator' +include('app') From 289bad691f102bc38131678cf094e7d44d220119 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:37:00 +0900 Subject: [PATCH 02/19] =?UTF-8?q?feat:=20=EA=B3=84=EC=82=B0=EA=B8=B0=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EB=8F=99=EC=9E=91=20=EA=B3=BC=EC=A0=95=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/programmers/App.java | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 app/src/main/java/com/programmers/App.java diff --git a/app/src/main/java/com/programmers/App.java b/app/src/main/java/com/programmers/App.java new file mode 100644 index 000000000..ffb0437d2 --- /dev/null +++ b/app/src/main/java/com/programmers/App.java @@ -0,0 +1,60 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package com.programmers; + +import com.programmers.calculator.Calculator; +import com.programmers.io.IOConsole; +import com.programmers.memory.HistoryMemory; + +import java.io.*; + +public class App { + // 계산기 옵션 + private static final String SHOW_HISTORY_OPTION = "1"; + private static final String CALCULATE_OPTION = "2"; + + private static final IOConsole ioConsole = new IOConsole(); // 입출력 콘솔 + private static final Calculator calculator = new Calculator(); // 숫자 계산을 담당하는 부분 + private static final HistoryMemory historyMemory = new HistoryMemory(); // 계산 결과 메모리 + + public static void main(String[] args) throws IOException { + while (true) { + ioConsole.printMenu(); + + String option = ioConsole.getOption(); + + switch (option) { + case SHOW_HISTORY_OPTION: { // 1. 조회 + String history = historyMemory.getHistory(); + ioConsole.print(history); + break; + } + case CALCULATE_OPTION: { // 2. 계산 + String inputExpression = ioConsole.getInput(); + + if (!ioConsole.validateInputExpression(inputExpression)) { + break; + } + + try { + String convertedExpression = calculator.convert(inputExpression); + + double answer = calculator.calculate(convertedExpression); + + historyMemory.saveHistory(inputExpression, answer); + + ioConsole.printAnswer(answer); + } catch (Exception e) { + ioConsole.print(e.getMessage() + "\n"); + } + break; + } + default: { // 잘못된 입력을 처리 + ioConsole.handleWrongInput(); + return; + } + } + } + } +} \ No newline at end of file From c65c229879c51acf7db3f0a65b79d796fcf40fe3 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:47:59 +0900 Subject: [PATCH 03/19] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/programmers/io/IOConsole.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 app/src/main/java/com/programmers/io/IOConsole.java diff --git a/app/src/main/java/com/programmers/io/IOConsole.java b/app/src/main/java/com/programmers/io/IOConsole.java new file mode 100644 index 000000000..7f9329d03 --- /dev/null +++ b/app/src/main/java/com/programmers/io/IOConsole.java @@ -0,0 +1,19 @@ +package com.programmers.io; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class IOConsole { + // 사용자에게 보여주는 메뉴 메시지 + private static final String HISTORY_OPTION_MESSAGE = "1. 조회"; + private static final String CALCULATE_OPTION_MESSAGE = "2. 계산"; + /** + * 계산기의 선택 메뉴를 출력하는 함수 + */ + public void printMenu() { + System.out.println(HISTORY_OPTION_MESSAGE); + System.out.println(CALCULATE_OPTION_MESSAGE); + System.out.println(); + } +} \ No newline at end of file From 146fb4e5c5bc1951d02bd4c9ae9599df84821dfd Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:50:31 +0900 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EC=9E=85=EB=A0=A5=EC=9D=84=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/programmers/io/IOConsole.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/java/com/programmers/io/IOConsole.java b/app/src/main/java/com/programmers/io/IOConsole.java index 7f9329d03..a2b29781e 100644 --- a/app/src/main/java/com/programmers/io/IOConsole.java +++ b/app/src/main/java/com/programmers/io/IOConsole.java @@ -8,6 +8,7 @@ public class IOConsole { // 사용자에게 보여주는 메뉴 메시지 private static final String HISTORY_OPTION_MESSAGE = "1. 조회"; private static final String CALCULATE_OPTION_MESSAGE = "2. 계산"; + private static final String USER_CHOICE_MESSAGE = "선택 : "; /** * 계산기의 선택 메뉴를 출력하는 함수 */ @@ -16,4 +17,18 @@ public void printMenu() { System.out.println(CALCULATE_OPTION_MESSAGE); System.out.println(); } + + /** + * 사용자의 선택을 받아 반환하는 함수 + * + * @return + * @throws IOException + */ + public String getOption() throws IOException { + System.out.print(USER_CHOICE_MESSAGE); + String expression = br.readLine(); + System.out.println(); + + return expression; + } } \ No newline at end of file From 69923ac924827fdbd72d91d839c196d850d1d512 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:51:46 +0900 Subject: [PATCH 05/19] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EC=9E=85=EB=A0=A5=EC=9D=84=20=EB=B0=9B=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/programmers/io/IOConsole.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/java/com/programmers/io/IOConsole.java b/app/src/main/java/com/programmers/io/IOConsole.java index a2b29781e..e51c2f37d 100644 --- a/app/src/main/java/com/programmers/io/IOConsole.java +++ b/app/src/main/java/com/programmers/io/IOConsole.java @@ -9,6 +9,8 @@ public class IOConsole { private static final String HISTORY_OPTION_MESSAGE = "1. 조회"; private static final String CALCULATE_OPTION_MESSAGE = "2. 계산"; private static final String USER_CHOICE_MESSAGE = "선택 : "; + private static final BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + /** * 계산기의 선택 메뉴를 출력하는 함수 */ @@ -31,4 +33,14 @@ public String getOption() throws IOException { return expression; } + + /** + * 사용자의 입력을 받아 반환하는 함수 + * + * @return 사용자가 입력한 문자열 + * @throws IOException + */ + public String getInput() throws IOException { + return br.readLine(); + } } \ No newline at end of file From 42c01e7f73db6b130045aeb6f5c2c47aa48c3cc3 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:53:20 +0900 Subject: [PATCH 06/19] =?UTF-8?q?feat:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EC=9D=84=20=EC=B2=98=EB=A6=AC=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/programmers/io/IOConsole.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/java/com/programmers/io/IOConsole.java b/app/src/main/java/com/programmers/io/IOConsole.java index e51c2f37d..68e9be388 100644 --- a/app/src/main/java/com/programmers/io/IOConsole.java +++ b/app/src/main/java/com/programmers/io/IOConsole.java @@ -9,6 +9,9 @@ public class IOConsole { private static final String HISTORY_OPTION_MESSAGE = "1. 조회"; private static final String CALCULATE_OPTION_MESSAGE = "2. 계산"; private static final String USER_CHOICE_MESSAGE = "선택 : "; + // 오류 발생 메시지 + private static final String WRONG_OPTION_MESSAGE = "계산기에서 지원하지 않는 옵션입니다."; + private static final String SHUTDOWN_MESSAGE = "계산기를 종료합니다."; private static final BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); /** @@ -43,4 +46,15 @@ public String getOption() throws IOException { public String getInput() throws IOException { return br.readLine(); } + + /** + * 잘못된 선택 입력이 들어왔을 때 처리하는 메소드 + * + * @throws IOException + */ + public void handleWrongInput() throws IOException { + System.out.println(WRONG_OPTION_MESSAGE); + System.out.println(SHUTDOWN_MESSAGE); + br.close(); + } } \ No newline at end of file From 7786b09c10b622581ac1a0f38b3252f549748a89 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:53:57 +0900 Subject: [PATCH 07/19] =?UTF-8?q?feat:=20=EA=B3=84=EC=82=B0=EC=9D=98=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EC=B6=9C=EB=A0=A5=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/programmers/io/IOConsole.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/java/com/programmers/io/IOConsole.java b/app/src/main/java/com/programmers/io/IOConsole.java index 68e9be388..484aa69d9 100644 --- a/app/src/main/java/com/programmers/io/IOConsole.java +++ b/app/src/main/java/com/programmers/io/IOConsole.java @@ -57,4 +57,15 @@ public void handleWrongInput() throws IOException { System.out.println(SHUTDOWN_MESSAGE); br.close(); } + + /** + * 계산의 결과를 출력하는 함수 + * + * @param answer 계산의 결과 + */ + public void printAnswer(double answer) { + System.out.printf("%.2f", answer); + System.out.println(); + System.out.println(); + } } \ No newline at end of file From 11744a77e5087cf87176abcfbe40e064a82318e7 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:54:18 +0900 Subject: [PATCH 08/19] =?UTF-8?q?feat:=20=EB=AC=B8=EC=9E=90=EC=97=B4?= =?UTF-8?q?=EC=9D=84=20=EC=B6=9C=EB=A0=A5=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/programmers/io/IOConsole.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/com/programmers/io/IOConsole.java b/app/src/main/java/com/programmers/io/IOConsole.java index 484aa69d9..b7cfc3d1a 100644 --- a/app/src/main/java/com/programmers/io/IOConsole.java +++ b/app/src/main/java/com/programmers/io/IOConsole.java @@ -68,4 +68,13 @@ public void printAnswer(double answer) { System.out.println(); System.out.println(); } + + /** + * 문자열을 출력하는 함수 + * + * @param s 문자열 + */ + public void print(String s) { + System.out.println(s); + } } \ No newline at end of file From 1c2a57469997afbaa521682a585a2fcdcf21cdc7 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:54:55 +0900 Subject: [PATCH 09/19] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=9E=85=EB=A0=A5=ED=95=9C=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=EC=8B=9D=EC=9D=84=20=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/programmers/io/IOConsole.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/src/main/java/com/programmers/io/IOConsole.java b/app/src/main/java/com/programmers/io/IOConsole.java index b7cfc3d1a..bc17e7b8d 100644 --- a/app/src/main/java/com/programmers/io/IOConsole.java +++ b/app/src/main/java/com/programmers/io/IOConsole.java @@ -9,9 +9,15 @@ public class IOConsole { private static final String HISTORY_OPTION_MESSAGE = "1. 조회"; private static final String CALCULATE_OPTION_MESSAGE = "2. 계산"; private static final String USER_CHOICE_MESSAGE = "선택 : "; + // 오류 발생 메시지 + private static final String WRONG_INPUT_MESSAGE = "계산기에서 지원하지 않는 입력입니다."; private static final String WRONG_OPTION_MESSAGE = "계산기에서 지원하지 않는 옵션입니다."; private static final String SHUTDOWN_MESSAGE = "계산기를 종료합니다."; + + // 사용자의 입력을 검증하는 regex. 사칙연산자와 숫자, 공백 문자열을 허용한다. + private static final String INPUT_REGEX = "^[\\d\\+\\-\\*/\\(\\)\\s]+$"; + private static final BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); /** @@ -77,4 +83,19 @@ public void printAnswer(double answer) { public void print(String s) { System.out.println(s); } + + /** + * 사용자의 input을 검증하는 메소드. + * + * @param inputExpression 사용자가 입력한 중위 표현식 + * @return true or false + */ + public boolean validateInputExpression(String inputExpression) { + if (!inputExpression.matches(INPUT_REGEX)) { + System.out.println(WRONG_INPUT_MESSAGE + "\n"); + return false; + } + + return true; + } } \ No newline at end of file From 350b19ac30e0300ec248ce507c15cb1586102329 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:56:51 +0900 Subject: [PATCH 10/19] =?UTF-8?q?feat:=20=EA=B3=84=EC=82=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=A0=A5=EC=9D=84=20=EC=A0=80=EC=9E=A5=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/programmers/memory/HistoryMemory.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 app/src/main/java/com/programmers/memory/HistoryMemory.java diff --git a/app/src/main/java/com/programmers/memory/HistoryMemory.java b/app/src/main/java/com/programmers/memory/HistoryMemory.java new file mode 100644 index 000000000..541346ea1 --- /dev/null +++ b/app/src/main/java/com/programmers/memory/HistoryMemory.java @@ -0,0 +1,13 @@ +package com.programmers.memory; + +import java.util.*; + +public class HistoryMemory { + private Map historyMemoryMap; + private int orderNumber; + + public HistoryMemory() { + this.historyMemoryMap = new HashMap<>(); + this.orderNumber = 0; + } +} \ No newline at end of file From df8112bad47a1b61f3f4acb8d70ca6048e3e5cf6 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:57:38 +0900 Subject: [PATCH 11/19] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=9E=85=EB=A0=A5=ED=95=9C=20=ED=91=9C=ED=98=84?= =?UTF-8?q?=EC=8B=9D=EC=9D=84=20=EC=A0=80=EC=9E=A5=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/programmers/memory/HistoryMemory.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/java/com/programmers/memory/HistoryMemory.java b/app/src/main/java/com/programmers/memory/HistoryMemory.java index 541346ea1..62257e1b0 100644 --- a/app/src/main/java/com/programmers/memory/HistoryMemory.java +++ b/app/src/main/java/com/programmers/memory/HistoryMemory.java @@ -10,4 +10,15 @@ public HistoryMemory() { this.historyMemoryMap = new HashMap<>(); this.orderNumber = 0; } + + /** + * 사용자가 입력한 중위 표현식을 저장하는 메소드 + * + * @param inputExpression 사용자가 입력한 중위 표현식 + * @param result 계산 결과 + */ + public void saveHistory(String inputExpression, double result) { + String formatExpression = String.format("%s = %.2f", inputExpression, result); + historyMemoryMap.put(++orderNumber, formatExpression); + } } \ No newline at end of file From 5a33909aaee561cfdc9c8b2e5eb68e2f50c2ba72 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 17:58:07 +0900 Subject: [PATCH 12/19] =?UTF-8?q?feat:=20=EA=B3=84=EC=82=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=A0=A5=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/programmers/memory/HistoryMemory.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/src/main/java/com/programmers/memory/HistoryMemory.java b/app/src/main/java/com/programmers/memory/HistoryMemory.java index 62257e1b0..e5cb81d30 100644 --- a/app/src/main/java/com/programmers/memory/HistoryMemory.java +++ b/app/src/main/java/com/programmers/memory/HistoryMemory.java @@ -21,4 +21,24 @@ public void saveHistory(String inputExpression, double result) { String formatExpression = String.format("%s = %.2f", inputExpression, result); historyMemoryMap.put(++orderNumber, formatExpression); } + + /** + * 계산 이력을 가져오는 메소드 + * + * @return 계산 이력 + */ + public String getHistory() { + StringBuilder sb = new StringBuilder(); + List keyList = new ArrayList<>(historyMemoryMap.keySet()); + + Collections.sort(keyList); + + for (int key : keyList) { + sb.append(historyMemoryMap.get(key) + "\n"); + } + + String history = sb.toString(); + + return history; + } } \ No newline at end of file From 5bb19931e641db0418407487999d3b330df31d01 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 18:05:36 +0900 Subject: [PATCH 13/19] =?UTF-8?q?feat:=20=EC=A4=91=EC=9C=84=20=ED=91=9C?= =?UTF-8?q?=ED=98=84=EC=8B=9D=EC=9D=84=20=EA=B2=80=EC=A6=9D=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../programmers/calculator/Calculator.java | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 app/src/main/java/com/programmers/calculator/Calculator.java diff --git a/app/src/main/java/com/programmers/calculator/Calculator.java b/app/src/main/java/com/programmers/calculator/Calculator.java new file mode 100644 index 000000000..a0dd471d7 --- /dev/null +++ b/app/src/main/java/com/programmers/calculator/Calculator.java @@ -0,0 +1,95 @@ +package com.programmers.calculator; + +import java.util.ArrayDeque; +import java.util.Deque; + +public class Calculator { + // 사칙 연산자 + private static final String ADD_OPERATOR = "+"; + private static final String SUBTRACTION_OPERATOR = "-"; + private static final String MULTIPLY_OPERATOR = "*"; + private static final String DIVIDE_OPERATOR = "/"; + + // 괄호 + private static final String OPEN_BRACKET = "("; + private static final String CLOSE_BRACKET = ")"; + + // 중위 표현식 검증 regex + private static final String EXPRESSION_REGEX = "(?<=[-+*/()])|(?=[-+*/()])"; // 정수, 괄호, 연산자들을 구분한다. + private static final String CONSECUTIVE_OPERATORS_REGEX = ".*[\\+\\-\\*/]{2,}.*"; // 반복되는 연산자를 검증한다. + + // 중위 표현식 오류 메시지 + private static final String WRONG_INFIX_EXPRESSION_MESSAGE = "중위 표현식이 유효하지 않습니다."; + /** + * 중위 표현식이 유효한지 검증하는 메소드 + * + * @param inputExpression 사용자에게 입력받은 중위 표현식 + * @return 검증이 완료된 표현식 + * @throws IllegalArgumentException + */ + private String validateInfixExpression(String inputExpression) throws IllegalArgumentException { + String blankRemovedExpression = inputExpression.replace(" ", ""); + + String[] expressionTokens = blankRemovedExpression.split(EXPRESSION_REGEX); + + validateBracket(expressionTokens); + + validateOperandAndOperatorNumber(expressionTokens); + + validateConsecutiveOperators(blankRemovedExpression); + + return blankRemovedExpression; + } + + /** + * 중위 표현식에서 괄호의 갯수를 검증하는 메소드 + * + * @param expressionTokens 표현식 조각으로 이루어진 토큰 배열 + * @throws IllegalArgumentException + */ + private void validateBracket(String[] expressionTokens) throws IllegalArgumentException { + Deque tokenStack = new ArrayDeque<>(); + + for (String token : expressionTokens) { + switch (token) { + case OPEN_BRACKET: { + tokenStack.push(token); + break; + } + case CLOSE_BRACKET: { + if (tokenStack.isEmpty()) { + throw new IllegalArgumentException(WRONG_INFIX_EXPRESSION_MESSAGE); + } + tokenStack.pop(); + } + } + } + + if (!tokenStack.isEmpty()) { + throw new IllegalArgumentException(WRONG_INFIX_EXPRESSION_MESSAGE); + } + } + + /** + * 연산자가 연속하는지 검증하는 메소드 + * + * @param expression 표현식 + * @throws IllegalArgumentException + */ + private void validateConsecutiveOperators(String expression) throws IllegalArgumentException { + if (expression.matches(CONSECUTIVE_OPERATORS_REGEX)) { + throw new IllegalArgumentException(WRONG_INFIX_EXPRESSION_MESSAGE); + } + } + + /** + * 연산자와 피연산자의 갯수 검증하는 메소드. + * + * @param tokens 표현식 토큰 + */ + private void validateOperandAndOperatorNumber(String[] tokens) { + if (tokens.length % 2 != 1) { // 중위 표현식의 토큰 갯수는 항상 홀수임을 이용함(피연산자의 개수 = 연산자의 갯수 + 1). + throw new IllegalArgumentException(WRONG_INFIX_EXPRESSION_MESSAGE); + } + } +} \ No newline at end of file From b20e83153cbec12d219267a53c5ae50ec0907056 Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 18:06:13 +0900 Subject: [PATCH 14/19] =?UTF-8?q?feat:=20=EC=A4=91=EC=9C=84=20=ED=91=9C?= =?UTF-8?q?=ED=98=84=EC=8B=9D=EC=9D=84=20=ED=9B=84=EC=9C=84=20=ED=91=9C?= =?UTF-8?q?=ED=98=84=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../programmers/calculator/Calculator.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/app/src/main/java/com/programmers/calculator/Calculator.java b/app/src/main/java/com/programmers/calculator/Calculator.java index a0dd471d7..9db7a4173 100644 --- a/app/src/main/java/com/programmers/calculator/Calculator.java +++ b/app/src/main/java/com/programmers/calculator/Calculator.java @@ -20,6 +20,59 @@ public class Calculator { // 중위 표현식 오류 메시지 private static final String WRONG_INFIX_EXPRESSION_MESSAGE = "중위 표현식이 유효하지 않습니다."; + + /** + * 중위 표현식을 후위 표현식으로 변환하는 메소드 + * + * @param inputExpression 사용자에게 입력받은 중위 표현식 + * @return postfixExpression 변환된 후위 표기식 + * @throws IllegalArgumentException + */ + public String convert(String inputExpression) throws IllegalArgumentException { + StringBuilder sb = new StringBuilder(); + Deque infixStack = new ArrayDeque<>(); + + String validExpression = validateInfixExpression(inputExpression); + String[] expressionTokens = validExpression.split(EXPRESSION_REGEX); + + for (String token : expressionTokens) { + switch (token) { + case ADD_OPERATOR: + case SUBTRACTION_OPERATOR: + case MULTIPLY_OPERATOR: + case DIVIDE_OPERATOR: { + while (!infixStack.isEmpty() && priority(infixStack.peek()) >= priority(token)) { + sb.append(infixStack.pop() + " "); + } + infixStack.push(token); + break; + } + case OPEN_BRACKET: { + infixStack.push(token); + break; + } + case CLOSE_BRACKET: { + while (!infixStack.isEmpty() && !infixStack.peek().equals(OPEN_BRACKET)) { + sb.append(infixStack.pop() + " "); + } + infixStack.pop(); + break; + } + default: { + sb.append(token + " "); + } + } + } + + while (!infixStack.isEmpty()) { + sb.append(infixStack.pop() + " "); + } + + sb.delete(sb.length() - 1, sb.length()); + String postfixExpression = sb.toString(); + + return postfixExpression; + } /** * 중위 표현식이 유효한지 검증하는 메소드 * From 64add595321377129445051e8145e5c7828da43a Mon Sep 17 00:00:00 2001 From: hi-june Date: Sun, 11 Jun 2023 18:06:48 +0900 Subject: [PATCH 15/19] =?UTF-8?q?feat:=20=ED=9B=84=EC=9C=84=20=ED=91=9C?= =?UTF-8?q?=EA=B8=B0=EC=8B=9D=EC=9D=84=20=EA=B8=B0=EB=B0=98=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EA=B3=84=EC=82=B0=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../programmers/calculator/Calculator.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/app/src/main/java/com/programmers/calculator/Calculator.java b/app/src/main/java/com/programmers/calculator/Calculator.java index 9db7a4173..0d0a11234 100644 --- a/app/src/main/java/com/programmers/calculator/Calculator.java +++ b/app/src/main/java/com/programmers/calculator/Calculator.java @@ -73,6 +73,76 @@ public String convert(String inputExpression) throws IllegalArgumentException { return postfixExpression; } + + /** + * 후위 표기식의 계산 결과를 반환하는 메소드 + * + * @param postfixExpression + * @return 연산의 결과 + * @throws ArithmeticException + */ + public double calculate(String postfixExpression) throws ArithmeticException { + Deque postfixStack = new ArrayDeque<>(); + + for (String postfixToken : postfixExpression.split(" ")) { + if (isOperator(postfixToken)) { + double y = Double.parseDouble(postfixStack.pop()); + double x = Double.parseDouble(postfixStack.pop()); + + switch (postfixToken) { + case ADD_OPERATOR: { + postfixStack.push(String.format("%.2f", x + y)); + break; + } + case SUBTRACTION_OPERATOR: { + postfixStack.push(String.format("%.2f", x - y)); + break; + } + case MULTIPLY_OPERATOR: { + postfixStack.push(String.format("%.2f", x * y)); + break; + } + case DIVIDE_OPERATOR: { + postfixStack.push(String.format("%.2f", x / y)); + } + } + } else { + postfixStack.push(postfixToken); + } + } + + return Double.parseDouble(postfixStack.pop()); + } + + /** + * 사칙연산자인지 확인하는 메소드 + * + * @param token + * @return true or false + */ + private boolean isOperator(String token) { + return token.equals(ADD_OPERATOR) || token.equals(SUBTRACTION_OPERATOR) || token.equals(MULTIPLY_OPERATOR) || token.equals(DIVIDE_OPERATOR); + } + + /** + * 연산자의 우선순위를 판별하는 메소드 + * + * @param operator 연산자 + * @return 우선순위 + */ + private int priority(String operator) { + if (operator.equals(OPEN_BRACKET) || operator.equals(CLOSE_BRACKET)) { + return 0; + } + if (operator.equals(ADD_OPERATOR) || operator.equals(SUBTRACTION_OPERATOR)) { + return 1; + } + if (operator.equals(MULTIPLY_OPERATOR) || operator.equals(DIVIDE_OPERATOR)) { + return 2; + } + return -1; + } + /** * 중위 표현식이 유효한지 검증하는 메소드 * From ee9ab37e6f2f82498c1340be09d9e389cc6bf2c6 Mon Sep 17 00:00:00 2001 From: hi-june Date: Tue, 13 Jun 2023 02:25:56 +0900 Subject: [PATCH 16/19] =?UTF-8?q?refactor:=20=EA=B3=84=EC=82=B0=EA=B8=B0?= =?UTF-8?q?=20=EB=8F=99=EC=9E=91=EC=9D=84=20=EB=8B=B4=EB=8B=B9=ED=95=98?= =?UTF-8?q?=EB=8A=94=20controller=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/programmers/App.java | 53 ++-------------- .../contoller/CalculatorController.java | 63 +++++++++++++++++++ 2 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/com/programmers/contoller/CalculatorController.java diff --git a/app/src/main/java/com/programmers/App.java b/app/src/main/java/com/programmers/App.java index ffb0437d2..8528510f9 100644 --- a/app/src/main/java/com/programmers/App.java +++ b/app/src/main/java/com/programmers/App.java @@ -3,58 +3,13 @@ */ package com.programmers; -import com.programmers.calculator.Calculator; -import com.programmers.io.IOConsole; -import com.programmers.memory.HistoryMemory; +import com.programmers.contoller.CalculatorController; -import java.io.*; +import java.io.IOException; public class App { - // 계산기 옵션 - private static final String SHOW_HISTORY_OPTION = "1"; - private static final String CALCULATE_OPTION = "2"; - - private static final IOConsole ioConsole = new IOConsole(); // 입출력 콘솔 - private static final Calculator calculator = new Calculator(); // 숫자 계산을 담당하는 부분 - private static final HistoryMemory historyMemory = new HistoryMemory(); // 계산 결과 메모리 - public static void main(String[] args) throws IOException { - while (true) { - ioConsole.printMenu(); - - String option = ioConsole.getOption(); - - switch (option) { - case SHOW_HISTORY_OPTION: { // 1. 조회 - String history = historyMemory.getHistory(); - ioConsole.print(history); - break; - } - case CALCULATE_OPTION: { // 2. 계산 - String inputExpression = ioConsole.getInput(); - - if (!ioConsole.validateInputExpression(inputExpression)) { - break; - } - - try { - String convertedExpression = calculator.convert(inputExpression); - - double answer = calculator.calculate(convertedExpression); - - historyMemory.saveHistory(inputExpression, answer); - - ioConsole.printAnswer(answer); - } catch (Exception e) { - ioConsole.print(e.getMessage() + "\n"); - } - break; - } - default: { // 잘못된 입력을 처리 - ioConsole.handleWrongInput(); - return; - } - } - } + CalculatorController calculatorController = new CalculatorController(); + calculatorController.run(); } } \ No newline at end of file diff --git a/app/src/main/java/com/programmers/contoller/CalculatorController.java b/app/src/main/java/com/programmers/contoller/CalculatorController.java new file mode 100644 index 000000000..ca95da1f8 --- /dev/null +++ b/app/src/main/java/com/programmers/contoller/CalculatorController.java @@ -0,0 +1,63 @@ +package com.programmers.contoller; + +import com.programmers.calculator.Calculator; +import com.programmers.io.IOConsole; +import com.programmers.memory.HistoryMemory; + +import java.io.IOException; + +public class CalculatorController { + // 계산기 옵션 + private static final String SHOW_HISTORY_OPTION = "1"; + private static final String CALCULATE_OPTION = "2"; + + private static IOConsole ioConsole; + private static Calculator calculator; + private static HistoryMemory historyMemory; + + public CalculatorController() { + ioConsole = new IOConsole(); // 입출력 콘솔 + calculator = new Calculator(); // 숫자 계산을 담당하는 부분 + historyMemory = new HistoryMemory(); // 계산 결과 메모리 + } + + public void run() throws IOException { + while (true) { + ioConsole.printMenu(); + + String option = ioConsole.getOption(); + + switch (option) { + case SHOW_HISTORY_OPTION: { // 1. 조회 + String history = historyMemory.getHistory(); + ioConsole.print(history); + break; + } + case CALCULATE_OPTION: { // 2. 계산 + String inputExpression = ioConsole.getInput(); + + if (!ioConsole.validateInputExpression(inputExpression)) { + break; + } + + try { + String convertedExpression = calculator.convert(inputExpression); + + double answer = calculator.calculate(convertedExpression); + + historyMemory.saveHistory(inputExpression, answer); + + ioConsole.printAnswer(answer); + } catch (Exception e) { + ioConsole.print(e.getMessage() + "\n"); + } + break; + } + default: { // 잘못된 입력을 처리 + ioConsole.handleWrongInput(); + return; + } + } + } + } +} From bfcfb870f68a961089175f03f27c1f19333e7300 Mon Sep 17 00:00:00 2001 From: hi-june Date: Tue, 13 Jun 2023 02:27:22 +0900 Subject: [PATCH 17/19] =?UTF-8?q?test:=20=EC=9E=85=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20test=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/programmers/io/IOConsoleTest.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 app/src/test/java/com/programmers/io/IOConsoleTest.java diff --git a/app/src/test/java/com/programmers/io/IOConsoleTest.java b/app/src/test/java/com/programmers/io/IOConsoleTest.java new file mode 100644 index 000000000..fd0daa8ec --- /dev/null +++ b/app/src/test/java/com/programmers/io/IOConsoleTest.java @@ -0,0 +1,65 @@ +package com.programmers.io; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class IOConsoleTest { + private static final String HISTORY_OPTION_MESSAGE = "1. 조회"; + private static final String CALCULATE_OPTION_MESSAGE = "2. 계산"; + + private static IOConsole ioConsole; + private static OutputStream out; + + @BeforeEach + void initAll() { + ioConsole = new IOConsole(); // 입출력 콘솔 + + // 표준 출력 설정 + out = new ByteArrayOutputStream(); + System.setOut(new PrintStream(out)); + } + + @Test + void printMenuTest() { + ioConsole.printMenu(); + + assertEquals(HISTORY_OPTION_MESSAGE + "\n" + CALCULATE_OPTION_MESSAGE + "\n\n", out.toString()); + } + + @Test + void printAnswerTest() { + double answer = 3.141592; + + ioConsole.printAnswer(answer); + + assertEquals("3.14\n\n", out.toString()); + } + + @Test + void validateInputExpressionTest() { + String expression1 = "1 +2"; + String expression2 = "1 * 2"; + String expression3 = "1 $2"; + String expression4 = " 12 - 1"; + String expression5 = "1 &2"; + String expression6 = "1 /2"; + + boolean result1 = ioConsole.validateInputExpression(expression1); + boolean result2 = ioConsole.validateInputExpression(expression2); + boolean result3 = ioConsole.validateInputExpression(expression3); + boolean result4 = ioConsole.validateInputExpression(expression4); + boolean result5 = ioConsole.validateInputExpression(expression5); + boolean result6 = ioConsole.validateInputExpression(expression6); + + assertEquals(true, result1); + assertEquals(true, result2); + assertEquals(false, result3); + assertEquals(true, result4); + assertEquals(false, result5); + assertEquals(true, result6); + } +} From 71ebf728acabd34f596474c6cf37fe9404f47e0c Mon Sep 17 00:00:00 2001 From: hi-june Date: Tue, 13 Jun 2023 02:27:33 +0900 Subject: [PATCH 18/19] =?UTF-8?q?test:=20=EB=A9=94=EB=AA=A8=EB=A6=AC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20test=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../programmers/memory/HistoryMemoryTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/src/test/java/com/programmers/memory/HistoryMemoryTest.java diff --git a/app/src/test/java/com/programmers/memory/HistoryMemoryTest.java b/app/src/test/java/com/programmers/memory/HistoryMemoryTest.java new file mode 100644 index 000000000..b4bfd30e3 --- /dev/null +++ b/app/src/test/java/com/programmers/memory/HistoryMemoryTest.java @@ -0,0 +1,29 @@ +package com.programmers.memory; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class HistoryMemoryTest { + static final HistoryMemory historyMemory = new HistoryMemory(); // 계산 결과 메모리 + + @Test + void memoryTest() { + String inputExpression1 = "1 + 2"; + String inputExpression2 = "1 + 2 * 3"; + String inputExpression3 = "3 - 2 * 2"; + + historyMemory.saveHistory(inputExpression1, 3); + historyMemory.saveHistory(inputExpression2, 7); + historyMemory.saveHistory(inputExpression3, -1); + + + String history = historyMemory.getHistory(); + + String expectedHistory1 = inputExpression1 + " = 3.00\n"; + String expectedHistory2 = inputExpression2 + " = 7.00\n"; + String expectedHistory3 = inputExpression3 + " = -1.00\n"; + + assertEquals(expectedHistory1 + expectedHistory2 + expectedHistory3, history); + } +} From c3ccd020a9b2cc417b0d21f0fb9f3e2d8eb421e9 Mon Sep 17 00:00:00 2001 From: hi-june Date: Tue, 13 Jun 2023 02:28:03 +0900 Subject: [PATCH 19/19] =?UTF-8?q?test:=20calculator=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20test=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calculator/CalculatorTest.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 app/src/test/java/com/programmers/calculator/CalculatorTest.java diff --git a/app/src/test/java/com/programmers/calculator/CalculatorTest.java b/app/src/test/java/com/programmers/calculator/CalculatorTest.java new file mode 100644 index 000000000..9dbd9a421 --- /dev/null +++ b/app/src/test/java/com/programmers/calculator/CalculatorTest.java @@ -0,0 +1,86 @@ +package com.programmers.calculator; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CalculatorTest { + static Calculator calculator; + + @BeforeEach + void initAll() { + calculator = new Calculator(); + } + + @Test + void convertValidExpressionTest() { + String inputExpression1 = "1 + 2"; + String inputExpression2 = "1 + 2 * 3"; + String inputExpression3 = "3 - 2 * 2"; + String inputExpression4 = "2 * (30 - 2) + 2"; + + String convertedExpression1 = calculator.convert(inputExpression1); + String convertedExpression2 = calculator.convert(inputExpression2); + String convertedExpression3 = calculator.convert(inputExpression3); + String convertedExpression4 = calculator.convert(inputExpression4); + + String postfixExpression1 = "1 2 +"; + String postfixExpression2 = "1 2 3 * +"; + String postfixExpression3 = "3 2 2 * -"; + String postfixExpression4 = "2 30 2 - * 2 +"; + + assertEquals(postfixExpression1, convertedExpression1); + assertEquals(postfixExpression2, convertedExpression2); + assertEquals(postfixExpression3, convertedExpression3); + assertEquals(postfixExpression4, convertedExpression4); + } + + @Test + void convertInValidExpressionTest() { + String inputExpression1 = "2 * ((30 - 2) + 2"; // 괄호의 갯수 검증 + String inputExpression2 = "2 * )30 - 2( + 2"; // 괄호 순서 검증 + String inputExpression3 = "3 -+ 2 * 2"; // 연산자 연속성 검증 + String inputExpression4 = "2 * 30 - 2 +"; // 연산자, 피연산자 갯수 검증 + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + calculator.convert(inputExpression1); + }); + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + calculator.convert(inputExpression2); + }); + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + calculator.convert(inputExpression3); + }); + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + calculator.convert(inputExpression4); + }); + } + + @Test + void calculateTest() { + String postfixExpression1 = "1 2 +"; + String postfixExpression2 = "1 2 3 * +"; + String postfixExpression3 = "3 2 2 * -"; + String postfixExpression4 = "2 30 2 - * 2 +"; + + double result1 = calculator.calculate(postfixExpression1); + double result2 = calculator.calculate(postfixExpression2); + double result3 = calculator.calculate(postfixExpression3); + double result4 = calculator.calculate(postfixExpression4); + + double answer1 = 1 + 2; + double answer2 = 1 + (2 * 3); + double answer3 = 3 - (2 * 2); + double answer4 = 2 * (30 - 2) + 2; + + assertEquals(answer1, result1); + assertEquals(answer2, result2); + assertEquals(answer3, result3); + assertEquals(answer4, result4); + } +}