다른 시간에 다른 매개 변수로 목록을 정렬하는 방법


95

Person예를 들어 여러 속성으로 명명 된 클래스가 있습니다 .

public class Person {
    private int id;
    private String name, address;
    // Many more properties.
}

많은 Person-object가 ArrayList<Person>. 이 목록을 여러 개의 정렬 매개 변수로 정렬하고 싶고, 때때로 다릅니다. 예를 들어 한 번은 name오름차순과 address내림차순 으로 정렬 하고 다른 시간에는 내림차순 으로 정렬하고 싶을 수 있습니다 id.

그리고 나만의 정렬 방법을 만들고 싶지 않습니다 (예 :를 사용하고 싶습니다 Collections.sort(personList, someComparator).이를 달성하는 가장 우아한 솔루션은 무엇입니까?

답변:


193

enum 접근 방식은 기본적으로 건전하다고 생각하지만 switch 문에는 실제로 더 많은 객체 지향 접근 방식이 필요합니다. 치다:

enum PersonComparator implements Comparator<Person> {
    ID_SORT {
        public int compare(Person o1, Person o2) {
            return Integer.valueOf(o1.getId()).compareTo(o2.getId());
        }},
    NAME_SORT {
        public int compare(Person o1, Person o2) {
            return o1.getFullName().compareTo(o2.getFullName());
        }};

    public static Comparator<Person> decending(final Comparator<Person> other) {
        return new Comparator<Person>() {
            public int compare(Person o1, Person o2) {
                return -1 * other.compare(o1, o2);
            }
        };
    }

    public static Comparator<Person> getComparator(final PersonComparator... multipleOptions) {
        return new Comparator<Person>() {
            public int compare(Person o1, Person o2) {
                for (PersonComparator option : multipleOptions) {
                    int result = option.compare(o1, o2);
                    if (result != 0) {
                        return result;
                    }
                }
                return 0;
            }
        };
    }
}

사용 예 (정적 가져 오기 사용).

public static void main(String[] args) {
    List<Person> list = null;
    Collections.sort(list, decending(getComparator(NAME_SORT, ID_SORT)));
}

12
열거 형의 스마트 한 사용 +1. 열거 형, "내림차순"및 "복합"과 함께하는 우아한 조합이 마음에 듭니다. null 값 처리가 누락 된 것 같지만 "내림차순"과 같은 방식으로 쉽게 추가 할 수 있습니다.
KLE 09-09-15

1
생각할 음식을 제공하는 많은 좋은 답변. 명확한 대안으로 눈에 띄는 대답이 없었기 때문에 나는 우아함이 마음에 들어서 이것을 받아 들일 것이지만, 나는이 대답을 보는 사람에게 다른 접근법도 확인하도록 촉구합니다.
runaros

1
@TheLittleNaruto, 비교 메소드는 o2가 더 크면 음수를, o1이 더 크면 양수를, 같으면 0을 반환합니다. -1을 곱하면 내림차순 (일반적인 오름차순과 반대)의 개념 인 결과가 반전되고 동일하면 0으로 남겨집니다.
Yishai

5
Java 8부터는 comparator.reversed()내림차순으로 사용할 수 있고 comparator1.thenComparing(comparator2)비교기를 연결 하는 데 사용할 수 있습니다 .
GuiSim

1
@JohnBaum, 첫 번째 비교기가 0이 아닌 결과를 반환하면 해당 결과가 반환되고 나머지 체인은 실행되지 않습니다.
Yishai

26

정렬하려는 각 속성에 대한 비교기를 만든 다음 다음과 같이 "비교 자 연결"을 시도 할 수 있습니다. :-)

