Làm cách nào để sắp xếp danh sách theo các tham số khác nhau ở các thời điểm khác nhau


95

Tôi có một lớp được đặt tên Personvới nhiều thuộc tính, ví dụ:

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

Rất nhiều-đối Persontượng được lưu trữ trong một ArrayList<Person>. Tôi muốn sắp xếp danh sách này theo nhiều tham số sắp xếp và khác nhau theo thời gian. Ví dụ, một lần tôi có thể muốn sắp xếp theo thứ tự nametăng dần rồi addressgiảm dần, và lần khác chỉ bằng cách idgiảm dần.

Và tôi không muốn tạo các phương pháp sắp xếp của riêng mình (tức là tôi muốn sử dụng Collections.sort(personList, someComparator). Giải pháp thanh lịch nhất đạt được điều này là gì?

Câu trả lời:


193

Tôi nghĩ cách tiếp cận enum của bạn về cơ bản là hợp lý, nhưng các câu lệnh switch thực sự cần một cách tiếp cận hướng đối tượng hơn. Xem xét:

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;
            }
        };
    }
}

Một ví dụ về cách sử dụng (với nhập tĩnh).

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

12
+1 cách sử dụng enum thông minh. Tôi thích sự kết hợp thanh lịch mà bạn làm với enums, "giảm dần" và "Composite". Tôi đoán rằng điều trị giá trị null bị thiếu, nhưng rất dễ dàng để thêm theo cách tương tự như "giảm dần".
KLE

1
Nhiều câu trả lời hay cung cấp thực phẩm để suy nghĩ. Vì không có câu trả lời nào nổi bật là phương án thay thế rõ ràng, tôi sẽ chấp nhận câu trả lời này vì tôi thích sự sang trọng, nhưng tôi khuyến khích bất kỳ ai xem câu trả lời này cũng nên kiểm tra các phương pháp tiếp cận khác.
runaros

1
@TheLittleNaruto, phương thức so sánh trả về một số âm nếu o2 lớn hơn, một số dương nếu o1 lớn hơn và bằng 0 nếu chúng bằng nhau. Nhân với -1 sẽ đảo ngược kết quả, đó là ý tưởng của sự giảm dần (ngược lại với thứ tự tăng dần thông thường), trong khi để nó bằng 0 nếu chúng bằng nhau.
Yishai

5
Lưu ý rằng vì Java 8, bạn có thể sử dụng comparator.reversed()để giảm dần và bạn có thể sử dụng comparator1.thenComparing(comparator2)để so sánh chuỗi.
GuiSim

1
@JohnBaum, nếu bộ so sánh đầu tiên trả về kết quả khác 0, kết quả đó được trả về và phần còn lại của chuỗi không được thực thi.
Yishai

26

Bạn có thể tạo bộ so sánh cho từng thuộc tính mà bạn có thể muốn sắp xếp và sau đó thử "chuỗi so sánh" :-) như thế này:

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;
    }
}

Bạn có thể sẽ nhận được cảnh báo khi nó được sử dụng (mặc dù trong JDK7, bạn có thể ngăn chặn nó).
Tom Hawtin - tackline 14/09/09

Tôi cũng thích điều này. Bạn có thể cung cấp một mẫu về cách sử dụng điều này với ví dụ đã cho?
runaros 15/09/09

@runaros: Sử dụng bộ so sánh từ câu trả lời của KLE: Collections.sort (/ * Bộ sưu tập <Người> * / người, ChainedComparator mới (NAME_ASC_ADRESS_DESC, ID_DESC));
Janus Troelsen

16

Một cách là tạo một Comparatorđối số nhận danh sách các thuộc tính để sắp xếp, như ví dụ này cho thấy.

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;
        }
    }
}

Sau đó, nó có thể được sử dụng trong mã ví dụ như sau:

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

Đúng. Nếu bạn muốn mã của mình rất chung chung, enum của bạn chỉ có thể chỉ định thuộc tính để đọc (bạn có thể sử dụng phản chiếu để lấy thuộc tính bằng cách sử dụng tên enum) và bạn có thể chỉ định phần còn lại bằng enum thứ hai: ASC & DESC, và có thể là một phần ba (NULL_FIRST hoặc NULL_LAST).
KLE 14/09/09

8

Một cách tiếp cận sẽ là soạn Comparators. Đây có thể là một phương thức thư viện (tôi chắc rằng nó tồn tại ở đâu đó ngoài kia).

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;
        }
        [...]
    };
}

Sử dụng:

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

Ngoài ra, hãy lưu ý rằng đó Collections.sortlà một loại ổn định. Nếu hiệu suất không hoàn toàn quan trọng, bạn sắp xếp thứ tự phụ trước thứ tự chính.

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

