[JAVA] 자바의 정렬 - Arrays.sort(), Comparable, Comparator



 오늘은 좀 짧은 글을 써볼까 합니다. 어떤 언어든 리스트나 배열을 정렬하는 기능을 지원하죠. 자바 역시 예외는 아닙니다. 자바에서 배열, 혹은 배열리스트 등을 정렬한다면 어떻게 해야할까요?



Arrays.sort() : 배열의 정렬


import java.util.Arrays;

public static void main(String[] args) {
    int[] arr = { 123, 22, 3221, 443 };
    Arrays.sort(arr);
    // 22, 123, 443, 3221
    
    String[] arr2 = { "hello", "world", "my", "name", "is", "keykat" };
    Arrays.sort(arr2);
	// "hello", "is", "keykat", "my", "name", "world"
}

배열을 정렬하는 가장 간단한 방법은 Arrays.sort()를 사용하는 것입니다. 기본적으로 제공하는 자료형 배열 (String도 포함) 을 오름차순으로 정렬해 줍니다. 이 때 String의 경우는 사전 순으로 정렬되죠.


사진을 보시면 파라미터에 여러가지가 들어갈 수 있는데, 일단 Comparator는 밑에서 이야기하고 싶으니 사진의 2번째 메서드인 sort(Object[] a, int fromIndex, int toIndex)를 봅시다.


public static void main(String[] args) {
    int[] arr = { 2, 8, 5, 6, 4, 3, 1, 10, 9 };
    Arrays.sort(arr, 3, 6);
	// 2 8 5 3 4 6 1 10 9
}

3번째 인덱스와 6번째 인덱스 사이, 즉 3, 4, 5번이 정렬된 것을 확인할 수 있습니다. (6, 4, 3 -> 3, 4, 6)





Comparable : 클래스의 정렬

이번엔 Comparable입니다. Comparable은 인터페이스로 반드시 주어진 메서드를 구현해야 합니다. 어떤 클래스가 Comparable을 상속 받았을 경우 필요 메서드인 compareTo를 구현한다면 같은 타입의 클래스끼리 크기를 비교할 수 있습니다. 


class Card implements Comparable<Card> {
    int number;
    public Card(int number) { this.number = number;}

    @Override
    public int compareTo(Card o) {
        return this.number - o.number;
    }
}

public static void main(String[] args) {
    Card[] cards = new Card[] { new Card(10), new Card(21), new Card(3), new Card(14), new Card(51) };
    Arrays.sort(cards);

    for (int i = 0; i < cards.length; i++) {
        System.out.printf(cards[i].number + " ");
    }
}
// 3 10 14 21 51

어렵지 않습니다. Comparable을 상속받고 (이 때 제네릭 타입은 클래스와 똑같이 해야 합니다. Comparable<Card>, 이렇게요!) compareTo를 override 하면 끝입니다. 이 때 현재 값을 기준으로 비교 값을 빼준 것을 리턴하면 오름차순, 그 반대로 하면 내림차순이 됩니다. 쉽게 말하자면

- this.number (기준 값) - o.number (비교 값) : 오름차순

- o.number (비교 값) - this.number (기준 값) : 내림차순


1개의 값 뿐만 아니라 여러 개의 값에 대한 정렬도 가능합니다. 무슨 이야기냐, 아래와 같습니다.


class Position implements Comparable<Position> {
    int x, y;
    public Position(int x, int y) { this.x = x; this.y = y; }

    @Override
    public int compareTo(Position o) {
        if (this.x < o.x) return -1;
        else if (this.x == o.x) {
            return this.y - o.y;
        }
        return 1;
    } 
}

public static void main(String[] args) {
    Position[] positions = new Position[] { 
        new Position(2, 1), new Position(3, 3), new Position(1, 4), new Position(2, 2), 
        new Position(4, 3), new Position(4, 2), new Position(1, 5) ,new Position(1, 1)
    };
    Arrays.sort(positions);

    for (int i = 0; i < positions.length; i++) {
        System.out.printf("(" + positions[i].x + ", " + positions[i].y + ")" + " ");
    }
}
// (1, 1) (1, 4) (1, 5) (2, 1) (2, 2) (3, 3) (4, 2) (4, 3)

x의 값이 같으면 y 값을 비교해서 정렬을 하는 방식입니다. 이런 방식을 사용한다면 3개, 4개, 또는 그 보다 더 많이도 가능하겠죠? 