public class ChainedComparator<T> implements Comparator<T> {
    private List<Comparator<T>> simpleComparators; 
    public ChainedComparator(Comparator<T>... simpleComparators) {
        this.simpleComparators = Arrays.asList(simpleComparators);
    }
    public int compare(T o1, T o2) {
        for (Comparator<T> comparator : simpleComparators) {
            int result = comparator.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }
}

이것이 사용될 때 경고를 받게 될 것입니다 (JDK7에서는 그것을 억제 할 수 있어야합니다).
Tom Hawtin-tackline

나도 좋아합니다. 주어진 예제에서 이것을 사용하는 방법에 대한 샘플을 제공 할 수 있습니까?
runaros

@runaros : KLE의 답변에서 비교기 사용 : Collections.sort (/ * Collection <Person> * / people, new ChainedComparator (NAME_ASC_ADRESS_DESC, ID_DESC));
Janus Troelsen 2011 년

16

한 가지 방법은 Comparator이 예제에서 볼 수 있듯이 정렬 할 속성 목록을 인수로 취하는를 만드는 것 입니다.

public class Person {
    private int id;
    private String name, address;

    public static Comparator<Person> getComparator(SortParameter... sortParameters) {
        return new PersonComparator(sortParameters);
    }

    public enum SortParameter {
        ID_ASCENDING, ID_DESCENDING, NAME_ASCENDING,
        NAME_DESCENDING, ADDRESS_ASCENDING, ADDRESS_DESCENDING
    }

    private static class PersonComparator implements Comparator<Person> {
        private SortParameter[] parameters;

        private PersonComparator(SortParameter[] parameters) {
            this.parameters = parameters;
        }

        public int compare(Person o1, Person o2) {
            int comparison;
            for (SortParameter parameter : parameters) {
                switch (parameter) {
                    case ID_ASCENDING:
                        comparison = o1.id - o2.id;
                        if (comparison != 0) return comparison;
                        break;
                    case ID_DESCENDING:
                        comparison = o2.id - o1.id;
                        if (comparison != 0) return comparison;
                        break;
                    case NAME_ASCENDING:
                        comparison = o1.name.compareTo(o2.name);
                        if (comparison != 0) return comparison;
                        break;
                    case NAME_DESCENDING:
                        comparison = o2.name.compareTo(o1.name);
                        if (comparison != 0) return comparison;
                        break;
                    case ADDRESS_ASCENDING:
                        comparison = o1.address.compareTo(o2.address);
                        if (comparison != 0) return comparison;
                        break;
                    case ADDRESS_DESCENDING:
                        comparison = o2.address.compareTo(o1.address);
                        if (comparison != 0) return comparison;
                        break;
                }
            }
            return 0;
        }
    }
}

그런 다음 다음과 같은 코드에서 사용할 수 있습니다.

cp = Person.getComparator(Person.SortParameter.ADDRESS_ASCENDING,
                          Person.SortParameter.NAME_DESCENDING);
Collections.sort(personList, cp);

예. 코드가 매우 일반적 이길 원하면 열거 형은 읽을 속성 만 지정할 수 있으며 (reflection을 사용하여 열거 형 이름을 사용하여 속성을 가져올 수 있음) 나머지는 두 번째 열거 형인 ASC & DESC로 지정할 수 있습니다. 세 번째 (NULL_FIRST 또는 NULL_LAST) 일 수 있습니다.
KLE

8

한 가지 접근 방식은 Comparators 를 작성하는 것 입니다. 이것은 라이브러리 메서드 일 수 있습니다 (저기 어딘가에 존재한다고 확신합니다).

public static <T> Comparator<T> compose(
    final Comparator<? super T> primary,
    final Comparator<? super T> secondary
) {
    return new Comparator<T>() {
        public int compare(T a, T b) {
            int result = primary.compare(a, b);
            return result==0 ? secondary.compare(a, b) : result;
        }
        [...]
    };
}

사용하다:

Collections.sort(people, compose(nameComparator, addressComparator));

또는 Collections.sort안정적인 정렬입니다. 성능이 절대적으로 중요하지 않은 경우 기본 순서보다 2 차 순서로 정렬합니다.

Collections.sort(people, addressComparator);
Collections.sort(people, nameComparator);

그러나 영리한 접근 방식은 0을 포함하여 가변 수의 비교기를 포함하도록 더 일반적으로 만들 수 있습니까?
runaros

compose(nameComparator, compose(addressComparator, idComparator))Java에 확장 메서드가 있으면 좀 더 잘 읽을 수 있습니다.
Tom Hawtin-tackline

4

비교기를 사용하면 매우 쉽고 자연스럽게 할 수 있습니다. Person 클래스 자체 또는 필요와 관련된 Service 클래스에서 비교기의 단일 인스턴스를 만들 수 있습니다.
익명 내부 클래스를 사용하는 예 :

