Ch 04. 코딩 테스트 필수 문법
프리미티브 타입과 레퍼런스 타입
- 프리미티브 타입(primitive type)이란?
- int, long, float, double과 같은 타입
- 레퍼런스 타입(reference type)이란?
- Integer, Long, Float, Double과 같은 타입
- 참조형 변수이므로 연산 속도가 프리미티브 타입보다 느리다.
- 컬렉션 프레임워크 등에서 정수형 또는 부동소수형을 저장할 때 사용하기 때문에 꼭 알아야 한다.
- 정수형
- 양의 정수, 음의 정수, 0
- 정수형 비트 연산
연산자 | 의미 |
a ^ b | xor |
~a | not |
a << 2 | 왼쪽 시프트 ( a에 2²를 곱한 것과 동일) |
a >> 1 | 오른쪽 시프트 (a를 2¹로 나눈 것과 동일) |
- 부동소수형
- 소수를 저장할 때 사용
- 엡실론을 포함한 연산에 주의하라
- 엡실론이란? 자바는 부동소수형 데이터를 이진법으로 표현. 표현 과정에서 오차가 발생하는 것.
- 부동소수형 데이터를 다룰 일이 생겼을 때 이 엡실론을 항상 생각해야 한다.
- 부동소수형을 사용하여 코드를 작성하면 엡실론이라는 요소 때문에 일부 테스트 케이스가 통과하지 못할 수 있으니 유의해야 한다.
- ex) 0.1을 3번 더한 a의 값에 0.3을 빼면 0이 아니다.
- 부동소수형 데이터를 활용하는 문제는 오차 허용 범위를 언급하는 경우가 많다. 문제를 분석할 때 꼭 이 부분을 체크해야 한다.
컬렉션 프레임워크
- 컬렉션 프레임워크란?
- 여러 개의 값을 저장하고 그 값을 쉬우면서도 효율적으로 처리해주는 표준화 클래스의 집합
- 코딩 테스트 문제를 해결하기 위한 다양한 자료구조인 리스트, 큐, 해시맵 등을 직접 구현하지 않고도 손쉽게 사용할 수 있게 해줌.
- 대표적으로 리스트(ArrayList), 스택(Stack), 큐(Queue), 데크(ArrayDeque), 해시맵(HashMap) 등이 있다.
- 배열
// 선언 필요
import java.util.Arrays;
int[] array = {1, 2, 3, 4};
int[] array2 = new int[]{1, 2, 3, 4};
int[] array3 = new int[5];
System.out.println(Arrays.toString(array));
- 배열의 인덱스
- 특정 원소 위치에 빠르게 접근하기 위한 기능
- '인덱스로 배열의 원소에 접근한다'
- 배열은 생성 이후에는 배열 크기를 변경할 수 없다.
- 배열 생성 후 새 데이터를 삽입하거나 삭제하는 것은 할 수 없고, 기존 데이터의 변경만 할 수 있다.
- 인덱스를 이용한 배열 요소에 대한 접근, 변경의 시간 복잡도는 O(1)이다.
- 리스트
- 코딩 테스트 기준으로는 일반적으로는 ArrayList를 의미한다.
- 배열과 ArrayList의 가장 큰 차이점은 크기(size)가 고정되어 있어서 데이터를 삭제하거나 삽입할 수 없지만, ArrayList는 가변 크기이므로 새 데이터를 맨 뒤에 추가할 때는 평균 시간 복잡도가 O(1)이며, 기존 데이터의 삭제 혹은 데이터를 중간에 삽입할 때는 시간 복잡도가 O(N)까지 커질 수 있으므로 주의가 필요하다.
ArrayList<Integer> list = new ArrayList<>();
list.add(n);
System.out.println(list.get(2)); // 특정 인덱스 값에 접근
System.out.println(list); // 모든 값 출력
- 해시맵
- 키(key)와 값(value) 쌍을 저장하는 해시 테이블로 구현되어 있다.
- 키를 사용하여 값을 검색하는 자료 구조
- 해시맵 초기화
// String - 문자열, Integer - 32비트 정수형
HashMap<String, Integer> map = new HashMap<>();
- 해시맵의 데이터 삽입과 출력
map.put("apple", 1);
System.out.println("전체값 출력 String=Integer 형태" + map);
- 해시맵의 데이터 검색
- key가 해시맵에 있는지 확인
String key = "apple";
if(map.containsKey(key)){
int value = map.get(key);
System.out.println(key + " : " + value); // apple : 1
} else {
System.out.println(key + "는 해시맵에 없습니다.");
}
- 해시맵 수정
map.put("banana", 4);
- 해시맵 삭제
map.remove("banana");
- 키가 해시맵에 없는 경우 예외가 발생하므로 예외 처리를 해주어야 한다.
- 참고로 자바에는 해시맵과 유사하지만 '키-값' 쌍으로 저장하지 않고 '값' 없이 '키'만 저장하는 해시셋도 있다.
- 문자열
- 자바에서 문자열은 이뮤터블(Immutable) 객체이다.
- 이뮤터블 객체란? 값을 변경할 수 없는 객체를 의미하고 시간 복잡도 관점에서 사용 시 주의해야 할 필요가 있다.
- 주의해야 할 점은 문자열은 기존 객체를 수정하는 것이 아니라 새로운 객체를 반환한다는 사실이다.
- 문자열 수정
string = string.replace("a", " ");
- StringBuffer와 StringBuilder
- System.identityHashCode() 메서드란? 객체를 특정할 수 있는 식별값을 반환한다.
- String s의 abc에 def를 이어붙였는데 identityHashCode() 메서드의 출력값이 달라졌다.
- 이는 "abc"값만 가지고 있던 s와 "abcdef"값을 가지고 있는 s가 서로 다른 객체임을 의미한다.
- 즉, String 객체의 값을 변경하는 작업은 새로운 String 객체를 만들고 값을 복사하는 작업이 수행됨을 의미한다.
- 따라서 다음의 연산이 수행된다.
- 새로운 String s 객체를 생성
- s가 가진 "abc" 값을 하나씩 복사
- "abc" 뒤에 "def" 저장
- 그 결과 s += "def" 코드 한 줄에서 총 6번의 내부 연산("abc" 값 3개 복사, "def" 값 3개 저장)이 수행된다.
- 시간 복잡도는 문자열의 길이를 N이라 했을 때 O(N)이 된다.
- 코드 실행 시간 재기
long start = System.out.currentTimeMillis();
long end = System.out.currentTimeMillis();
System.out.println(((end - start) / 1000.0) + "초");
- 시간 복잡도를 줄이기 위해 나온 것이 StringBuilder와 StringBuffer 클래스다.
- StringBuilder와 StringBuffer 클래스는 뮤터블하므로 값을 변경할 때 시간 복잡도 관점에서 훨씬 더 효율적이다.
- String의 값을 변경하는 연산이 많을 때는 효율이 높은 StringBuilder 클래스나 StringBuffer 클래스를 사용해야한다.
- 두 클래스의 차이는 멀티스레드 환경에서 Thread-Safe 여부로 나뉜다.
- 하지만 코딩 테스트에서는 다수의 스레드를 생성할 필요가 없다.
- 결론은 Thread-Safe가 없는 StringBuilder 클래스가 속도 측면에서 미세하지만 빠르므로 StringBuilder를 사용하면 된다.
- StringBuilder 클래스의 활용 방법
//StringBuilder 객체 생성
StringBuilder sb = new StringBuilder();
// 문자열 Add
sb.append(10);
sb.append("ABC");
// 10ABC
// 특정 위치 인덱스 삭제
sb.deleteCharAt(3);
// 10AC
// 특정 위치 인덱스 추가
sb.insert(1, 2); // 1번째 인덱스에 2라는 문자 추가
// 120AC
메서드
- 메서드는 프로그램의 중요한 요소이다.
- 메서드 정의
- 메서드는 클래스 내부에 정의한 함수를 말한다. 자바에서는 대부분 클래스 내부에 함수를 정의할 것이므로 메서드라고 부르겠습니다.
public int function_name(int param1, int param2) {
// 메서드의 실행 코드
// ....
return result; // 반환 값
}
- 메서드 호출
public static void main(String[] args){
// 함수를 호출하여 결과 출력
int ret = add(5, 10);
System.out.println(ret);
}
public static int add(int num1, int num2){
int result = num1 + num2;
return result;
}
- 람다식 (lambda expression)이란?
- 자바 1.8 버전에서 추가되었다.
- 다른 말로는 익명 함수(annonymous function)라고도 한다.
- 익명함수란 말 그대로 이름이 없는 함수를 말하며 코드에서 딱 한번 실행할 목적으로 사용하거나 함수 자체를 다른 함수의 인수로 전달할 때도 사용할 수 있다.
- 또한, 람다를 사용하면 함수를 간결하게 표현할 수 있고 가독성이 좋아진다는 장점이 있다.
- 람다식의 정의와 사용
- 람다식은 다음과 같이 정의하여 사용할 수 있다.
- 다음 코드는 int형 dest와 cost를 멤버 변수로 갖는 Node 클래스의 객체를 생성하여 배열에 담고 그 배열의 객체들을 cost 기준으로 오름차순 정렬한다.
private static class Node {
int dest, cost;
public Node(int dest, int cost){
this.dest = dest;
this.cost = cost;
}
}
public static void main(String[] args) {
Node[] nodes = new Node[5];
nodes[0] = new Node(1, 10);
nodes[1] = new Node(2, 20);
nodes[2] = new Node(3, 15);
nodes[3] = new Node(4, 5);
nodes[4] = new Node(1, 25);
// 1. 2번과 동일한 로직을 수행한다.
// 더 짧으면서 가독성이 좋다.
// 코딩 테스트에서 람다식을 사용하는 일은 극히 드물지만 이렇게 정렬 API를 사용할 때 간혹 사용하면 좋다.
Arrays.sort(nodes, (o1, o2) -> Integer.compare(o1.cost, o2.cost);
// 2번
// Integer.compare(int x, int y) 메서드는 다음과 같다.
// x < y면 -1 반환
// x > y면 1 반환
// x == y면 0 반환
Arrays.sort(nodes, new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2){
return Integer.compare(o1.cost, o2.cost);
}
});
}
- 간혹 정렬 조건을 구현할 때 [x-y]와 같이 뺄셈을 하는 경우가 있는데, 이렇게 하면 오버플로의 위험이 있다.
- 자료형에 맞는 compare() 메서드를 사용하는 것이 좋다.
코딩 테스트 코드 구현 노하우
- 조기반환(early return)
- 코드 실행 과정이 함수 끝까지 도달하기 전에 반환하는 기법
- 가독성을 높여줄 뿐만 아니라 예외를 조금 더 깔끔하고 빠르게 처리할 수 있다.
public static void main(String[] args) {
System.out.println(totalPrice(4, 50));
}
static int totalPrice(int quantity, int price) {
int total = quantity * price;
if(total > 100)
// 함수 자체를 조기에 종료할 수 있으므로 이후 예외에 대한 처리를 하지 않아도 된다.
return (int)(total * 0.9);
return total;
}
- 보호 구문(guard clauses)
- 본격적인 로직을 진행하기 전 예외 처리 코드를 추가하는 기법
- 조건문을 이용하여 초기에 입력 값이 유효한지 검사하고 그렇지 않으면 바로 함수를 종료하는 보호 구문을 쓸 수 있다.
import java.util.List;
static double calculateAverage(List<Integer> numbers) {
if(numbers == null) // null이면 종료(예외)
return 0;
if(numbers.isEmpty()) // 데이터가 없으면 종료(예외)
return 0;
int total = numbers.stream().mapToInt(i -> i).sum(); // 예외 처리 후 기능 구현
return (double) total / numbers.size();
}
- 이렇게 구현한 코드는 보호 구문 이후 구현부에서 입력값에 대한 예외를 고려하지 않아도 되므로 보기 좋다.
- 추가로 이런 습관을 들이면 처음부터 예외를 고려할 수 있어 코드를 더 안전하게 작성할 수 있게 된다.
- 코드를 보면 예외처리를 하여 함수를 종료시킨다.
- 예외를 잘 고려했다면 이후 코드에서는 원하는 동작 구현에만 집중하면 된다.
- 제네릭(generic)
- 빌드 레벨에서 타입을 체크하여 타입 안정성을 제공하고, 타입 체크와 형변환을 생략할 수 있게 해주어 코드를 간결하게 만들어주는 기능이다.
List list = new ArrayList();
list.add(10);
list.add("abc");
int sum1 = (int)list.get(0) + (int)list.get(1); // 1. 런타임 오류 발생
List<Integer> genericList = new ArrayList<>();
genericList.add(10);
genericList.add("abc"); // 2. 문법(빌드 레벨) 오류 발생
int sum2 = genericList.get(0) + genericList.get(1);
- List를 정의할 때 <Integer>와 같이 타입을 강제하는 것을 제네릭이라고 한다.
- 제네릭은 타입에 맞지 않는 데이터를 추가하려고 할 때 문법 오류를 발생시켜 개발자의 실수를 방지해준다.
- 따라서 1번은 코드를 실행해야만 오류가 발생한다는 것을 알 수 있지만, 2번은 빌드 자체가 안되므로 런타임 버그를 방지할 수 있다.
- 또한 데이터에 접근하여 사용하려고 할 때 형변환을 할 필요가 없기 때문에 코드가 간결해진다.
- 코딩 테스트에서는 여러 타입의 데이터를 하나의 컬렉션에 넣어야 하는 경우는 거의 없으므로 제네릭으로 타입을 강제하여 실수를 방지하는 것이 좋다.
'개인 공부 > 알고리즘' 카테고리의 다른 글
[ 코테 준비 PGS ] 코딩 테스트 합격자 되기(자바 편) - Ch 5 문제 편 (0) | 2024.08.06 |
---|---|
[ 코테 준비 PGS ] 코딩 테스트 합격자 되기(자바 편) - Ch 5 (0) | 2024.07.30 |
[ 코테 준비 PGS ] 코딩 테스트 합격자 되기(자바 편) - Ch 3 (0) | 2024.07.18 |
[ 코테 준비 PGS ] 코딩 테스트 합격자 되기(자바 편) - Ch 2 (0) | 2024.07.18 |
[ 코테 준비 PGS ] 코딩 테스트 합격자 되기(자바 편) - Ch 1 (0) | 2024.07.18 |