Bagaimana cara mengurutkan daftar berdasarkan parameter yang berbeda pada waktu yang berbeda

95

Saya memiliki kelas yang diberi nama Persondengan beberapa properti, misalnya:

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

Banyak Person-objects disimpan di file ArrayList<Person>. Saya ingin mengurutkan daftar ini dengan beberapa parameter sortir, dan berbeda dari waktu ke waktu. Misalnya saya mungkin suatu saat ingin mengurutkan dengan namenaik lalu addressturun, dan di lain waktu hanya dengan idturun.

Dan saya tidak ingin membuat metode sortir saya sendiri (yaitu, saya ingin menggunakan Collections.sort(personList, someComparator). Apa solusi paling elegan yang dapat mencapai ini?

runaros
sumber

Jawaban:

193

Saya pikir pendekatan enum Anda pada dasarnya bagus, tetapi pernyataan sakelar benar-benar membutuhkan pendekatan yang lebih berorientasi objek. Mempertimbangkan:

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

Contoh penggunaan (dengan impor statis).

public static void main(String[] args) {
    List<Person> list = null;
    Collections.sort(list, decending(getComparator(NAME_SORT, ID_SORT)));
}
Yishai
sumber
12
+1 penggunaan enum dengan cerdas. Saya suka kombinasi elegan yang Anda lakukan dengan enum, "descending" dan "Composite". Saya kira perlakuan nilai nol hilang, tetapi mudah untuk menambahkan cara yang sama seperti "turun".
KLE
1
Banyak jawaban bagus yang memberikan bahan pemikiran. Karena tidak ada jawaban yang menonjol sebagai alternatif yang jelas, saya akan menerima yang ini karena saya suka keanggunan, tetapi saya mendorong siapa pun yang melihat jawaban ini untuk memeriksa pendekatan lain juga.
runaros
1
@TheLittleNaruto, metode bandingkan mengembalikan angka negatif jika o2 lebih besar, positif jika o1 lebih besar, dan nol jika sama. Mengalikan dengan -1 membalikkan hasil, yang merupakan gagasan menurun (kebalikan dari urutan naik biasanya), sementara membiarkannya nol jika nilainya sama.
Yishai
5
Perhatikan bahwa sejak Java 8 Anda dapat menggunakan comparator.reversed()untuk turun dan Anda dapat menggunakan comparator1.thenComparing(comparator2)untuk merangkai pembanding.
GuiSim
1
@JohnBaum, jika pembanding pertama mengembalikan hasil bukan nol, hasil itu dikembalikan dan sisa rantai tidak dieksekusi.
Yishai
26

Anda dapat membuat komparator untuk setiap properti yang mungkin ingin Anda sortir dan kemudian mencoba "rangkaian pembanding" :-) seperti ini:

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;
    }
}
Tadeusz Kopec
sumber
Anda mungkin akan mendapatkan peringatan saat itu digunakan (meskipun di JDK7 Anda harus bisa menekannya).
Tom Hawtin - tackline
Saya juga suka ini. Dapatkah Anda memberikan contoh tentang cara menggunakan ini dengan contoh yang diberikan?
runaros
@runaros: Menggunakan pembanding dari jawaban KLE: Collections.sort (/ * Collection <Person> * / people, new ChainedComparator (NAME_ASC_ADRESS_DESC, ID_DESC));
Janus Troelsen
16

Salah satu caranya adalah dengan membuat Comparatoryang mengambil sebagai argumen daftar properti untuk diurutkan, seperti yang ditunjukkan contoh ini.

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

Ini kemudian dapat digunakan dalam kode misalnya seperti ini:

cp = Person.getComparator(Person.SortParameter.ADDRESS_ASCENDING,
                          Person.SortParameter.NAME_DESCENDING);
Collections.sort(personList, cp);
runaros
sumber
Iya. Jika Anda ingin kode Anda menjadi sangat umum, enum Anda hanya dapat menentukan properti yang akan dibaca (Anda dapat menggunakan refleksi untuk mendapatkan properti menggunakan nama enum), dan Anda dapat menentukan sisanya dengan enum kedua: ASC & DESC, dan mungkin yang ketiga (NULL_FIRST atau NULL_LAST).
KLE
8

Salah satu pendekatannya adalah dengan menulis Comparators. Ini bisa menjadi metode perpustakaan (saya yakin itu ada di suatu tempat di luar sana).

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

Menggunakan:

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