ArrayList<Position> list = new ArrayList<>();
Collections.sort(list);

참고로 Comparable 인터페이스를 상속받은 클래스를 ArrayList에 넣을 때 해당 리스트는 Collections 라이브러리의 sort 함수를 이용해 정렬 가능합니다.






Comparator : 정렬 방법 정하기


Comparator 그냥 사용해보기

마지막은 Comparator입니다. 역시 인터페이스로 Comparable이랑 비슷하게 생겼습니다. 다만 Comparable은 정렬을 원하는 클래스가 상속을 받는 반면, Comparator는 정렬 방법을 정하는 새로운 클래스를 만들어서 정렬 기준을 정합니다. 아래와 같이 말이죠.


class Position {
    int x, y;
    public Position(int x, int y) { this.x = x; this.y = y; }
}

class SortMethod implements Comparator<Position> {

    @Override
    public int compare(Position o1, Position o2) {
        if (o1.x < o2.x) return -1;
        else if (o1.x == o2.x) {
            return o1.y - o2.y;
        }
        return 1;
    }
}


public static void main(String[] args) {
    Position[] positions = new Position[] { 
        new Position(2, 1), new Position(3, 3), new Position(1, 4), new Position(2, 2), 
        new Position(4, 3), new Position(4, 2), new Position(1, 5) ,new Position(1, 1)
    };
    Arrays.sort(positions, new SortMethod());

    for (int i = 0; i < positions.length; i++) {
        System.out.printf("(" + positions[i].x + ", " + positions[i].y + ")" + " ");
    }
}
// (1, 1) (1, 4) (1, 5) (2, 1) (2, 2) (3, 3) (4, 2) (4, 3)

이전과 비슷해 보이지만, Position 클래스가 더 이상 Comparable 인터페이스를 상속 받지 않는다는 점이 다릅니다. 대신 SortMethod라는 클래스가 Comparator 인터페이스를 통해 Position을 어떤 방식으로 정렬할지 규칙을 만들어주죠. 이렇게 만들어진 Comparator를 Arrays.sort()의 2번째 인자로 넣어주시면 됩니다.


ArrayList<Position> list = new ArrayList<>();
Collections.sort(list, new SortMethod());

Collections의 sort를 쓸 때도 마찬가지입니다.



Comparator 람다식으로 만들어보기

위와 같이 클래스로 만들어서 인자로 넘겨줄 수도 있지만, Comparator는 람다식으로 만들어서 넘겨줄 수도 있습니다. 


public static void main(String[] args) {
    // 1차원 배열은 어차피 Array.sort가 알아서 정렬해 줍니다.
    int[] arr1 = {1, 5, 3, 2, 8, 7, 9, 10};
    Arrays.sort(arr1);

    // 대신 역순 정렬을 원한다면 Comparator 타입인 Collections.reverseOrder()를 사용합니다. 
    // 중요한 것은 int형 배열이 아닌 "Integer"형 배열, 즉 기본 타입을 래퍼 클래스로 만들어야 합니다.
    Integer[] arr2 = {1, 5, 3, 2, 8, 7, 9, 10};
    Arrays.sort(arr2, Collections.reverseOrder());

    // 첫번째 원소 기준으로 정렬한 다음 두번째 원소에 대해 정렬합니다.
    // 2번 하니까 시간 복잡도는 O(2n)이 되겠네요.
    int[][] arr3 = { {1, 2}, {1, 3}, {3, 4}, {2, 4}, {3, 6}, {1,  4}, {2, 2} };
    Arrays.sort(arr3, (o1, o2) -> o1[0] - o2[0]);
    Arrays.sort(arr3, (o1, o2) -> o1[1] - o2[1]);

    // Collections.sort에도 역시 람다식을 적용할 수 있습니다.
    ArrayList<Integer> list = new ArrayList<>();
    Collections.sort(list, (o1, o2) -> o1 - o2);
}




마치며..

아마 그렇게 어려운 내용은 아니었을 것입니다. 애당초 많이 사용되기도 하고, 구현하는 것 자체도 크게 어렵지 않으니까 말이죠. 가끔 까먹을 때 한번씩 보는 정도면 될 것 같습니다. 그럼 오늘도 즐거운 코딩하세요!




댓글