거북이-https://velog.io/@violet_evgadn 이전완료

Compare 관련 메소드 본문

코딩 테스트 시 알면 좋은 것들

Compare 관련 메소드

VioletEvgadn 2022. 12. 31. 01:58

코딩 테스트 시 필요한 이유

코딩 테스트에 항상 나오는 문제가 있다.

 

"~기준으로 정렬하여 반환하시오"

 

이런 경우 Arrays.sort()나 Collections.sort()의 기본적인 정렬 Rule인 Primitive Class(or Wrapper Class) 오름차순 정렬을 사용하면 참 좋겠지만 코딩 테스트에는 이런 날먹을 막기 위해 대부분 정렬 특수 조건을 추가한다.

 

"A 기준으로 정렬하고, A가 같다면 B 기준으로 정렬하십시오"

"A 기준으로 내림차순 정렬하십시오"

 

결국 이를 위해선 Java의 Compare 관련 메서드 및 인터페이스에 대해 알고 있어야 한다.

이전에 Stream을 공부할 때 sorted()를 사용하며 Comparator Interface를 람다식으로 잠시 사용한 적이 있었는데, 이번 기회에 한 번 제대로 알아보자.


compareTo

compareTo()는 두 개의 값을 비교하여 int 값으로 결과를 반환해주는 함수이다.

 

그런데 compareTo를 사용하다 보면 항상 항상 헷갈리는 게 있다.

compareTo()가 0을 반환하면 같다는 것은 알겠는데, "return s1 - s2"의 상황에서 음수일 경우 s1이 큰 걸까 s2가 큰 걸까?

 

필자도 항상 이게 헷갈려서 compare 관련 로직이 필요한 경우 return 함수를 임의로 지정한 뒤 원하는 대로 정렬되었는지 확인하고 만약 반대로 정렬되었다면 "return s2-s1"으로 s1과 s2를 바꿔주는 식으로 사용했었다.

 

계속해서 이런 귀찮은 작업을 수행하지 않도록 이번 Section에서 확실히 return 값 부호에 따른 정렬을 확실히 이해하고 넘어가자.

 

◎ 값 대소에 따른 compareTo 반환값

먼저 결과부터 말해보자.

 

x.compareTo(y)

  • x < y : 음수
  • x = y : 0
  • x > y : 양수

이를 확실히 이해할 방법이 없을까?

 

사람마다 외우는 방법이 다르겠지만 필자는 아래와 같이 외우고 있다.

 

compare 관련 로직을 짤 때 "정수를 정렬한다"라는 생각과 a.compareTo(b)나 compare(a,b) 메서드는 "a가 b보다 작은 index(앞쪽에 존재)를 가지고 있다"라는 생각에서 시작한다.

그리고 자바에서는 모든 원소에 대해 compare(앞 쪽에 있는 값, 뒤 쪽에 있는 값)을 수행하면 음수가 나오도록 정렬한다.

 

위 개념을 통해 x.compareTo(y)를 이해해보자.

(자바에서 Override 하지 않은 원소 정렬은 무조건 오름차순이라는 것을 미리 알고 가자)

 

먼저 3.compareTo(4)의 상황이다.

int[] arr = {3,4}의 상황에서 3과 4를 비교하는 것과 동일한 상황이다.

현재 배열 arr는 자바 기본 설정대로 오름차순 정렬되어 있다. 따라서 정상적으로 정렬되어 있는 상황이고, 음수를 반환할 것이다.

 

반대로 4.compareTo(3)의 상황이다.

int[] arr = {4,3}의 상황에서 4와 3을 비교하는 상황이다.

배열 arr는 내림차순 정렬되어 있기 때문에 자바 기본 정렬 Rule인 오름차순과 충돌된다.

따라서 양수를 반환하는 것이다.

 

이는 String 데이터에도 똑같이 적용되는데 단지 숫자적 오름차순/내림차순이 아닌 사전적 정의의 오름차순/내림차순이라고만 바꿔 생각하면 된다.

 