Tuy nhiên, phương pháp tiếp cận thông minh có thể được thực hiện chung chung hơn, sao cho nó bao gồm một số lượng biến so sánh, có thể bao gồm cả số không?
runaros 14/09/09

compose(nameComparator, compose(addressComparator, idComparator))Điều đó sẽ tốt hơn một chút nếu Java có các phương thức mở rộng.
Tom Hawtin - tackline 14/09/09

4

So sánh cho phép bạn làm điều đó rất dễ dàng và tự nhiên. Bạn có thể tạo các trường hợp so sánh đơn lẻ, trong chính lớp Người của bạn hoặc trong lớp Dịch vụ được liên kết với nhu cầu của bạn.
Ví dụ, sử dụng các lớp bên trong ẩn danh:

    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... 

Nếu bạn có nhiều, bạn cũng có thể cấu trúc mã trình so sánh của mình theo bất kỳ cách nào bạn muốn. Ví dụ, bạn có thể:

  • kế thừa từ một trình so sánh khác,
  • có một CompositeComparator tổng hợp một số bộ so sánh hiện có
  • có một bộ so sánh Null xử lý các trường hợp rỗng, sau đó ủy quyền cho một bộ so sánh khác
  • Vân vân...

2

Tôi nghĩ rằng việc kết hợp các trình sắp xếp với lớp Person, như trong câu trả lời của bạn, không phải là một ý tưởng hay, vì nó kết hợp phép so sánh (thường là theo hướng kinh doanh) và đối tượng mô hình để gần nhau. Mỗi lần bạn muốn thay đổi / thêm thứ gì đó, trình sắp xếp, bạn cần phải chạm vào lớp người, đây thường là điều bạn không muốn làm.

Sử dụng Dịch vụ hoặc thứ gì đó tương tự, cung cấp các phiên bản Bộ so sánh, như KLE đề xuất, nghe có vẻ linh hoạt và dễ mở rộng hơn.


Đối với tôi, điều này dẫn đến khớp nối chặt chẽ bởi vì bằng cách nào đó, lớp chủ sở hữu bộ so sánh ĐÃ biết cấu trúc dữ liệu chi tiết của một lớp Người (về cơ bản là các trường của lớp Người để so sánh) và nếu bạn muốn thay đổi điều gì đó trong các trường Người thì điều này dẫn đến việc theo dõi giống nhau thay đổi trong lớp so sánh. Tôi đoán các trình so sánh Person nên là một phần của lớp Person. blog.sanaulla.info/2008/06/26/…
Stan

2

Cách tiếp cận của tôi được xây dựng dựa trên Yishai. Lỗ hổng chính là không có cách nào để sắp xếp tăng dần đầu tiên cho một thuộc tính và sau đó giảm dần cho một thuộc tính khác. Điều này không thể được thực hiện với các phép liệt kê. Đối với điều đó, tôi đã sử dụng các lớp học. Bởi vì Sắp xếp thứ tự phụ thuộc mạnh mẽ vào kiểu mà tôi ưa thích để triển khai nó như một lớp người bên trong.

Lớp 'Người' với lớp bên trong 'Sắp xếp':

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();
    }
}

Một ví dụ để sử dụng lớp Person và SortOrder của nó:

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());
         }
    }
}

oRUMOo


Sự phức tạp của loại chuỗi các trình so sánh này sẽ như thế nào? Về cơ bản chúng ta có sắp xếp mỗi khi chúng ta xâu chuỗi các trình so sánh không? Vì vậy, chúng tôi thực hiện một phép toán * log (n) cho mỗi bộ so sánh?
John Baum

0

Gần đây tôi đã viết Bộ so sánh để sắp xếp nhiều trường trong bản ghi Chuỗi được phân tách. Nó cho phép bạn xác định dấu phân cách, cấu trúc bản ghi và các quy tắc sắp xếp (một số trong số đó là dành riêng cho loại). Bạn có thể sử dụng điều này bằng cách chuyển đổi bản ghi Người thành một chuỗi được phân tách.

Thông tin bắt buộc được đưa vào chính Bộ so sánh, theo chương trình hoặc thông qua tệp XML.

XML được xác thực bởi một gói tệp XSD nhúng. Ví dụ: bên dưới là bố cục bản ghi được phân cách bằng tab với bốn trường (hai trong số đó có thể sắp xếp):

<?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>

Sau đó, bạn sẽ sử dụng điều này trong java như sau:

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

Thư viện có thể được tìm thấy ở đây:

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


0

Giả sử có một lớp Coordinateở đó và người ta phải sắp xếp nó theo cả hai cách theo tọa độ X và tọa độ Y. Hai bộ so sánh khác nhau là cần thiết cho nó. Dưới đây là mẫu

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
        };
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.