Bagaimana cara mengurutkan berdasarkan dua bidang di Jawa?


171

Saya memiliki berbagai objek person (int age; String name;).

Bagaimana saya bisa mengurutkan susunan ini menurut abjad berdasarkan nama dan kemudian berdasarkan usia?

Algoritma mana yang akan Anda gunakan untuk ini?

Jawaban:


221

Anda dapat menggunakan Collections.sortsebagai berikut:

private static void order(List<Person> persons) {

    Collections.sort(persons, new Comparator() {

        public int compare(Object o1, Object o2) {

            String x1 = ((Person) o1).getName();
            String x2 = ((Person) o2).getName();
            int sComp = x1.compareTo(x2);

            if (sComp != 0) {
               return sComp;
            } 

            Integer x1 = ((Person) o1).getAge();
            Integer x2 = ((Person) o2).getAge();
            return x1.compareTo(x2);
    }});
}

List<Persons> sekarang disortir berdasarkan nama, kemudian berdasarkan usia.

String.compareTo"Membandingkan dua string secara leksikografis" - dari dokumen .

Collections.sortadalah metode statis di pustaka Koleksi asli. Itu melakukan penyortiran yang sebenarnya, Anda hanya perlu menyediakan Komparator yang mendefinisikan bagaimana dua elemen dalam daftar Anda harus dibandingkan: ini dicapai dengan menyediakan implementasi comparemetode Anda sendiri .


10
Anda juga dapat menambahkan parameter tipe Comparatoragar tidak harus memasukkan input.
biziclop

@Ralph: Saya telah mengubah jawaban saya, dan menambahkan deskripsi singkat.
Richard H

Karena OP sudah memiliki kelas objeknya sendiri, akan lebih masuk akal untuk diimplementasikan Comparable. Lihat jawabannya oleh @ berry120
Zulaxia

1
Ulasan kode mini: klausa lain redundan karena return pertama bertindak sebagai klausa penjaga. Jawaban yang bagus, memperlakukan saya dengan baik.
Tom Saleeba

30
Karena pertanyaan / jawaban ini masih terhubung, harap dicatat bahwa dengan Java SE 8 ini menjadi lebih sederhana. Jika ada getter, Anda dapat menulisComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparingInt(Person::getAge);
Puce

143

Bagi mereka yang dapat menggunakan Java 8 streaming API, ada pendekatan yang lebih rapi yang didokumentasikan dengan baik di sini: Lambdas dan pengurutan

Saya mencari yang setara dengan C # LINQ:

.ThenBy(...)

Saya menemukan mekanisme di Java 8 di Comparator:

.thenComparing(...)

Jadi di sini adalah cuplikan yang menunjukkan algoritme.

    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

Lihatlah tautan di atas untuk cara yang lebih rapi dan penjelasan tentang bagaimana inferensi tipe Java membuatnya sedikit lebih kikuk untuk didefinisikan dibandingkan dengan LINQ.

Berikut ini adalah unit test lengkap untuk referensi:

@Test
public void testChainedSorting()
{
    // Create the collection of people:
    ArrayList<Person> people = new ArrayList<>();
    people.add(new Person("Dan", 4));
    people.add(new Person("Andi", 2));
    people.add(new Person("Bob", 42));
    people.add(new Person("Debby", 3));
    people.add(new Person("Bob", 72));
    people.add(new Person("Barry", 20));
    people.add(new Person("Cathy", 40));
    people.add(new Person("Bob", 40));
    people.add(new Person("Barry", 50));

    // Define chained comparators:
    // Great article explaining this and how to make it even neater:
    // http://blog.jooq.org/2014/01/31/java-8-friday-goodies-lambdas-and-sorting/
    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

    // Sort the stream:
    Stream<Person> personStream = people.stream().sorted(comparator);

    // Make sure that the output is as expected:
    List<Person> sortedPeople = personStream.collect(Collectors.toList());
    Assert.assertEquals("Andi",  sortedPeople.get(0).name); Assert.assertEquals(2,  sortedPeople.get(0).age);
    Assert.assertEquals("Barry", sortedPeople.get(1).name); Assert.assertEquals(20, sortedPeople.get(1).age);
    Assert.assertEquals("Barry", sortedPeople.get(2).name); Assert.assertEquals(50, sortedPeople.get(2).age);
    Assert.assertEquals("Bob",   sortedPeople.get(3).name); Assert.assertEquals(40, sortedPeople.get(3).age);
    Assert.assertEquals("Bob",   sortedPeople.get(4).name); Assert.assertEquals(42, sortedPeople.get(4).age);
    Assert.assertEquals("Bob",   sortedPeople.get(5).name); Assert.assertEquals(72, sortedPeople.get(5).age);
    Assert.assertEquals("Cathy", sortedPeople.get(6).name); Assert.assertEquals(40, sortedPeople.get(6).age);
    Assert.assertEquals("Dan",   sortedPeople.get(7).name); Assert.assertEquals(4,  sortedPeople.get(7).age);
    Assert.assertEquals("Debby", sortedPeople.get(8).name); Assert.assertEquals(3,  sortedPeople.get(8).age);
    // Andi     : 2
    // Barry    : 20
    // Barry    : 50
    // Bob      : 40
    // Bob      : 42
    // Bob      : 72
    // Cathy    : 40
    // Dan      : 4
    // Debby    : 3
}

/**
 * A person in our system.
 */
public static class Person
{
    /**
     * Creates a new person.
     * @param name The name of the person.
     * @param age The age of the person.
     */
    public Person(String name, int age)
    {
        this.age = age;
        this.name = name;
    }

    /**
     * The name of the person.
     */
    public String name;

    /**
     * The age of the person.
     */
    public int age;

    @Override
    public String toString()
    {
        if (name == null) return super.toString();
        else return String.format("%s : %d", this.name, this.age);
    }
}

Apa yang akan menjadi kompleksitas dari rangkaian pembanding seperti ini? Apakah kita pada dasarnya menyortir setiap kali kita rantai pembanding? Jadi kami melakukan operasi NlogN untuk setiap pembanding?
John Baum

17
Jika ada getter, Anda dapat menulisComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);
Puce

1
Menggunakan thenComparingInt untuk usia (int)
Puce

Sintaks dengan labda '->' tidak bekerja untuk saya. Orang :: getLastName tidak.
Noldy

Setelah membuat komparator, apa yang dibutuhkan untuk membuat stream, mengurutkannya dengan komparator dan kemudian mengumpulkannya? Bisakah Anda menggunakan Collections.sort(people, comparator);saja?
Anish Sana

107

Menggunakan pendekatan Java 8 Streams ...

//Creates and sorts a stream (does not sort the original list)       
persons.stream().sorted(Comparator.comparing(Person::getName).thenComparing(Person::getAge));

Dan pendekatan Java 8 Lambda ...

//Sorts the original list Lambda style
persons.sort((p1, p2) -> {
        if (p1.getName().compareTo(p2.getName()) == 0) {
            return p1.getAge().compareTo(p2.getAge());
        } else {
            return p1.getName().compareTo(p2.getName());
        } 
    });

Akhirnya...

//This is similar SYNTAX to the Streams above, but it sorts the original list!!
persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));

19

Anda perlu menerapkan sendiri Comparator, dan kemudian menggunakannya: misalnya

Arrays.sort(persons, new PersonComparator());

Pembanding Anda dapat terlihat sedikit seperti ini:

public class PersonComparator implements Comparator<? extends Person> {

  public int compare(Person p1, Person p2) {
     int nameCompare = p1.name.compareToIgnoreCase(p2.name);
     if (nameCompare != 0) {
        return nameCompare;
     } else {
       return Integer.valueOf(p1.age).compareTo(Integer.valueOf(p2.age));
     }
  }
}

Pembanding pertama-tama membandingkan nama-nama itu, jika mereka tidak sama dengan itu mengembalikan hasil dari membandingkan mereka, kalau tidak mengembalikan hasil membandingkan ketika membandingkan usia kedua orang.

Kode ini hanya konsep: karena kelasnya tidak berubah Anda bisa memikirkan membangun singleton, bukannya membuat contoh baru untuk setiap penyortiran.


16

Anda dapat menggunakan pendekatan Java 8 Lambda untuk mencapai ini. Seperti ini:

persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));

15

Mintalah kelas orang Anda menerapkan Comparable<Person>dan kemudian menerapkan metode compareTo, misalnya:

public int compareTo(Person o) {
    int result = name.compareToIgnoreCase(o.name);
    if(result==0) {
        return Integer.valueOf(age).compareTo(o.age);
    }
    else {
        return result;
    }
}

Yang pertama akan mengurutkan berdasarkan nama (huruf besar-kecil) dan kemudian berdasarkan usia. Anda kemudian dapat menjalankan Arrays.sort()atau Collections.sort()pada koleksi atau larik objek Person.


Saya biasanya lebih suka ini daripada membuat Komparator, karena, seperti kata berry120, Anda kemudian dapat mengurutkan dengan metode bawaan, daripada perlu selalu menggunakan Komparator kustom Anda.
Zulaxia

4

Jambu biji ComparisonChainmenyediakan cara yang bersih untuk melakukannya. Lihat tautan ini .

Utilitas untuk melakukan pernyataan perbandingan berantai. Sebagai contoh:

   public int compareTo(Foo that) {
     return ComparisonChain.start()
         .compare(this.aString, that.aString)
         .compare(this.anInt, that.anInt)
         .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
         .result();
   }

4

Anda bisa melakukan ini:

List<User> users = Lists.newArrayList(
  new User("Pedro", 12), 
  new User("Maria", 10), 
  new User("Rafael",12)
);

users.sort(
  Comparator.comparing(User::getName).thenComparing(User::getAge)
);

3

Gunakan Comparatorlalu letakkan benda ke CollectiondalamnyaCollections.sort();

class Person {

    String fname;
    String lname;
    int age;

    public Person() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getFname() {
        return fname;
    }

    public void setFname(String fname) {
        this.fname = fname;
    }

    public String getLname() {
        return lname;
    }

    public void setLname(String lname) {
        this.lname = lname;
    }

    public Person(String fname, String lname, int age) {
        this.fname = fname;
        this.lname = lname;
        this.age = age;
    }

    @Override
    public String toString() {
        return fname + "," + lname + "," + age;
    }
}

public class Main{

    public static void main(String[] args) {
        List<Person> persons = new java.util.ArrayList<Person>();
        persons.add(new Person("abc3", "def3", 10));
        persons.add(new Person("abc2", "def2", 32));
        persons.add(new Person("abc1", "def1", 65));
        persons.add(new Person("abc4", "def4", 10));
        System.out.println(persons);
        Collections.sort(persons, new Comparator<Person>() {

            @Override
            public int compare(Person t, Person t1) {
                return t.getAge() - t1.getAge();
            }
        });
        System.out.println(persons);

    }
}

3

Buat sebanyak mungkin pembanding. Setelah itu, panggil metode "thenComparing" untuk setiap kategori pesanan. Ini adalah cara yang dilakukan oleh Streams. Lihat:

//Sort by first and last name
System.out.println("\n2.Sort list of person objects by firstName then "
                                        + "by lastName then by age");
Comparator<Person> sortByFirstName 
                            = (p, o) -> p.firstName.compareToIgnoreCase(o.firstName);
Comparator<Person> sortByLastName 
                            = (p, o) -> p.lastName.compareToIgnoreCase(o.lastName);
Comparator<Person> sortByAge 
                            = (p, o) -> Integer.compare(p.age,o.age);

//Sort by first Name then Sort by last name then sort by age
personList.stream().sorted(
    sortByFirstName
        .thenComparing(sortByLastName)
        .thenComparing(sortByAge)
     ).forEach(person->
        System.out.println(person));        

Lihat: Urutkan objek yang ditentukan pengguna pada beberapa bidang - Pembanding (lambda stream)


3

Saya akan berhati-hati ketika menggunakan Guava ComparisonChainkarena itu membuat instance per elemen telah dibandingkan sehingga Anda akan melihat penciptaan N x Log Nrantai perbandingan hanya untuk membandingkan jika Anda menyortir, atauN contoh jika Anda mengulangi dan memeriksa kesetaraan.