이런 compareTo의 성질을 이용해 Array나 Collection을 오름차순 정렬시킬 수 있고, 정렬에 조건을 달아줄 수도 있으며 객체에 대한 정렬도 가능해지는 것이다.


Comparable과 Comparator

이 둘은 객체를 비교할 수 있게 만들어 주는 Interface이다.

Interface인 만큼 Comparable과 Comparator에는 필수적으로 구현해야 하는 메서드가 존재한다.

 

그렇다면 이 두 인터페이스의 차이를 알아보자.

 

◎ 구현해야 하는 메소드

  • Comparable : compareTo
  • Comparator : compare(a, b)

 

◎ 비교 대상

  • Comparable : "자기 자신"과 "매개 변수"를 비교
  • Comparator : "매개 변수"와 "매개 변수"를 비교

 

◎ import 시켜줘야 하는 Package

  • Comparable : java.lang(import 시켜줄 필요 없음)
  • Comparator : java.util

 

◎ 주로 사용하는 방법

  • Comparable : Class에 implement(상속) 시켜 사용
  • Comparator : 익명 구현체를 통해 사용

이는 단어 뜻을 알면 쉽게 이해 가능하다.

 

"Comparator"란 "비교 측정기"라는 뜻을 가지고 있다. 즉 비교를 수행할 때 비교의 기준이 될 척도에 대한 인터페이스임을 알 수 있다.

따라서 비교 측정기가 필요한 상황에 익명 구현체를 넣어줌으로써 활용하는 경우가 많을 것이고, 이 때문에 비교값으로 2개 Parameter를 집어넣는 compare() 메서드를 사용하는 것이다.

 

"Comparable"은 "Compare" + "-able"이 합쳐진 단어로써 "비교가 가능한"이라는 뜻을 지닌다.

객체에 상속시킴으로써 원래는 해당 객체가 비교 불가능한 객체였으나 Comparable Interface를 통해 비교(혹은 정렬)가 가능한 객체로 만들어주는 것이다.

Comparable은 "자기 자신"이 비교 가능한 객체가 되도록 만드는 게 주요 역할이므로 "자기 자신"과 "매개 변수"를 비교하는 compareTo 메서드를 Override 해야 하는 것이다.


Comparable을 활용한 정렬

위에서 Comparable은 "compareTo"를 활용한 인터페이스라고 설명했다.

 

앞서 말했듯 a.compareTo(b)는 a가 앞쪽에 존재하는 데이터, b가 뒤쪽에 존재하는 데이터이다.

그리고 자바는 항상 "앞쪽에 있는 데이터와 뒤쪽에 있는 데이터의 비교값이 음수"인 방향으로 정렬을 시작한다.

 

Comparable은 compareTo 메서드를 Override 해야 하므로 "자기 자신"이 a, "매개 변수의 데이터"가 b라고 생각하며 구현하면 된다.

즉, 매개 변수에 오는 값이 Index상 뒤에 있는 값이라고 생각하면 되는 것이다.

 

잘 이해가 안 될 수도 있을 것이다. 코드를 통해 알아보자.

class Member implements Comparable<Member>{
	int age;
	String name;

	public Member(int age, String name) {
		this.age = age;
		this.name = name;
	}

	@Override
	public int compareTo(Member o) {
		return this.age - o.age;
	}
}
Random random = new Random();
List<Member> list = new ArrayList<>();
for(int i =0;i<10;i++){
    list.add(new Member(random.nextInt(100)+1,"Member"+i));
}

Collections.sort(list);

System.out.println("나이 순으로 내림차순 정렬");
list.stream().forEach(i->System.out.println(i.age+" "+i.name));

위 Member의 compareTo 메서드를 보면 "this.age - o.age"를 반환함을 볼 수 있다.

 

일단 Member는 나이 순으로 정렬하는 것을 알 수 있다.

두 번째로, compareTo 메서드가 항상 음수 값을 반환하도록 정렬되므로 this.age < o.age 순으로 정렬된다.