Atau, perhatikan bahwa Collections.sortini adalah jenis yang stabil. Jika kinerja tidak terlalu penting, Anda mengurutkan menjadi urutan sekunder sebelum yang utama.

Collections.sort(people, addressComparator);
Collections.sort(people, nameComparator);
Tom Hawtin - tackline
sumber
Pendekatan yang cerdas, bagaimanapun, dapatkah itu dibuat lebih umum, seperti itu mencakup sejumlah variabel pembanding, mungkin termasuk nol?
runaros
compose(nameComparator, compose(addressComparator, idComparator))Itu akan terbaca sedikit lebih baik jika Java memiliki metode ekstensi.
Tom Hawtin - tackline
4

Pembanding memungkinkan Anda melakukannya dengan sangat mudah dan alami. Anda dapat membuat satu instance komparator, baik di kelas Person Anda sendiri, atau di kelas Layanan yang terkait dengan kebutuhan Anda.
Contoh, menggunakan kelas dalam anonim:

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

Jika Anda memiliki banyak, Anda juga dapat menyusun kode komparator sesuka Anda. Misalnya, Anda dapat:

  • mewarisi dari pembanding lain,
  • memiliki CompositeComparator yang menggabungkan beberapa komparator yang ada
  • memiliki NullComparator yang menangani kasus null, lalu mendelegasikannya ke pembanding lain
  • dll ...
KLE
sumber
2

Saya pikir menggabungkan penyortir ke kelas Person, seperti dalam jawaban Anda, bukanlah ide yang baik, karena menggabungkan perbandingan (biasanya didorong oleh bisnis) dan objek model untuk berdekatan satu sama lain. Setiap kali Anda ingin mengubah / menambahkan sesuatu yang penyortir, Anda perlu menyentuh kelas orang, yang biasanya sesuatu yang tidak ingin Anda lakukan.

Menggunakan Service atau sesuatu yang serupa, yang menyediakan instance Comparator, seperti yang diusulkan KLE, terdengar jauh lebih fleksibel dan dapat diperluas.

gia
sumber
Bagi saya ini mengarah pada kopling ketat karena entah bagaimana kelas pemegang komparator HARUS mengetahui struktur data terperinci dari kelas Person (pada dasarnya bidang kelas Person mana yang akan dibandingkan) dan jika Anda akan mengubah sesuatu di bidang Persons, ini mengarah ke jejak yang sama perubahan di kelas pembanding. Saya kira pembanding Person harus menjadi bagian dari kelas Person. blog.sanaulla.info/2008/06/26/…
Stan
2

Pendekatan saya dibangun di atas Yishai. Kesenjangan utamanya adalah tidak ada cara untuk mengurutkan menaik pertama untuk suatu atribut dan setelah itu menurun untuk atribut lainnya. Ini tidak bisa dilakukan dengan pencacahan. Untuk itu saya menggunakan kelas. Karena SortOrder sangat bergantung pada tipe yang saya suka untuk mengimplementasikannya sebagai kelas dalam orang.

Kelas 'Orang' dengan kelas dalam 'SortOrder':

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

Contoh penggunaan kelas Person dan SortOrder-nya:

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

oRUMOo
sumber
Apa kompleksitas dari rangkaian pembanding semacam ini? Apakah kita pada dasarnya menyortir setiap kali kita merantai pembanding? Jadi kita melakukan operasi * log (n) untuk setiap pembanding?
John Baum
0

Saya baru-baru ini menulis Comparator untuk mengurutkan beberapa bidang dalam catatan String yang dipisahkan. Ini memungkinkan Anda untuk menentukan pemisah, struktur rekaman, dan aturan pengurutan (beberapa di antaranya khusus untuk tipe). Anda dapat menggunakan ini dengan mengonversi rekaman Person menjadi String yang dipisahkan.

Informasi yang diperlukan disebarkan ke Comparator itu sendiri, baik secara terprogram atau melalui file XML.

XML divalidasi oleh paket file XSD yang disematkan. Misalnya, di bawah ini adalah tata letak rekaman yang dibatasi tab dengan empat bidang (dua di antaranya dapat diurutkan):

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

Anda kemudian akan menggunakan ini di java seperti ini:

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

Perpustakaan dapat ditemukan di sini:

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

Constantin
sumber
0

Misalkan sebuah kelas Coordinateada di sana dan seseorang harus mengurutkannya dengan kedua cara menurut koordinat X dan koordinat Y. Dibutuhkan dua pembanding berbeda untuk itu. Berikut ini contohnya

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
        };
    }
}
ravi ranjan.dll
sumber