    public static final Comparator<Person> NAME_ASC_ADRESS_DESC
     = new Comparator<Person>() {
      public int compare(Person p1, Person p2) {
         int nameOrder = p1.getName().compareTo(p2.getName);
         if(nameOrder != 0) {
           return nameOrder;
         }
         return -1 * p1.getAdress().comparedTo(p2.getAdress());
         // I use explicit -1 to be clear that the order is reversed
      }
    };

    public static final Comparator<Person> ID_DESC
     = new Comparator<Person>() {
      public int compare(Person p1, Person p2) {
         return -1 * p1.getId().comparedTo(p2.getId());
         // I use explicit -1 to be clear that the order is reversed
      }
    };
    // and other comparator instances as needed... 

많은 경우 원하는 방식으로 비교기 코드를 구성 할 수도 있습니다 . 예를 들어 다음과 같이 할 수 있습니다.

  • 다른 비교기에서 상속,
  • 기존 비교기를 집계하는 CompositeComparator가 있습니다.
  • null 케이스를 처리하는 NullComparator가 있고 다른 비교기에 위임
  • 기타...

2

나는 당신의 대답과 같이 분류기를 Person 클래스에 결합하는 것은 좋은 생각이 아니라고 생각합니다. 왜냐하면 그것은 비교 (일반적으로 비즈니스 중심)와 모델 객체를 서로 가깝게 결합하기 때문입니다. 분류기에서 무언가를 변경 / 추가하고 싶을 때마다 사람 클래스를 터치해야합니다. 일반적으로 원하지 않는 일입니다.

KLE가 제안한 것과 같은 Comparator 인스턴스를 제공하는 서비스 또는 이와 유사한 것을 사용하면 훨씬 유연하고 확장 할 수 있습니다.


나에게 이것은 어떻게 든 비교기 홀더 클래스가 Person 클래스의 자세한 데이터 구조를 알고 있기 때문에 (기본적으로 비교할 Person 클래스의 필드) 그리고 Persons 필드에서 무언가를 변경하면 동일한 추적으로 이어지기 때문에 긴밀한 결합으로 이어집니다. 비교기 클래스의 변경. Person 비교자는 Person 클래스의 일부 여야한다고 생각합니다. blog.sanaulla.info/2008/06/26/…
Stan

2

내 접근 방식은 Yishai를 기반으로합니다. 주된 차이는 속성에 대해 먼저 오름차순으로 정렬하고 그 후에 다른 속성에 대해 내림차순으로 정렬 할 수있는 방법이 없다는 것입니다. 열거 형으로는 수행 할 수 없습니다. 그것을 위해 나는 수업을 사용했습니다. SortOrder는 내부 클래스로 구현하기를 선호하는 유형에 크게 의존하기 때문입니다.

내부 클래스가 'SortOrder'인 'Person'클래스 :

import java.util.Comparator;

public class Person {
    private int id;
    private String firstName; 
    private String secondName;

    public Person(int id, String firstName, String secondName) {
        this.id = id;
        this.firstName = firstName;
        this.secondName = secondName;   
    }

    public abstract static class SortOrder implements Comparator<Person> {
        public static SortOrder PERSON_ID = new SortOrder() {
            public int compare(Person p1, Person p2) {
                return Integer.valueOf(p1.getId()).compareTo(p2.getId());
            }
        };
        public static SortOrder PERSON_FIRST_NAME = new SortOrder() {
            public int compare(Person p1, Person p2) {
                return p1.getFirstName().compareTo(p2.getFirstName());
            }
        };
        public static SortOrder PERSON_SECOND_NAME = new SortOrder() {
            public int compare(Person p1, Person p2) {
                return p1.getSecondName().compareTo(p2.getSecondName());
            }
        };

        public static SortOrder invertOrder(final SortOrder toInvert) {
            return new SortOrder() {
                public int compare(Person p1, Person p2) {
                    return -1 * toInvert.compare(p1, p2);
                }
            };
        }

