티스토리 뷰

JAVA

[JAVA] Sort() 정렬하기

CharlieZip 2021. 7. 13. 00:21
반응형

[ Sort() 란? ]

java.util.Arrays 클래스의 메서드로 배열(Array)을 쉽게 오름차순 or 내림차순 정렬이 가능합니다.

 

[ 1차원 배열 오름차순 정렬 ]

Arrays.sort() 메서드를 사용하면 자동으로 오름차순 정렬이 됩니다.

int[] arr = {1, 3, 7, 2, 5};          //Primitive Type
String[] stringArr = {"A", "C", "F", "E", "D"};

Arrays.sort(arr);    //arr : [1, 2, 3, 5, 7]

Arrays.sort(stringArr);  //stringArr : [A, C, D, E, F]

 

[ 1차원 배열 내림차순 정렬 ]

내림차순 정렬할 때는 Collections.reverseOrder() 메서드를 사용합니다.

여기서 주의해야할 점이 내림차순은 byte, char, int, long 같은 Primitive Type 배열에는 적용이 불가능하여

Integer, String 같은 Wrapper Class를 이용해야 합니다.

int[] arr1 = {1, 3, 7, 2, 5};
Integer[] arr2 = {1, 3, 7, 2, 5};
String[] stringArr = {"A", "C", "F", "E", "D"};

Arrays.sort(arr1, Collections.reverseOrder());   // error
Arrays.sort(arr2, Collections.reverseOrder());   // arr2 : [7, 5, 3, 2, 1]
Arrays.sort(stringArr, Collections.reverseOrder()); // stringArr : [F, E, D, C, A]

 

 

그러면 2차원 배열 어떻게 정렬할까요??

[ 2차원 배열 오름차순 정렬 (미구현)]

오름차순 배열이니 똑같이 Arrays.sort() 메서드를 사용해 보겠습니다.

int[][] arr = {{10, 3}, {5, 8}, {15, 10}, {2, 6}, {7, 5}};

Arrays.sort(arr);

위와 같이 CastException 이 발생하게 됩니다.

CastException 이 발생하는 이유는 arr 배열을 정렬할때 어떤 기준으로 정렬을 해야 할지 모르기 때문입니다.

예를 들어 arr[0] 인 {10 , 3} 과 arr[1] 인 {5 , 8} 을 비교할때 10 과 5를 비교해야 할지, 3 과 8 을 비교해야 할지 모르기 때문입니다.

 

그래서 어떤 기준을 정해주기 위해선 어떻게 해야 할까요?

비교 기준을 정해주기 위해 사용하는것이 Comparable/Comparator 인터페이스 입니다.

그럼 Comparable/Comparator에 대해 알아보겠습니다.

 

[ Comparable/Comparator 란? ]

Comparable과 Comparator는 비교의 기준을 정해주기 위해 사용됩니다.

단순하게는 {10 , 3} 과 {5 , 8} 에서 10과 5인 첫번째 원소를 통해 비교하자라는 기준을 세울수도 있지만

객체지향언어인 Java에서 객체들 간의 비교도 가능하게 해줍니다.

 

예를 들면, 학생들의 과목 성적을 담은 클래스가 있다고 생각해 보겠습니다.

class Student {
    String name;       // 이름
    int scoreA;        // A과목 성적
    int scoreB;        // B과목 성적

    public Student(String name, int scoreA, int scoreB) {
        this.name = name;
        this.scoreA = scoreA;
        this.scoreB = scoreB;
    }
}

그리고 철수랑 영희라는 Student 객체를 생성하였습니다.

Student a = new Student("철수", 90, 50);
Student b = new Student("영희", 80, 80);

그럼 철수랑 영희를 비교할 때 어떤 기준으로 비교해야 할까요??

A과목 성적으로도 비교할 수도 있고 B과목 성적으로도 비교할 수도 있습니다.

이러한 문제를 해결하기 위해 Comparable과 Comparator를 사용합니다.

 

그러면 Comparable과 Comparator는 무슨 차이인것일까요??

 

Comparable 과 Comparator는 인터페이스 로 인터페이스 내에 선언된 메소드를 반드시 구현해야 합니다.

  • Comparable - 인터페이스를 구현하려면 compareTo() 메소드를 오버라이딩 해야 하는데
    compareTo(T o)는 매개변수가 하나로 "자기 자신과 매개변수 객체를 비교"합니다.
  • Comparator - 인터페이스를 구현하려면 compare() 메소드를 오버라이딩 해야 하는데
    compare(T o1, T o2)는 매개변수가 두개로 "두 매개변수 객체를 비교"해야 합니다.

 

