연산자(Operator)
연산자란, 하나의 값 또는 여러 개의 값을 피연산자로 하여 새로운 값을 만들어내는 기호를 의미합니다. 자바에서 연산자의 종류는 많습니다. 하지만, 모든 연산자를 지금 한 번에 배울 필요는 없습니다.
이번 챕터에서는 자바에서 자주 사용하는 연산자를 중심으로 하여 연산자에 대해 학습합니다.
학습 목표
- 산술 연산자를 활용할 수 있다.
- 증감 연산자를 활용할 수 있다.
- 복합 대입 연산자를 활용할 수 있다.
- 비교 연산자를 활용할 수 있다.
- 연산자 우선순위를 이해한다.
산술 연산자
산술 연산자는 사칙연산에 사용되는 연산자(+, -, *, /)와 나머지 연산자(%)를 포함합니다.
산술 연산자의 동작은 우리가 알고 있는 일반적인 수학 연산과 거의 동일합니다.
int num1 = 1;
int num2 = num1 + 2; // num2 -> 3
int num3 = num2 - 1; // num3 -> 2
int num4 = num2 * 3; // num4 -> 9
int num5 = num4 / 2; // num5 -> 4
int num6 = num5 % 3; // num6 -> 1
나눗셈 연산자와 나머지 연산자를 사용할 때는 우항에 0이 위치할 수 없습니다. 즉, 어떤 수를 0으로 나눌 수 없습니다.
int num1 = 9 / 0; // 에러
double num2 = 9.0 / 0; // Infinity
int num3 = 9 % 0; // 에러
double num4 = 9.0 % 0; // 에러
나눗셈 연산자를 사용할 때, 좌항과 우항이 모두 int형이면 그 결과도 int형이기 때문에 소수점 이하의 값은 버려지고, 오직 몫만 결과로 반환됩니다.
반면, 좌항이나 우항 중 하나라도 실수 타입의 값이 존재한다면, 실수 타입이 아닌 값도 실수 타입으로 자동으로 형변환되어 계산되며, 결과적으로 실수 간의 나눗셈 연산이 되어 소수점이 버려지지 않은 온전한 값이 결과값으로 반환됩니다.
double num1 = 9 / 2; // 4.0
int num2 = 9 / 2; // 4
double num3 = 9.0 / 2; // 4.5
증감연산자
코드를 작성하다 보면, 어떤 정수 타입 변수가 가진 값을 증가시키거나 감소시켜야 하는 경우가 있습니다. 예를 들어, 1이라는 값을 할당받아 저장하고 있는 변수 num1의 값을 1 증가시켜야 한다면, 그리고 1이라는 값을 할당받아 저장하고 있는 변수 num2의 값을 1 감소시켜야 한다면 아래와 같은 코드를 작성할 수 있을 것입니다.
num1 = num1 + 1; // num1의 값은 2가 됩니다.
num2 = num2 - 1; // num2의 값은 0이 됩니다.
위의 코드는 아래와 같이 줄여서 작성할 수 있습니다.
num1++; // num1 = num1 + 1;과 같습니다.
++num1; // num1 = num1 + 1;과 같습니다.
num2--; // num2 = num2 - 1;과 같습니다.
--num2; // num2 = num2 - 1;과 같습니다.
이처럼 변수의 값을 1씩 증가시키거나 감소시키는 연산자를 증감 연산자라고 합니다. 주의해야 할 점은 증감 연산자의 연산 순서가 증감 연산자의 위치에 따라 달라질 수 있다는 점입니다. 증감 연산자가 피연산자보다 앞에 있으면 전위형 증감 연산자, 뒤에 있으면 후위형 증감 연산자라고 합니다.
- 전위형 증감 연산자 : 증감 연산을 먼저 수행한 후, 그 결과값을 적용합니다.
- 후위형 증감 연산자 : 기존의 값을 먼저 적용하고 그다음에 증감 연산을 수행합니다.
아래 예제를 통해 이해해 봅시다.
int num1 = 1;
int prefix = ++num1;
int num2 = 1;
int postfix = num2++;
위의 코드가 실행되고 난 후 num1, num2, prefix, postfix의 값은 어떻게 되어 있을까요?
- num1 & prefix : num1에 전위형 증감연산자 ++를 사용했으므로, num1의 값이 1만큼 먼저 증가한 다음 그 결과값이 prefix에 할당됩니다.
- 따라서 num1의 값은 2, prefix의 값도 2가 됩니다.
- num2 & postfix : num2에 후위형 증감연산자 ++를 사용했으므로, num2의 값이 먼저 postfix에 할당된 다음에 num2의 값이 1 증가합니다.
- 따라서 num2의 값은 2, postfix의 값은 1이 됩니다.
복합 대입 연산자
앞서 변수를 학습하면서 대입 연산자를 먼저 학습했었습니다. 대입 연산자는 사칙 연산자와 함께 조합하여 복합 대입 연산자로 활용할 수 있습니다.
증감 연산자에서 사용한 예시를 그대로 가져오겠습니다.
num1 = num1 + 1;
num2 = num2 - 1;
위 예제와 같이 1씩 증가시키거나 감소시키고자 하는 상황에서는 증감 연산자를 사용할 수 있다고 배웠습니다. 그러나, 아래와 같이 1이 아니라 2 혹은 3씩 증가시키거나 감소시키고 싶을 때는 증감 연산자를 사용할 수 없습니다.
num1 = num1 + 2;
num2 = num2 - 2;
이러한 경우에 복합 대입 연산자를 사용하여 연산을 간략하게 표현할 수 있습니다. 복합 대입 연산자를 사용하면 바로 위의 예제를 아래와 같이 간략하게 표현할 수 있습니다.
num1 += 2;
num2 -= 2;
모든 사칙 연산자 및 나머지 연산자와 대입 연산자를 결합해서 복합 대입 연산자로 사용할 수 있습니다.
// num1~5의 값을 모두 10이라고 한다면,
num1 += 3; // num1 -> 13
num2 -= 3; // num2 -> 7
num3 *= 3; // num3 -> 30
num4 /= 3; // num4 -> 3
num5 %= 3; // num5 -> 1
비교 연산자
비교 연산자는 boolean 타입으로 평가될 수 있는 조건식에 사용되며, 크게 대소 비교(>, <, <=, >=) 연산자와 등가 비교(==, !=) 연산자로 분류됩니다.
대소 비교 연산자
대소 비교 연산자는 boolean을 제외한 나머지 기본 타입에 모두 사용할 수 있습니다.
비교 연산자를 사용할 때 주의할 점은, 이항 비교만 가능하다는 것입니다. 즉 “x가 1보다 크고 5보다 작다”를 표현할 때, 1 < x < 5가 아니라, 1 < x && x < 5와 같이 논리 연산자를 사용해 작성해주어야 합니다. 논리 연산자에 대해서는 다음 챕터에서 살펴보겠습니다.
등가 비교 연산자
등가 비교 연산자는 모든 타입에 사용할 수 있는 연산자로, 두 항의 값이 동등한 지의 여부를 판단할 때 사용합니다. 다만, 기본 타입과 참조 타입 간에는 등가 비교 연산자를 사용할 수 없습니다. 즉, 기본 타입의 값끼리, 혹은 참조 타입의 값끼리만 등가 비교 연산자를 사용할 수 있습니다.
등가 비교 연산자는 값이 동등하면 true를, 그렇지 않으면 false를 반환합니다.
논리 연산자
논리 연산자는 AND 연산자 &&, OR 연산자 ||, NOT 연산자 !를 포함하며, 공통으로 boolean 타입을 반환합니다.
논리 연산자의 연산 결과는 다음과 같습니다.
- AND 연산자(&&): 피연산자 두 개가 모두 true일 때 true를 반환하며, 피연산자 두 개 중 하나라도 true가 아니면 false를 반환합니다.
- OR 연산자(||): 피연산자 두 개 중 단 하나라도 true면 true를 반환하며, 피연산자가 모두 false인 경우에만 false를 반환합니다.
NOT 연산자(!)는 true와 false를 반대로 바꾸는 연산자입니다.
// 아래의 비교 연산 결과는 모두 true입니다.
!true == false
!false == true
!!true == true
연산자 우선순위
연산자에는 우선순위가 있습니다.
주요 연산자 우선순위를 표로 나타내면 다음과 같습니다.
다음의 예시를 통해서 좀 더 자세히 알아보도록 하겠습니다.
// 참 또는 거짓) ? 참일 때 결과 : 거짓일 때 결과
int num = (1 + 2 == 3 && 4 + 1 * 2 == 6) ? 3 + 4 : 5 + 6;
System.out.println(num);
1) *곱셈 연산자가 수행됩니다.
int num = (1 + 2 == 3 && 4 + 2 == 6) ? 3 + 4 : 5 + 6;
2) + 덧셈 연산자가 수행됩니다.
int num = (3 == 3 && 6 == 6) ? 7 : 11;
3) == 등가 연산자가 수행됩니다.
int num = (true && true) ? 7 : 11;
4) && AND 연산자가 수행됩니다.
int num = true ? 7 : 11;
5) ?: 조건 연산자가 수행됩니다.
int num = 7;
6) = 대입 연산자가 수행되어 7이 출력됩니다.
연산자 우선순위를 암기할 필요는 없습니다. 연산자의 우선순위는 수학 규칙과 비슷합니다. 괄호를 먼저 연산하고, 곱셈, 나눗셈이 덧셈, 뺄셈보다 먼저 연산됩니다.
자주 사용하는 연산은 코드를 작성하면서 자연스럽게 익힐 수 있습니다. 모든 것을 한 번에 외우려고 하지 마세요. 필요하면 그때그때 찾아보고, 적용하면서 익숙해지는 것이 현명한 방법입니다.
콘솔 입출력(I/O)
이번 챕터에서는 문자열을 출력하고 입력받을 수 있는 메서드들에 대해서 학습합니다.
학습 목표
- 다음의 콘솔 출력 메서드들의 차이를 이해하고, 이를 활용할 수 있다.
- System.out.print()
- System.out.println()
- System.out.printf()
- Scanner를 활용하여 문자열 데이터를 입력받을 수 있다.
콘솔 출력
프로그래밍하면서 변수에 담긴 값을 확인해야 할 필요가 있을 때, 콘솔에 출력할 수 있습니다. 콘솔에 값을 출력하기 위해서는 System.out.print(), System.out.println(), System.out.printf() 메서드를 활용합니다.
출력하기 : System.out.print()
System.out.print() 메서드는 소괄호 안의 내용을 단순히 출력하기만 하고, 줄 바꿈을 하지 않습니다.
다음 코드의 출력 결과를 직접 확인해 보세요.
System.out.print("Hello JAVA");
System.out.print("Kim" + "Coding");
System.out.print(3+6);
System.out.print(2022 + "year");
System.out.print(”Hello Java”)를 실행하면 소괄호 안의 “Hello Java”가 출력되고 커서는 출력된 “Hello Java” 뒤로 이동합니다.
출력하고 줄 바꾸기 : System.out.println()
System.out.println() 메서드는 소괄호 안의 내용을 콘솔에 출력하고 줄 바꿈을 합니다. print 뒤에 붙은 ln은 line을 의미합니다.
형식대로 출력하기 System.out.printf()
System.out.printf()는 지시자(specifier, 형식 지정자)를 이용해 변수의 값을 여러 형식으로 출력해 주는 메서드입니다. 지시자는 이름 그대로 값을 어떤 형식으로 출력할 것인지를 지정하기 위해 사용합니다. f는 formatted의 약자입니다.
지시자는 실제로 출력되는 값이 아니며, 값을 변환해 자신의 위치에 출력해 주는 기능을 합니다. 자바에서 사용할 수 있는 지시자는 아래와 같습니다. 다시 한번 강조하지만, 아래 지시자들을 외우려고 하지 마세요! 만약 여러분들이 학습을 진행하시다가 문자열을 출력해야 하는 경우가 발생한다면, 문자열을 출력할 수 있는 지시자를 다시 찾아보고, 찾아본 것을 적용해 보면서 익숙해지면 됩니다.
다음 코드를 콘솔에 출력해보세요.
System.out.printf("%s%n", "Hello JAVA"); // 줄 바꿈이 됩니다.
System.out.printf("%s%n", "Kim" + "Coding");
System.out.printf("%d%n", 3+6);
System.out.printf("지금은 %s입니다", 2022 + "year"); // 자동 타입 변환이 일어납니다.
System.out.printf("나는 %c%s입니다", '김', "코딩"); //여러 개의 인자를 넣을 수 있습니다.
콘솔 입력
‘컴퓨터의 이해’에서 컴퓨터는 데이터를 입력받아 어떤 처리를 수행하고 그것을 저장하거나 출력하는 전자 장치라고 했습니다. 방금 우리는 데이터를 출력하는 방법을 배웠습니다. 이제는 데이터를 입력받는 방법을 학습해 보겠습니다.
데이터를 입력받는 코드는 아래와 같습니다.
import java.util.Scanner; // Scanner 클래스를 가져옵니다.
Scanner scanner = new Scanner(System.in); // Scanner 클래스의 인스턴스를 생성합니다.
String inputValue = scanner.nextLine(); // 입력한 내용이 inputValue에 저장됩니다.
System.out.println(inputValue); // 입력한 문자열이 출력됩니다.
이제 위의 코드를 간략하게 설명하겠습니다. 자바를 처음 배우는 입장에서 아래의 설명이 쉽게 이해되지 않을 수 있습니다. 만약 이해가 쉽지 않다면, 지금은 위의 예시 코드를 ‘자바에서 데이터를 입력받을 때 사용하는 ‘템플릿’ 정도로만 생각하고 넘어가셔도 좋습니다. 추후, 클래스를 배우고 나면 아래의 설명이 쉽게 읽힐 것이니 걱정하지 않아도 됩니다.
- import java.util.Scanner;
- 데이터를 입력받는 기능을 작은 프로그램으로 만들어둔 것이 Scanner입니다. 이 Scanner는 java.util이라는 위치에 저장돼 있는데, Scanner를 사용하려면 먼저 여러분이 작성하고 있는 소스 코드 안으로 Scanner를 불러와야 합니다. 즉, import java.util.Scanner;는 ‘java.util에 있는 Scanner를 이 소스 코드로 불러와라’라는 의미가 됩니다.
- Scanner scanner = new Scanner(System.in);클래스를 통해 객체를 만들어 낼 때는 new 연산자를 사용하며, 그 결과물로 만들어진 객체를 인스턴스라고 합니다. 이렇게 클래스에 new 연산자를 적용하여 인스턴스를 만드는 것을 ‘클래스를 인스턴스화한다'라고 표현합니다. 자세한 내용은 추후 객체지향 프로그래밍을 학습하면서 배워보도록 하겠습니다.
- 정리하자면, 위의 코드는 불러온 Scanner 클래스를 new 연산자를 통해 인스턴스를 생성하고, 생성된 인스턴스를 변수 scanner에 할당하는 코드입니다. 이제 데이터를 입력받기 위한 준비 과정이 끝났습니다.
- 위에서 불러온 Scanner는 클래스입니다. 클래스는 이후에 자세하게 배울 것이지만 여기에서 여러분들의 이해를 돕기 위해 가볍게 설명하겠습니다. 클래스는 객체를 찍어낼 수 있는 일종의 틀이며, 우리가 사용하고자 하는 Scanner 클래스의 데이터 입력 기능은 Scanner 클래스 자체가 아니라, 그것을 통해 만들어낸 객체에 존재합니다. 따라서 데이터 입력 기능을 사용하려면 Scanner 클래스를 통해 객체를 먼저 만들어야 합니다.
- String inputValue = scanner.nextLine();
- 위에서 Scanner 클래스를 인스턴스화한 scanner에는 nextLine()이라는 메서드가 존재합니다. 이 메서드는 콘솔을 통해 문자열 데이터를 입력받는 기능을 수행합니다. 즉 scanner.nextLine()은 문자열을 입력받기 위한 코드이며, 입력받은 문자열은 inputValue라는 변수에 할당되어 저장됩니다.
참고로, scanner에는 문자열을 입력받는 nextLine()뿐만 아니라, 정수형을 입력받을 수 있는 nextInt(), 실수형을 입력받을 수 있는 nextFloat()등의 메서드들도 존재합니다.
제어문(Control Flow Statements) - 조건문
이번 챕터에서는 조건문과 반복문을 학습합니다. 조건문과 반복문을 통틀어 제어문이라고 합니다.
일반적으로 코드의 흐름은 위에서 아래로 향합니다. 즉, 위에서부터 아래로 순차적으로 실행됩니다. 하지만, 제어문을 사용하면 코드의 흐름을 개발자가 원하는 방향으로 바꿀 수 있습니다. 제어문은 if문, switch문을 사용하는 조건문과 for문, while문, do while문을 사용하는 반복문으로 분류됩니다.
- 제어문
- 조건문 : if문, switch문
- 반복문 : for문, while문, do while문
조건문을 사용하면 특정 조건에 부합하는 경우에 어떤 코드를 실행시키면서 어떤 코드는 실행시키지 않을 수 있으며, 반복문을 사용하면 특정한 코드를 반복적으로 실행시킬 수도 있습니다. 이번 유닛을 학습하고 나면 여러분은 앞서 배운 타입과 연산자를 활용해 간단한 프로그래밍 로직을 작성할 수 있게 됩니다.
제어문 학습을 통해 프로그래밍의 즐거움을 느껴보시기 바랍니다.
학습 목표
조건문 기초, 실습
- if와 else if , else, switch를 이해하고 활용할 수 있다.
- 논리연산자(&&, ||, ! ...)를 활용하여 복잡한 조건을 간결하게 작성할 수 있다.
조건문 문제로 배우는 알고리즘
- 조건문 연습 문제 해결 방법을 익히면서, 문제를 체계적으로 해결하는 방법을 배운다.
- 복잡한 조건문을 활용하여, 실생활에서 마주하는 문제를 해결하기 위한 알고리즘을 구현할 수 있다.
조건문
if 문
if문의 소괄호 안에는 boolean 값으로 평가될 수 있는 조건식을 넣어주고, 중괄호 안의 실행 블록에는 조건식이 참일 때 실행하고자 하는 코드를 적어주면 됩니다.
if (조건식) {
//조건식이 참이라면 실행되는 블록입니다.
}
중괄호({})를 이용해 여러 문장을 하나의 단위로 묶을 수 있습니다. 이것을 ‘블록(block)’이라고 합니다.
if...else문
if...else문은 조건식의 결과에 따라 실행블록을 선택합니다. if문의 조건식이 true이면 해당 블록이 실행되고, 조건식이 false이면 다음으로 넘어가 else if 문의 조건식을 검사합니다. else if문의 모든 조건식이 false라면, 나머지 경우를 의미하는 else 블록이 실행됩니다. 다음의 그림을 통해서 흐름을 이해해 봅시다.
if(조건식1) {
//조건식1이 참이라면, 실행되는 블록입니다.
}
else if (조건식2) {
//조건식1이 참이 아니면서 조건식2가 참일 때, 실행되는 블록입니다.
}
else {
//조건식1과 2가 모두 참이 아닐 때, 실행되는 블록입니다.
//else문은 생략 가능합니다.
}
예제를 통해 조금 더 구체적으로 살펴봅시다.
import java.util.Scanner;
public class Main {
static Scanner myInput = new Scanner(System.in);
public static void main(String[] args) {
String dice = myInput.nextLine(); //주사위 번호 입력값을 받아옵니다.
if (dice.equals("1")) { // 입력한 주사위 번호가 1이면 다음 블록을 실행
System.out.println("1번"); // 콘솔에 "1번"을 출력
}
else if (dice.equals("2")) {
System.out.println("2번");
}
else if (dice.equals("3")) {
System.out.println("3번");
}
else if (dice.equals("4")) {
System.out.println("4번");
}
else if (dice.equals("5")) {
System.out.println("5번");
}
else if (dice.equals("6")) {
System.out.println("6번");
}
else {
System.out.println("없는 숫자! " + dice);
}
}
}
Switch문
switch문도 if문과 마찬가지로 조건 제어문입니다. 하지만 switch문은 if문처럼 조건식이 true일 경우에 블록 내부의 실행문을 실행하는 것이 아니라, 변수가 어떤 값을 갖느냐에 따라 실행문이 선택됩니다. if문은 조건식의 결과가 true와 false 두 가지밖에 없기 때문에 경우의 수가 많아질수록 if-else를 반복적으로 추가해야 하기 때문에 코드가 복잡해집니다. 그러나 switch문은 변수의 값에 따라서 실행문이 결정되기 때문에 같은 기능의 if문보다 코드가 간결할 수 있습니다.
아래 예시를 통해 흐름을 살펴보도록 하겠습니다.
import java.util.Scanner;
public class Main {
static Scanner myInput = new Scanner(System.in);
public static void main(String[] args) {
String dice = myInput.nextLine(); //주사위 번호 입력
switch (dice) {
case "1":
System.out.println("1번");
break; //다음 case를 실행하지 않고, switch문 탈출!
case "2":
System.out.println("2번");
break;
case "3":
System.out.println("3번");
break;
case "4":
System.out.println("4번");
break;
case "5":
System.out.println("5번");
break;
case "6":
System.out.println("6번");
break;
default: //switch문의 괄호 안 값과 같은 값이 없으면, 여기서 실행문 실행
System.out.println("없는 숫자! " + dice);
break;
}
}
}
switch문은 괄호 안의 값과 동일한 값을 갖는 case로 가서 실행문을 실행합니다. 그러나 만약 괄호 안의 값과 동일한 값을 갖는 case가 없으면 default로 가서 실행문을 실행합니다(default는 생략 가능합니다).
위의 예시 코드에서는 num의 값과 case의 번호가 같으면 해당 case안의 실행문을 실행하고 실행이 완료되면 break를 통해서 switch문을 탈출합니다. 주의할 점은, break문을 작성하지 않으면 switch문을 탈출하지 않고 모든 case문을 실행하게 되어 원하는 방식으로 동작하지 않는다는 것입니다(이를 fall through라고 합니다).
switch문에는 int 뿐만 아니라, char 타입 변수도 사용가능하고, 자바 7부터는 String 타입의 변수도 올 수 있습니다.
다음은 직급을 입력받아 값에 따라 해당하는 월급을 출력하는 예제입니다.
public class Main {
static Scanner userInput = new Scanner(System.in);
public static void main(String[] args) {
String yourPosition = userInput.nextLine(); //입력받기
switch(yourPosition){
case "Senior" : // Senior일 경우
System.out.println("700만원");
break;
case "Junior" : //Junior 또는 Manager일 경우
case "Manager" : //실행문이 같으면 이렇게 작성 가능
System.out.println("500만원");
break;
default:
System.out.println("300만원");
break;
}
}
}
참고자료
아래 예시는 위의 예시와 동일하지만 자바 14에서 표준화된 향상된 switch문(enhanced switch문)입니다. 현재 코스에서는 jdk-11버전을 사용하고 있으므로, “이런 것이 있구나”정도로 이해하고 넘어가도 무방합니다.
기존의 switch문에서 변경된 점은 아래와 같습니다.
- 여러 조건에 따라, , 로 구분하여 한 번에 처리할 수 있게 되었습니다.
- : 대신에 >를 사용하고, break문이 생략되었습니다. ( > 람다식과 유사하게 화살표를 사용하여 람다 스타일 구문이라고도 합니다. 람다식은 이후에 자세히 다룹니다.)
- >를 사용했다면 실행문이 2개 이상이거나, 반환값이 존재할 경우 중괄호 블록({ })을 사용해야 합니다.( :을 사용하면, 실행문이 여러 개라 할지라도 중괄호 블록({ })을 사용하지 않아도 됩니다. )
//직급별 월급 출력하기
public class Main {
static Scanner userInput = new Scanner(System.in);
public static void main(String[] args) {
String[] positionList = {"Manager", "Senior", "Junior"};
String yourPosition = userInput.nextLine(); //입력 받기
switch (yourPosition) {
case "Senior" -> System.out.println("700만원");
case "Junior", "Manager" -> System.out.println("500만원");
}
}
}
조건문 문제로 배우는 알고리즘
알고리즘?
우리는 지금 개발자가 되기 위해 커리큘럼을 따라 공부하고 있습니다. 어떤 문제를 해결하기 위해 논리적인 해결 방법을 담은 코드를 작성하는 일이 개발이라 할 수 있습니다. 개발자로서 새로운 해결책, 특히 새로운 소프트웨어를 만들기 위해서는 무엇이 필요할까요?
먼저는 해결해야 할 문제가 무엇인지 바르게 정의해야 하고, 어떤 방법으로 이를 해결할 수 있을지에 대한 바른 솔루션이 필요할 것입니다.
어떤 문제를 해결하기 위한 일련의 절차나 방법을 정해진 형태로 표현한 것을 알고리즘이라고 합니다. 알고리즘은 문제 해결을 위한 고민을 논리적으로 풀어내는 작업을 하는 방법입니다. 샌드위치를 만드는 알고리즘에 대해서 고민해 보신 적이 있나요?
이번 시간에는 convertScoreToGradeWithPlusAndMinus 문제를 함께 살펴보면서 알고리즘이 무엇이며, 어떤 과정을 통해 문제를 해결할 수 있는지 함께 생각해 보겠습니다. 먼저 문제로 이동하셔서 문제를 확인하고 돌아와 주세요.
해당 문제는 여러 가지 요구사항이 있지만, 여기에서는 다음 조건만 생각해 봅시다.
- (100 - 90) --> A
- (89 - 80) --> B
- (79 - 70) --> C
- (69 - 60) --> D
- 만약 점수의 1의 자리가 0~2 사이라면 등급과 함께 ``를 반환해야 합니다.
- 만약 점수의 1의 자리가 8~9 사이라면 등급과 함께 +를 반환해야 합니다.
이 문제를 풀기 위해 아래와 같이 하나씩 조건을 만들어서 해결할 수도 있습니다.
- 60~62점 사이를 D-
- 63~67점 사이를 D
- 68~69점 사이를 D+
- 70~72점 사이를 C-
- 73~77점 사이를 C
- 78~79점 사이를 C+
- 80~82점 사이를 B-
- 83~87점 사이를 B
- 88~89점 사이를 B+
- 90~92점 사이를 A-
- 93~97점 사이를 A
- 98~100점 사이를 A+
그러나 개발자는, 항상 더 나은 방법을 고민할 수 있어야 합니다. 지금이야 불과 몇 개 되지 않는 grade 문자열이지만, 만일 요구사항이 늘어난다면 비효율적인 코드가 될 수도 있습니다. 50~59, 40~49, 30~39 등 더 작은 숫자를 Grading해야 하거나, 1의 자리를 더욱 세분화해 -- 또는 ++를 붙이게 된다면 모든 조건을 만드는 일은 더욱 번거로워집니다.
더욱 효율적인 알고리즘을 적용하기 위해서는 어떻게 접근해야 할까요? 문제를 분해하여 더욱 작은 문제로 만들고, 수도 코드를 작성하는 일이 큰 도움이 될 수 있습니다. 수도 코드를 실제 실행할 수 있는 코드로 작성하면서 프로그램을 완성해 가는 과정을 함께 따라가면서 고민해 보시면 좋을 것 같습니다.
문제 분해
이 문제는 크게 두 가지의 작은 문제로 분리할 수 있습니다.
- 10의 자리로 grade를 구분한다.
- 1의 자리로 기호(+, -)를 구분한다.
수도 코드(Pseudocode) 작성
수도 코드를 작성하면서 프로그램이 동작하는 순서를 파악합니다.
- score가 100이면, 즉시 A+가 최종 결과가 된다.
- score에서 10의 자리를 얻어낸다.
- score에서 1의 자리를 얻어낸다.
- 10의 자리가 6이면, grade는 D이다.
- 10의 자리가 7이면, grade는 C이다.
- 10의 자리가 8이면, grade는 B이다.
- 10의 자리가 9이면, grade는 A이다.
- 1의 자리가 0~2 이면, (grade 뒤에 붙는) 기호는 ``이다.
- 1의 자리가 3~7 이면, (grade 뒤에 붙는) 기호는 빈 문자열이다(기호가 붙지 않는다).
- 1의 자리가 8~9 이면, (grade 뒤에 붙는) 기호는 +이다.
- grade와 기호를 문자열을 조합하여 최종 결과를 만든다.
- 최종 결과를 리턴한다.
코드로 옮기기
그럼 이제 수도 코드로 작성된 내용을 실제 코드로 옮겨볼까요?
어떻게 작성하느냐에 따라 조금의 차이는 있을 수 있지만, 대략적으로 아래와 같은 코드를 작성할 수 있을 것입니다.
public class Solution {
public String convertScoreToGradeWithPlusAndMinus(int score) {
String grade;
if (score > 100 || score < 0) {
return "INVALID SCORE";
}
if (score == 100) {
return "A+";
}
if (score >= 90) {
grade = "A";
} else if (score >= 80) {
grade = "B";
} else if (score >= 70) {
grade = "C";
} else if (score >= 60) {
grade = "D";
} else {
grade = "F";
}
if (!grade.equals("F")) {
int extra = score % 10;
if (extra <= 2) {
grade = grade + "-";
} else if (extra >= 8) {
grade = grade + "+";
}
}
return grade;
}
}
작성해 둔 수도 코드를 주석 처리하고, 수도 코드에 해당하는 부분을 코드로 작성합니다. 이 방법으로 별도의 주석을 작성하지 않아도, 자연스럽게 코드의 역할을 설명할 수 있습니다. 위에서 문제를 분리했던 것처럼, 우리는 그 해결 방법도 분리할 수 있습니다. 10의 자리를 판별하는 부분과, 1의 자리를 판별하는 부분을 분리할 수 있습니다.