        public static Comparator<Person> combineSortOrders(final SortOrder... multipleSortOrders) {
            return new Comparator<Person>() {
                public int compare(Person p1, Person p2) {
                    for (SortOrder personComparator: multipleSortOrders) {
                        int result = personComparator.compare(p1, p2);
                        if (result != 0) {
                            return result;
                        }
                    }
                    return 0;
                }
            };
        }
    }

    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();

        result.append("Person with id: ");
        result.append(id);
        result.append(" and firstName: ");
        result.append(firstName);
        result.append(" and secondName: ");
        result.append(secondName);
        result.append(".");

        return result.toString();
    }
}

Person 클래스와 그 SortOrder를 사용하는 예 :

import static multiplesortorder.Person.SortOrder.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import multiplesortorder.Person;

public class Application {

    public static void main(String[] args) {
        List<Person> listPersons = new ArrayList<Person>(Arrays.asList(
                 new Person(0, "...", "..."),
                 new Person(1, "...", "...")
             ));

         Collections.sort(listPersons, combineSortOrders(PERSON_FIRST_NAME, invertOrder(PERSON_ID)));

         for (Person p: listPersons) {
             System.out.println(p.toString());
         }
    }
}

오 루무


이러한 종류의 비교기 연결의 복잡성은 무엇입니까? 우리는 비교기를 연결할 때마다 본질적으로 정렬합니까? 그래서 우리는 각 비교기에 대해 * log (n) 연산을합니까?
John Baum 2015

0

최근에 구분 된 문자열 레코드 내에서 여러 필드를 정렬하기 위해 비교기를 작성했습니다. 구분 기호, 레코드 구조 및 정렬 규칙 (일부는 유형별)을 정의 할 수 있습니다. Person 레코드를 구분 된 문자열로 변환하여이를 사용할 수 있습니다.

필수 정보는 프로그래밍 방식으로 또는 XML 파일을 통해 비교기 자체에 시드됩니다.

XML은 패키지에 포함 된 XSD 파일에 의해 유효성이 검사됩니다. 예를 들어, 아래는 4 개의 필드 (이 중 2 개는 정렬 가능)가있는 탭으로 구분 된 레코드 레이아웃입니다.

<?xml version="1.0" encoding="ISO-8859-1"?> 
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <delimiter>&#009;</delimiter>

    <column xsi:type="Decimal">
        <name>Column One</name>
    </column>

    <column xsi:type="Integer">
        <name>Column Two</name>
    </column>

    <column xsi:type="String">
        <name>Column Three</name>
        <sortOrder>2</sortOrder>
        <trim>true</trim>
        <caseSensitive>false</caseSensitive>        
        <stripAccents>true</stripAccents>
    </column>

    <column xsi:type="DateTime">
        <name>Column Four</name>
        <sortOrder>1</sortOrder>
        <ascending>true</ascending>
        <nullLowSortOrder>true</nullLowSortOrder>
        <trim>true</trim>
        <pattern>yyyy-MM-dd</pattern>
    </column>

</row>

그런 다음 Java에서 다음과 같이 사용합니다.

Comparator<String> comparator = new RowComparator(
              new XMLStructureReader(new File("layout.xml")));

도서관은 여기에서 찾을 수 있습니다 :

http://sourceforge.net/projects/multicolumnrowcomparator/


0

클래스 Coordinate가 있고 X 좌표와 Y 좌표에 따라 두 가지 방식으로 분류해야 한다고 가정합니다 . 이를 위해서는 두 개의 다른 비교기가 필요합니다. 아래는 샘플입니다

class Coordinate
{

    int x,y;

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

    static Comparator<Coordinate> getCoordinateXComparator() {
        return new Comparator<Coordinate>() {

            @Override
            public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
                if(Coordinate1.x < Coordinate2.x)
                    return 1;
                else
                    return 0;
            }
            // compare using Coordinate x
        };
    }

    static Comparator<Coordinate> getCoordinateYComparator() {
        return new Comparator<Coordinate>() {

            @Override
            public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
                if(Coordinate1.y < Coordinate2.y)
                    return 1;
                else
                    return 0;
            }
            // compare using Coordinate y
        };
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.