그러면 Student 객체에 Comparable 인터페이스를 구현해 보겠습니다.

class Student implements Comparable<Student> {
    String name;       // 이름
    int scoreA;        // A과목 성적
    int scoreB;        // B과목 성적

    public Student(String name, int scoreA, int scoreB) {
        this.name = name;
        this.scoreA = scoreA;
        this.scoreB = scoreB;
    }

    // 필수 구현 부분
    @Override
    public int compareTo(Student o) {
        return this.scoreA - o.scoreA;
    }
}

여기서 compareTo() 메서드가 우리가 객체 비교 기준을 정해주는 부분입니다.

compareTo() 메서드는 자기 자신과 매개변수로 넘어온 객체를 비교하며, int값을 반환해 줍니다.

 

그럼 int 값을 반환해 준다면 어떤 int 값을 반환해 주는 걸까요?

자기 자신과 넘어온 객체의 차이를 int로 반환해 줍니다.
예를 들어 나의 A과목 성적이 90점인데 매개변수로 넘어온 객체의 A과목의 성적이 80점이라면 90 - 80 = 10의 int값을 반환해 줄것입니다.

  • int 값이 양수 : 자기 자신 > 매개변수 객체
  • int 값이 0     : 자기 자신 = 매개변수 객체
  • int 값이 음수 : 자기 자신 < 매개변수 객체

 

그러면 Student 객체를 scoreA를 기준으로 비교 해보겠습니다.

public class Main {
    public static void main(String[] args) {
        Student a = new Student("Kim", 90, 50);
        Student b = new Student("Park", 80, 80);

        int result = a.compareTo(b);

        System.out.println(result);   // 10
    }
}

class Student implements Comparator<Student> {
    String name;       // 이름
    int scoreA;        // A과목 성적
    int scoreB;        // B과목 성적

    public Student(String name, int scoreA, int scoreB) {
        this.name = name;
        this.scoreA = scoreA;
        this.scoreB = scoreB;
    }

    @Override
    public int compareTo(Student o) {
        return this.scoreA - o.scoreA;
    }
}

compareTo() 메서드를 이용해 a학생과 b학생의 scoreA 과목 점수를 비교하였더니

두 학생의 scoreA 과목 점수 차이인 10을 반환하는 것을 볼 수 있습니다.

 

 

이번에는 Comparator 인터페이스를 구현해 보겠습니다.

Comparator는 compare() 메서드를 구현하면 되는데 바로 코드를 보여드리겠습니다.

class Student implements Comparator<Student> {
    String name;       // 이름
    int scoreA;        // A과목 성적
    int scoreB;        // B과목 성적

    public Student(String name, int scoreA, int scoreB) {
        this.name = name;
        this.scoreA = scoreA;
        this.scoreB = scoreB;
    }

    @Override
    public int compare(Student o1, Student o2) {
        return o1.scoreA - o2.scoreA;
    }
}

compare() 메서드를 보면 Comparable의 compareTo()와는 다르게, 두 객체를 비교하는 것이기 때문에 매개변수 o1,o2 두개를 지정해줘야 비교할 수 있습니다. 그리고 o1과 o2을 비교하여 int값을 반환해 줍니다.

  • int 값이 양수 : o1 객체 > o2 객체
  • int 값이 0     : o1 객체 = o2 객체
  • int 값이 음수 : o1 객체 < o2 객체

그러면 Student 객체를 scoreA를 기준으로 비교 해보겠습니다.

public class Main {
    public static void main(String[] args) {
        Student a = new Student("Kim", 90, 50);
        Student b = new Student("Park", 80, 80);
        Student c = new Student("Lee", 40, 70);

        int result = a.compare(b, c);

        System.out.println(result);     // 40
    }
}

class Student implements Comparator<Student> {
    String name;       // 이름
    int scoreA;        // A과목 성적
    int scoreB;        // B과목 성적

    public Student(String name, int scoreA, int scoreB) {
        this.name = name;
        this.scoreA = scoreA;
        this.scoreB = scoreB;
    }

    @Override
    public int compare(Student o1, Student o2) {
        return o1.scoreA - o2.scoreA;
    }
}

b학생과 c학생의 scoreA과목 점수 차인 40을 반환해주는걸 볼 수 있습니다.

compareTo() 메서드랑 다른점은 b와 c 객체의 scoreA값을 비교하는데

a객체의 compare 메서드를 통해 한걸 볼 수 있습니다.

즉 compare 메서드는 자기와 관련이 없는 객체들을 비교 가능하게 해주는 메서드입니다.