Saya lebih suka membuat statis Comparatormenggunakan Java 8 API terbaru jika memungkinkan atau OrderingAPI Guava yang memungkinkan Anda untuk melakukan itu, berikut adalah contoh dengan Java 8:

import java.util.Comparator;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsLast;

private static final Comparator<Person> COMPARATOR = Comparator
  .comparing(Person::getName, nullsLast(naturalOrder()))
  .thenComparingInt(Person::getAge);

@Override
public int compareTo(@NotNull Person other) {
  return COMPARATOR.compare(this, other);
}

Berikut ini cara menggunakan OrderingAPI Guava : https://github.com/google/guava/wiki/OrderingExplained


1
"... membuat instance dari itu per elemen telah dibandingkan ..." - ini tidak benar. Setidaknya dalam versi modern Guava, memanggil comparemetode tidak membuat apa-apa, tetapi mengembalikan salah satu contoh tunggal LESS, GREATERatau ACTIVEtergantung pada hasil perbandingan. Ini adalah pendekatan yang sangat dioptimalkan dan tidak menambah memori atau overhead kinerja.
Yoory N.

Iya; Saya baru saja melihat kode sumbernya sekarang, saya mengerti maksud Anda, tetapi saya akan lebih cenderung menggunakan API perbandingan Java 8 yang baru demi ketergantungan.
Guido Medina

2

Atau Anda dapat mengeksploitasi fakta bahwa Collections.sort()(atau Arrays.sort()) stabil (tidak menyusun ulang elemen yang sama) dan menggunakan aComparator untuk mengurutkan berdasarkan usia terlebih dahulu dan kemudian yang lain untuk mengurutkan berdasarkan nama.

Dalam kasus khusus ini, ini bukan ide yang sangat bagus tetapi jika Anda harus dapat mengubah urutan sortir dalam runtime, ini mungkin berguna.


2

Anda dapat menggunakan Pembanding serial generik untuk mengurutkan koleksi berdasarkan beberapa bidang.

import org.apache.commons.lang3.reflect.FieldUtils;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
* @author MaheshRPM
*/
public class SerialComparator<T> implements Comparator<T> {
List<String> sortingFields;

public SerialComparator(List<String> sortingFields) {
    this.sortingFields = sortingFields;
}

public SerialComparator(String... sortingFields) {
    this.sortingFields = Arrays.asList(sortingFields);
}

@Override
public int compare(T o1, T o2) {
    int result = 0;
    try {
        for (String sortingField : sortingFields) {
            if (result == 0) {
                Object value1 = FieldUtils.readField(o1, sortingField, true);
                Object value2 = FieldUtils.readField(o2, sortingField, true);
                if (value1 instanceof Comparable && value2 instanceof Comparable) {
                    Comparable comparable1 = (Comparable) value1;
                    Comparable comparable2 = (Comparable) value2;
                    result = comparable1.compareTo(comparable2);
                } else {
                    throw new RuntimeException("Cannot compare non Comparable fields. " + value1.getClass()
                            .getName() + " must implement Comparable<" + value1.getClass().getName() + ">");
                }
            } else {
                break;
            }
        }
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
    return result;
}
}

0
Arrays.sort(persons, new PersonComparator());



import java.util.Comparator;

public class PersonComparator implements Comparator<? extends Person> {

    @Override
    public int compare(Person o1, Person o2) {
        if(null == o1 || null == o2  || null == o1.getName() || null== o2.getName() ){
            throw new NullPointerException();
        }else{
            int nameComparisonResult = o1.getName().compareTo(o2.getName());
            if(0 == nameComparisonResult){
                return o1.getAge()-o2.getAge();
            }else{
                return nameComparisonResult;
            }
        }
    }
}


class Person{
    int age; String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Versi terbaru:

public class PersonComparator implements Comparator<? extends Person> {

   @Override
   public int compare(Person o1, Person o2) {

      int nameComparisonResult = o1.getName().compareToIgnoreCase(o2.getName());
      return 0 == nameComparisonResult?o1.getAge()-o2.getAge():nameComparisonResult;

   }
 }

Penanganan pengecualian nullpointer bagus dan memperjelas bahwa itu tidak akan bekerja dengan nol, tetapi itu tetap akan dinaikkan
Ralph

Anda memang benar. Saya baru-baru ini memeriksa beberapa nilai untuk menyalin dari satu tempat ke tempat lain dan sekarang saya terus melakukannya di mana-mana.
fmucar

0

Untuk kelas Bookseperti ini:

package books;

public class Book {