마지막으로 이전에 말했었듯 매개변수로 들어온 Member O가 현재 객체보다 뒤쪽에 존재해야 하므로 "뒤 쪽에 있는 객체가 나이가 더 많게" 정렬된다.

 

즉, 나이 기준 오름차순 정렬된다는 것을 알 수 있다.

 

이를 사용하면 나이 기준 내림차순 정렬하는 방법도 쉽게 알 수 있다.

// 내림차순 정렬시키는 compareTo 메서드
@Override
public int compareTo(Member o) {
return o.age - this.age;
}

 

마지막으로 "나이 순으로 내림차순 정렬하고, 만약 나이가 같다면 이름 순으로 오름차순 정렬" 하도록 compareTo 객체를 바꿔보자.

@Override
public int compareTo(Member o) {
    if(o.age == this.age){
        return this.name.compareTo(o.name);
    }
    return o.age - this.age;
}

먼저 나이 순으로 내림차순 하는 것은 o.age - t his.age로 손쉽게 구현 가능하다.

문제는 나이가 같을 경우(o.age == this.age)이다.

 

이 경우 이름순으로 오름차순 정렬 시켜야 한다.

String의 compareTo 메서드는 자바에서 이미 구현되어 있는데, 이전에 말했듯 java의 compareTo는 Default로 오름차순 정렬되도록 구현되어 있다.

 

즉, this.name.compareTo(o.name)으로 뒤쪽 index에 있는 이름이 사전적으로 뒤에 존재하도록 설정하면 원하는 대로 정렬되는 것이다.


Comparator를 활용한 정렬

Comparator는 compare(a, b) 메서드를 Override 한다는 특징과 "익명 구현체를 주로 활용한다"라는 특징을 가지고 있다.

이때 compare(a, b)에서 a가 b보다 앞쪽에 있는 객체임만 알고 있다면 나머지는 Comparable 사용과 매우 유사하다.

 

위에서 사용한 Member 객체를 통해 똑같이 상황 3개를 구현해 보자.

class Member{
	int age;
	String name;

	public Member(int age, String name) {
		this.age = age;
		this.name = name;
	}
}

먼저 나이 순으로 오름차순 정렬하는 상황이다.

Random random = new Random();
List<Member> list = new ArrayList<>();
for(int i =0;i<10;i++){
    int age = random.nextInt(100) + 1;
    list.add(new Member(age,"Member"+i));
}

// 익명구현체를 람다식으로 대체함
Collections.sort(list, (a1, a2) -> a1.age - a2.age);

list.stream().forEach(i->System.out.println(i.age+" "+i.name));

(a1, a2)에서 a2가 a1보다 뒤쪽에 있는 객체이다.

객체들이 "오름차순 정렬" 돼야 한다면 a1.age < a2.age여야 한다.

두 객체의 연산 결과가 음수가 나와야 하므로 a1.age - a2.age로 설정한 것을 볼 수 있다.

 

그렇다면 내림차순 정렬은 어떻게 해야 할까?

Collections.sort(list, (a1, a2) -> a2.age - a1.age);

마지막으로 "나이 순으로 내림차순 정렬하고, 만약 나이가 같다면 이름 순으로 오름차순 정렬"하는 상황이다.

Collections.sort(list, (a1, a2) -> {
            if (a1.age == a2.age) {
                return a1.name.compareTo(a2.name);
            }
            return a2.age - a1.age;
        });

여기에서 매우 중요한 점이 (a1, a2) -> { ~~~~ } 형식으로 위 2개 상황과는 다르게 뒷 로직을 {}로 묶었음을 알 수 있다.

 

이 점만 주의하면 제대로 된 정렬 결과를 도출함을 알 수 있다.

 

'코딩 테스트 시 알면 좋은 것들' 카테고리의 다른 글

조합과 순열  (0) 2023.01.03
문자열 뒤집기  (0) 2023.01.03
log & 거듭제곱  (0) 2022.12.30
N 진법  (0) 2022.12.30
Long & Int 범위  (0) 2022.12.30
Comments