꼭 a객체의 compare 메서드를 사용하지 않고도 b나 c 객체의 compare 메서드를 통해서도 비교가 가능합니다.

 

만약 a.compare 메소드에서 a와 b를 비교하고 싶다면  a.compare(a,b); 라고 해주면 됩니다.

 

드디어 comparable과 comparator를 통한 비교를 알아보았는데요.

사실 저희가 궁금한건 정렬입니다.

그러면 comparable 과 comparator를 이용해 어떻게 정렬을 할 수 있을까요??

두 인터페이스를 이용해 정렬할 때의 차이점은 무엇일까요??

  • Comparable - 인터페이스를 구현한 객체 스스로에게 부여하는 한 가지 기본 정렬 규칙을 설정하는 목적으로 사용한다.
  • Comparator - 인터페이스를 구현한 클래스는 정렬 규칙 그 자체를 의미하며, 기본 정렬 규칙과 다르게 원하는대로 정렬순서를 지정하고 싶을 때 사용한다.

즉, Comparable은 구현한 객체에게만 정렬 규칙을 부여해주지만 Comparator는 여러 객체에게 정렬 규칙을 부여해 줄 수 있습니다.

 

먼저 정렬을 구현하기 전에 compareTo() 와 compare() 반환 값에 대해 알 필요가 있습니다.

위에서 제가 compareTo() 와 compare() 의 반환값을 양수, 0, 음수로 구분해 놓았습니다.

그러면 왜 굳이 양수, 0 , 음수의 구분이 필요할까요??

결론부터 말씀 드리면 양수가 나오면 두 원소의 위치를 바꾼다는 것입니다.

 

Java에서 정렬할때 기본값이 '오름차순'이 기준입니다.

Arrays.sort() 도 기본적으로 오름차순을 기준으로 정렬을 해줍니다.

 

오름차순이라는 얘기는 자기자신보다 뒤에 있는 원소가 더 크다는 것을 의미합니다.

즉 자기 자신 < 후행 원소 라는 뜻이 됩니다.

그래서 정렬 할 때 객체를 비교하여 음수가 나오면 두 원소의 위치를 바꾸지 않고, 양수가 나오면 두 원소의 위치를 바꿔줍니다.

 

Comparable 인터페이스를 통한 오름차순 정렬을 해보겠습니다.

코드를 먼저 보고 설명하겠습니다.

class Student implements Comparable<Student> {
    String name;       // 이름
    int scoreA;        // A과목 성적
    int scoreB;        // B과목 성적

    public Student(String name, int scoreA, int scoreB) {
        this.name = name;
        this.scoreA = scoreA;
        this.scoreB = scoreB;
    }

    @Override
    public String toString(){
        return "{name: " + name + ", scoreA: " + scoreA + ", scoreB: " + scoreB + "}\n";
    }

    @Override
    public int compareTo(Student o) {
        return this.scoreA - o.scoreA;
    }
}

public class Main {
    public static void main(String[] args) {
        Student[] students = {
                new Student("Kim", 90, 50),
                new Student("Park", 80, 80),
                new Student("Lee", 40, 70),
                new Student("Yong", 70, 100),
                new Student("Ahn", 50, 80)
        };

        Arrays.sort(students);

        System.out.println(Arrays.toString(students));
    }
}

scoreA를 기준으로 오름차순이 된걸 확인 할 수 있습니다.

compareTo() 메서드에 this.scoreA - o.scoreA 의 뜻은 자기 자신보다 o의 객체가 크다면 두 원소의 위치를 바꿔라는 뜻입니다.

 

꼭 Student[] 배열로 하지 않으셔도되고 List를 이용한 구현도 가능하나 List 자료구조를 사용하신다면
Arrays.sort() 대신 Collections.sort()를 사용하셔야 합니다.

 

Student 클래스에 toString() 메서드는 결과를 보기 좋게 출력하기 위해 사용한 것이니 정렬할 때는 신경쓰지 않으셔도 됩니다.

 

반대로 내림차순 정렬을 하고 싶다면 compareTo() 메서드 부분만 변경하면 됩니다.

@Override
public int compareTo(Student o) {
    return o.scoreA - this.scoreA;
}

 

 

Comparator 인터페이스를 통한 오름차순을 해보겠습니다.

Comparator는 제가 정렬 규칙 그 자체라고 말씀드렸습니다. 

그래서 별도의 규칙을 가진 커스텀클래스를 생성해야 합니다.

class CustomComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.scoreA - o2.scoreA;
    }
}