    private Integer id;
    private Integer number;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getNumber() {
        return number;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "book{" +
                "id=" + id +
                ", number=" + number +
                ", name='" + name + '\'' + '\n' +
                '}';
    }
}

menyortir kelas utama dengan benda tiruan

package books;

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


public class Main {

    public static void main(String[] args) {
        System.out.println("Hello World!");

        Book b = new Book();

        Book c = new Book();

        Book d = new Book();

        Book e = new Book();

        Book f = new Book();

        Book g = new Book();
        Book g1 = new Book();
        Book g2 = new Book();
        Book g3 = new Book();
        Book g4 = new Book();




        b.setId(1);
        b.setNumber(12);
        b.setName("gk");

        c.setId(2);
        c.setNumber(12);
        c.setName("gk");

        d.setId(2);
        d.setNumber(13);
        d.setName("maths");

        e.setId(3);
        e.setNumber(3);
        e.setName("geometry");

        f.setId(3);
        f.setNumber(34);
        b.setName("gk");

        g.setId(3);
        g.setNumber(11);
        g.setName("gk");

        g1.setId(3);
        g1.setNumber(88);
        g1.setName("gk");
        g2.setId(3);
        g2.setNumber(91);
        g2.setName("gk");
        g3.setId(3);
        g3.setNumber(101);
        g3.setName("gk");
        g4.setId(3);
        g4.setNumber(4);
        g4.setName("gk");





        List<Book> allBooks = new ArrayList<Book>();

        allBooks.add(b);
        allBooks.add(c);
        allBooks.add(d);
        allBooks.add(e);
        allBooks.add(f);
        allBooks.add(g);
        allBooks.add(g1);
        allBooks.add(g2);
        allBooks.add(g3);
        allBooks.add(g4);



        System.out.println(allBooks.size());


        Collections.sort(allBooks, new Comparator<Book>() {

            @Override
            public int compare(Book t, Book t1) {
                int a =  t.getId()- t1.getId();

                if(a == 0){
                    int a1 = t.getNumber() - t1.getNumber();
                    return a1;
                }
                else
                    return a;
            }
        });
        System.out.println(allBooks);

    }


   }

0

Saya tidak yakin apakah jelek untuk menulis kompartor di dalam kelas Person dalam kasus ini. Apakah itu seperti ini:

public class Person implements Comparable <Person> {

    private String lastName;
    private String firstName;
    private int age;

    public Person(String firstName, String lastName, int BirthDay) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = BirthDay;
    }

    public int getAge() {
        return age;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    @Override
    public int compareTo(Person o) {
        // default compareTo
    }

    @Override
    public String toString() {
        return firstName + " " + lastName + " " + age + "";
    }

    public static class firstNameComperator implements Comparator<Person> {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.firstName.compareTo(o2.firstName);
        }
    }

    public static class lastNameComperator implements Comparator<Person> {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.lastName.compareTo(o2.lastName);
        }
    }

    public static class ageComperator implements Comparator<Person> {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.age - o2.age;
        }
    }
}
public class Test {
    private static void print() {
       ArrayList<Person> list = new ArrayList();
        list.add(new Person("Diana", "Agron", 31));
        list.add(new Person("Kay", "Panabaker", 27));
        list.add(new Person("Lucy", "Hale", 28));
        list.add(new Person("Ashley", "Benson", 28));
        list.add(new Person("Megan", "Park", 31));
        list.add(new Person("Lucas", "Till", 27));
        list.add(new Person("Nicholas", "Hoult", 28));
        list.add(new Person("Aly", "Michalka", 28));
        list.add(new Person("Adam", "Brody", 38));
        list.add(new Person("Chris", "Pine", 37));
        Collections.sort(list, new Person.lastNameComperator());
        Iterator<Person> it = list.iterator();
        while(it.hasNext()) 
            System.out.println(it.next().toString()); 
     }  
}    
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.