그리고 커스텀클래스로 생성한 정렬 규칙을 정렬할 때 적용 해주면 됩니다.

public class Sort_Blog {
    public static void main(String[] args) {
        int[][] arr = {{10, 3}, {5, 8}, {15, 10}, {2, 6}, {7, 5}};

        Student[] students = {
                new Student("Kim", 90, 50),
                new Student("Park", 80, 80),
                new Student("Lee", 40, 70),
                new Student("Yong", 70, 100),
                new Student("Ahn", 50, 80)
        };

        Arrays.sort(students, new CustomComparator());

        System.out.println(Arrays.toString(students));
    }

}

class CustomComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.scoreA - o2.scoreA;
    }
}


class Student {
    String name;       // 이름
    int scoreA;        // A과목 성적
    int scoreB;        // B과목 성적

    public Student(String name, int scoreA, int scoreB) {
        this.name = name;
        this.scoreA = scoreA;
        this.scoreB = scoreB;
    }

    @Override
    public String toString(){
        return "{name: " + name + ", scoreA: " + scoreA + ", scoreB: " + scoreB + "}\n";
    }

}

 

[ 2차원 배열 정렬 ]

그럼 이제 우리가 알아본 comparator 인터페이스를 이용해 2차원 배열을 정렬해 보겠습니다.

먼저 2차원 배열 정렬 규칙을 갖는 클래스를 구현해보겠습니다.

class CustomComparator implements Comparator<int[]> {
    @Override
    public int compare(int[] o1, int[] o2) {
        return o1[0] - o2[0];
    }
}

제가 구현한 규칙은 배열의 첫번째 값을 비교해서 정렬하는 것입니다.

 

구현한 클래스를 통해 2차원 배열 정렬을 해보겠습니다.

public class Sort_Blog {
    public static void main(String[] args) {
        int[][] arr = {{10, 3}, {5, 8}, {15, 10}, {2, 6}, {7, 5}};
        
        Arrays.sort(arr, new CustomComparator());

        for (int[] a : arr) {
            System.out.println(Arrays.toString(a));
        }
    }
}

class CustomComparator implements Comparator<int[]> {
    @Override
    public int compare(int[] o1, int[] o2) {
        return o1[0] - o2[0];    // o1과 o2의 배열의 첫번째 원소 값을 비교
    }
}

 

2차원 배열도 정렬이 완료 되었습니다.

이제 거의 다 왔습니다.

여기서 마지막으로 조금만 코드를 수정해 보겠습니다.

 

위에서는 Comparator 인터페이스를 구현한 커스텀 클래스를 통해 구현하였습니다.

그런데 잘 생각해보면 커스텀 클래스는 정렬 규칙이라는 성질만 가지고 있지 클래스의 이름이 굳이 필요하지 않습니다.

 

즉 우리는 배열의 첫번째 원소 값으로 비교할거야 라는 규칙만 있으면 되지 굳이 CustomComparator 라는 클래스의 이름까지는 필요하지 않은겁니다.

 

이럴때 사용할 수 있는 '익명클래스' 라는 것이 있습니다.

말 그대로 익명의 성질을 가진 클래스 라는 뜻입니다.

 

여기서는 '익명클래스' 설명은 넘어가고 코드를 통해 익명클래스를 어떻게 사용하는지

그로인한 코드의 차이가 어떻게 되는지만 살펴보겠습니다.

int[][] arr = {{10, 3}, {5, 8}, {15, 10}, {2, 6}, {7, 5}};
Arrays.sort(arr, new Comparator<int[]>() {
	@Override
	public int compare(int[] o1, int[] o2) {
	return o1[0] - o2[0];
	}
});

Comparator를 상속받아 클래스를 만든것은 같지만 코드의 이름이 정의되어 있지않는 것을 볼 수 있습니다.

이렇게 익명클래스를 처리하면 아주 간단히 클래스를 만들고 처리할 수 있는 장점이 있습니다.

 

또한 Lambda 함수를 통해서도 구현 가능합니다.

int[][] arr = {{10, 3}, {5, 8}, {15, 10}, {2, 6}, {7, 5}};
Arrays.sort(arr, ((o1, o2) -> {
	return o1[0] - o2[0];
}));

 

 

 

부족한 제 글 읽어주셔서 감사합니다. 오늘도 좋은 하루 되세요!!

 

 

 

참고자료

https://coding-factory.tistory.com/549

https://st-lab.tistory.com/243?category=830901 

반응형

'JAVA' 카테고리의 다른 글

[JAVA] 빌더 패턴(Builder pattern)  (0) 2021.11.09
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함