Birden çok alan içeren Collections.sort


83

Üç alanlı "Rapor" nesnelerinin bir listesi var (Tüm Dize türü) -

ReportKey
StudentNumber
School

Bir sıralama kodum var ...

Collections.sort(reportList, new Comparator<Report>() {

@Override
public int compare(final Report record1, final Report record2) {
      return (record1.getReportKey() + record1.getStudentNumber() + record1.getSchool())                      
        .compareTo(record2.getReportKey() + record2.getStudentNumber() + record2.getSchool());
      }

});

Bazı nedenlerden dolayı, sıralı sıraya sahip değilim. Alanlar arasına boşluk bırakılması tavsiye edildi, ama neden?

Kodda yanlış bir şey görüyor musunuz?


Sabit uzunluklu alanlar mı? Record1.getReportKey () "AB" ve record1.getStudentNumber () "CD", ancak record2.getReportKey () "ABCD" ise ne olur?
mellamokb

Sabit uzunluk. Üzgünüm söylemeyi unuttum.
Milli Szabo

Yanıtlar:


138

Kodda yanlış bir şey görüyor musunuz?

Evet. Karşılaştırmadan önce neden üç alanı birbirine ekliyorsunuz?

Muhtemelen şöyle bir şey yapardım: (alanların onları sıralamak istediğiniz sırada olduğunu varsayarak)

@Override public int compare(final Report record1, final Report record2) {
    int c;
    c = record1.getReportKey().compareTo(record2.getReportKey());
    if (c == 0)
       c = record1.getStudentNumber().compareTo(record2.getStudentNumber());
    if (c == 0)
       c = record1.getSchool().compareTo(record2.getSchool());
    return c;
}

Lütfen detaylandırın. Bunu nasıl yapacağım o zaman? Teşekkürler.
Milli Szabo

Merhaba, daha fazla if (c == 0) ekleyebilirsiniz? Doğru olup olmadığını bilmiyorum ama hayır gibi görünüyor, çünkü ilk koşul yerine

10
Anladığını sanmıyorum a.compareTo(b); kural, 0 değerinin eşitliği, negatif tam sayıların bunu a < bve pozitif tam sayıların ve a > biçin bunu temsil etmesidir . Comparable ab
Jason S

1
Çalışmıyor. Bunu denedim ve işe yaramıyor. Yalnızca bir ile çalışırcompareTo
shimatai

110

(orijinal olarak Java'daki nesne listelerini birden çok alana göre sıralamanın Yolları'ndan )

Bu temelde orijinal çalışma kodu

Java 8 lambda'yı kullanma (10 Nisan 2019'da eklendi)

Java 8 bunu lambda ile güzel bir şekilde çözer (Guava ve Apache Commons hala daha fazla esneklik sunabilir):

Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

@ Gaoagong'un aşağıdaki cevabı için teşekkürler .

Buradaki avantajlardan biri, alıcıların tembel olarak değerlendirilmesidir (örneğin getSchool(), sadece ilgili ise değerlendirilir).

Dağınık ve kıvrımlı: Elle sıralama

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        int sizeCmp = p1.size.compareTo(p2.size);  
        if (sizeCmp != 0) {  
            return sizeCmp;  
        }  
        int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);  
        if (nrOfToppingsCmp != 0) {  
            return nrOfToppingsCmp;  
        }  
        return p1.name.compareTo(p2.name);  
    }  
});  

Bu, çok fazla yazma, bakım gerektirir ve hataya açıktır. Tek avantajı, alıcıların yalnızca ilgili olduğunda çağrılmasıdır.

Yansıtıcı yol: BeanComparator ile sıralama

ComparatorChain chain = new ComparatorChain(Arrays.asList(
   new BeanComparator("size"), 
   new BeanComparator("nrOfToppings"), 
   new BeanComparator("name")));

Collections.sort(pizzas, chain);  

Açıkçası, bu daha özlüdür, ancak bunun yerine Dizeleri kullanarak alanlara doğrudan başvurunuzu kaybettiğinizde (tür güvenliği yok, otomatik yeniden düzenleme yok) daha da hataya meyillidir. Şimdi bir alan yeniden adlandırılırsa, derleyici bir sorun bildirmez bile. Dahası, bu çözüm yansıma kullandığından, sıralama çok daha yavaştır.

Oraya gitmek: Google Guava'nın Karşılaştırma Zinciri ile Sıralama

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();  
        // or in case the fields can be null:  
        /* 
        return ComparisonChain.start() 
           .compare(p1.size, p2.size, Ordering.natural().nullsLast()) 
           .compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast()) 
           .compare(p1.name, p2.name, Ordering.natural().nullsLast()) 
           .result(); 
        */  
    }  
});  

Bu çok daha iyidir, ancak en yaygın kullanım durumu için bazı kazan plakası kodu gerektirir: boş değerlere varsayılan olarak daha az değer verilmelidir. Boş alanlar için, Guava'ya bu durumda ne yapacağına fazladan bir yönerge sağlamalısınız. Belirli bir şey yapmak istiyorsanız bu esnek bir mekanizmadır, ancak genellikle varsayılan durumu (yani 1, a, b, z, null) istersiniz.

Ve aşağıdaki yorumlarda belirtildiği gibi, bu alıcıların tümü her karşılaştırma için hemen değerlendirilir.

Apache Commons ile Sıralama CompareToBuilder

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();  
    }  
});  

Guava'nın ComparisonChain'i gibi, bu kütüphane sınıfı birden çok alanda kolaylıkla sıralama yapar, ancak aynı zamanda boş değerler için varsayılan davranışı da tanımlar (yani, 1, a, b, z, null). Bununla birlikte, kendi Karşılaştırıcınızı sağlamadığınız sürece başka bir şey de belirtemezsiniz.

Yine, aşağıdaki yorumlarda belirtildiği gibi, bu alıcıların tümü her karşılaştırma için hemen değerlendirilir.

Böylece

Sonuçta, lezzet ve esneklik ihtiyacı (Guava's ComparisonChain) ile özlü kod (Apache's CompareToBuilder) söz konusudur.

Bonus yöntemi

Ben güzel bir çözüm bulduğunu birleştirir birden karşılaştırıcılar öncelik sırasına göre CodeReview üzerinde bir de MultiComparator:

class MultiComparator<T> implements Comparator<T> {
    private final List<Comparator<T>> comparators;

    public MultiComparator(List<Comparator<? super T>> comparators) {
        this.comparators = comparators;
    }

    public MultiComparator(Comparator<? super T>... comparators) {
        this(Arrays.asList(comparators));
    }

    public int compare(T o1, T o2) {
        for (Comparator<T> c : comparators) {
            int result = c.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }

    public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
        Collections.sort(list, new MultiComparator<T>(comparators));
    }
}

Tabii ki Apache Commons Collections bunun için zaten bir kullanıma sahiptir:

ComparatorUtils.chainedComparator (comparatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));

Temiz kod için mükemmel çözüm ve amaca da hizmet ediyor
cryptonkid

java 8 lambda ile harika
frank

Harika cevap için teşekkürler Benny. Özelliğin doğrudan nesnemin içinde olmadığı bir senaryom var. ancak iç içe geçmiş bir nesne var. bu durumda nasıl yaparım? örneğin burada Collections.sort (reportList, Comparator.comparing (Report :: getReportKey) .thenComparing (Report :: getStudentNumber) .thenComparing (Report :: getSchool)); Rapor Nesnesinin İçinde Bir öğrenci nesnem var ve ardından öğrenci nesnesinin içinde bir öğrenci numaram var. bu durumda burada nasıl sıralayabiliriz? herhangi bir yardım takdir edilecektir.
Madhu Reddy

@MadhuReddy Örnekte yöntem referansları lambda'lar olarak kullanılır, ancak bunun yerine uygun iç içe alanı döndüren uygun bir lambda sağlayabilirsiniz.
Benny Bottema

44

Ben kullanarak bir karşılaştırıcı yapmak istiyorum Guava 'ler ComparisonChain:

public class ReportComparator implements Comparator<Report> {
  public int compare(Report r1, Report r2) {
    return ComparisonChain.start()
        .compare(r1.getReportKey(), r2.getReportKey())
        .compare(r1.getStudentNumber(), r2.getStudentNumber())
        .compare(r1.getSchool(), r2.getSchool())
        .result();
  }
}

20

Bu eski bir soru, bu yüzden Java 8 eşdeğeri görmüyorum. İşte bu özel durum için bir örnek.

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

/**
 * Compares multiple parts of the Report object.
 */
public class SimpleJava8ComparatorClass {

    public static void main(String[] args) {
        List<Report> reportList = new ArrayList<>();
        reportList.add(new Report("reportKey2", "studentNumber2", "school1"));
        reportList.add(new Report("reportKey4", "studentNumber4", "school6"));
        reportList.add(new Report("reportKey1", "studentNumber1", "school1"));
        reportList.add(new Report("reportKey3", "studentNumber2", "school4"));
        reportList.add(new Report("reportKey2", "studentNumber2", "school3"));

        System.out.println("pre-sorting");
        System.out.println(reportList);
        System.out.println();

        Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

        System.out.println("post-sorting");
        System.out.println(reportList);
    }

    private static class Report {

        private String reportKey;
        private String studentNumber;
        private String school;

        public Report(String reportKey, String studentNumber, String school) {
            this.reportKey = reportKey;
            this.studentNumber = studentNumber;
            this.school = school;
        }

        public String getReportKey() {
            return reportKey;
        }

        public void setReportKey(String reportKey) {
            this.reportKey = reportKey;
        }

        public String getStudentNumber() {
            return studentNumber;
        }

        public void setStudentNumber(String studentNumber) {
            this.studentNumber = studentNumber;
        }

        public String getSchool() {
            return school;
        }

        public void setSchool(String school) {
            this.school = school;
        }

        @Override
        public String toString() {
            return "Report{" +
                   "reportKey='" + reportKey + '\'' +
                   ", studentNumber='" + studentNumber + '\'' +
                   ", school='" + school + '\'' +
                   '}';
        }
    }
}

comparingve thenComparingkazanmak için!
asgs

1
Bu 24. minimum android versiyonu için sorar
viper

14

Rapor anahtarına, ardından öğrenci numarasına, sonra okula göre sıralamak istiyorsanız, şöyle bir şey yapmalısınız:

public class ReportComparator implements Comparator<Report>
{
    public int compare(Report r1, Report r2)
    {
        int result = r1.getReportKey().compareTo(r2.getReportKey());
        if (result != 0)
        {
            return result;
        }
        result = r1.getStudentNumber().compareTo(r2.getStudentNumber());
        if (result != 0)
        {
            return result;
        }
        return r1.getSchool().compareTo(r2.getSchool());
    }
}

Bu, elbette değerlerin hiçbirinin boş olamayacağını varsayar - rapor, rapor anahtarı, öğrenci numarası veya okul için boş değerlere izin vermeniz gerekirse daha karmaşık hale gelir.

Eğer iken olabilir boşluklar kullanılarak işe dize birleştirme sürümü almak sen kendini yukarıdaki kodu boşluk vb dahil garip verileri olsaydı, hala garip durumlarda başarısız olur mantıklı istediğiniz kodu ... sonra, ilk rapor anahtar ile karşılaştırmak sadece rapor anahtarları aynıysa öğrenci numarasıyla uğraşın, vb.


6
Bu kodda "yanlış" olmasa da ve bunu anlıyorum. Jason'ın uygulamasını tercih ediyorum çünkü sadece bir dönüş ifadesine sahip olduğundan takip etmesi daha kolay görünüyor.
jzd

Kodunuzu kullanmaya çalıştığımda compareTo()yöntemi kullanamıyor . Lütfen sorunu çözmeme yardım eder misiniz?
viper

@viper: Hayır, çünkü uygulamıyorum Comparable<T>, uyguluyorum Comparator<T>. Neyi başarmaya çalıştığınızı, neyi denediğinizi ya da neyin yanlış gittiğini bilmiyoruz. Belki de yeni bir soru sormalısınız, muhtemelen daha fazla araştırma yaptıktan sonra. (Zaten sorulmuş olabilir.)
Jon Skeet

7

Java 8 Lambda yaklaşımını kullanmanızı öneririm:

List<Report> reportList = new ArrayList<Report>();
reportList.sort(Comparator.comparing(Report::getRecord1).thenComparing(Report::getRecord2));

5

Java8'de birden çok alanla sıralama

package com.java8.chapter1;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static java.util.Comparator.*;



 public class Example1 {

    public static void main(String[] args) {
        List<Employee> empList = getEmpList();


        // Before Java 8 
        empList.sort(new Comparator<Employee>() {

            @Override
            public int compare(Employee o1, Employee o2) {
                int res = o1.getDesignation().compareTo(o2.getDesignation());
                if (res == 0) {
                    return o1.getSalary() > o2.getSalary() ? 1 : o1.getSalary() < o2.getSalary() ? -1 : 0;
                } else {
                    return res;
                }

            }
        });
        for (Employee emp : empList) {
            System.out.println(emp);
        }
        System.out.println("---------------------------------------------------------------------------");

        // In Java 8

        empList.sort(comparing(Employee::getDesignation).thenComparing(Employee::getSalary));
        empList.stream().forEach(System.out::println);

    }
    private static List<Employee> getEmpList() {
        return Arrays.asList(new Employee("Lakshman A", "Consultent", 450000),
                new Employee("Chaitra S", "Developer", 250000), new Employee("Manoj PVN", "Developer", 250000),
                new Employee("Ramesh R", "Developer", 280000), new Employee("Suresh S", "Developer", 270000),
                new Employee("Jaishree", "Opearations HR", 350000));
    }
}

class Employee {
    private String fullName;
    private String designation;
    private double salary;

    public Employee(String fullName, String designation, double salary) {
        super();
        this.fullName = fullName;
        this.designation = designation;
        this.salary = salary;
    }

    public String getFullName() {
        return fullName;
    }

    public String getDesignation() {
        return designation;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return "Employee [fullName=" + fullName + ", designation=" + designation + ", salary=" + salary + "]";
    }

}

empList.sort(comparing(Employee::getDesignation).thenComparing(Employee::getSalary));bu kod bana yardımcı oldu. Teşekkürler
Sumit Badaya

Minimum android sürümü 24 kullanılmasını ister.
viper

4

ÖğrenciNumarası sayısal ise sayısal değil alfanümerik olarak sıralanır. Bekleme

"2" < "11"

Olacak:

"11" < "2"

Bu, sonucun neden yanlış olduğu sorusuna cevap verir.
Florian F

3

Önce ReportKey'e, sonra Öğrenci Numarasına, sonra Okul'a göre sıralamak isterseniz, her bir Dizeyi birleştirmek yerine karşılaştırmanız gerekir. Her bir ReportKey aynı uzunlukta olacak şekilde dizeleri boşluklarla doldurursanız, yönteminiz işe yarayabilir, ancak gerçekten çabaya değmez. Bunun yerine, ReportKeys'i karşılaştırmak için karşılaştırma yöntemini değiştirin, CompareTo 0 döndürürse, StudentNumber'ı ve ardından School'u deneyin.


3

ComparatorArayüzü JDK1.8'de sunulan yöntemlerle kullanın : comparingve thenComparingveya daha somut yöntemler: comparingXXXve thenComparingXXX.

Örneğin, bir kişi listesini önce kimliğine, ardından yaşına, ardından ismine göre sıralamak istersek:

            Comparator<Person> comparator = Comparator.comparingLong(Person::getId)
                    .thenComparingInt(Person::getAge)
                    .thenComparing(Person::getName);
            personList.sort(comparator);

0

Bir nesnedeki 2 alanı, bir String ve bir int'i karşılaştıran ve sıralamak için Collator kullanan tam bir örnek aşağıda verilmiştir.

public class Test {

    public static void main(String[] args) {

        Collator myCollator;
        myCollator = Collator.getInstance(Locale.US);

        List<Item> items = new ArrayList<Item>();

        items.add(new Item("costrels", 1039737, ""));
        items.add(new Item("Costs", 1570019, ""));
        items.add(new Item("costs", 310831, ""));
        items.add(new Item("costs", 310832, ""));

        Collections.sort(items, new Comparator<Item>() {
            @Override
            public int compare(final Item record1, final Item record2) {
                int c;
                //c = record1.item1.compareTo(record2.item1); //optional comparison without Collator                
                c = myCollator.compare(record1.item1, record2.item1);
                if (c == 0) 
                {
                    return record1.item2 < record2.item2 ? -1
                            :  record1.item2 > record2.item2 ? 1
                            : 0;
                }
                return c;
            }
        });     

        for (Item item : items)
        {
            System.out.println(item.item1);
            System.out.println(item.item2);
        }       

    }

    public static class Item
    {
        public String item1;
        public int item2;
        public String item3;

        public Item(String item1, int item2, String item3)
        {
            this.item1 = item1;
            this.item2 = item2;
            this.item3 = item3;
        }       
    }

}

Çıktı:

costrels 1039737

maliyetler 310831

maliyetler 310832

Maliyetler 1570019


0

Yukarıdaki birçok yanıt, aslında çalışmayan tek karşılaştırıcı yöntemle karşılaştırılan alanlara sahiptir. Bazı cevaplar var, ancak her alan için farklı karşılaştırıcılar uygulanmış olsa da, bunu gönderiyorum çünkü bu örnek çok daha net ve anlaşılması daha kolay olacağını düşünüyorum.

class Student{
    Integer bornYear;
    Integer bornMonth;
    Integer bornDay;
    public Student(int bornYear, int bornMonth, int bornDay) {

        this.bornYear = bornYear;
        this.bornMonth = bornMonth;
        this.bornDay = bornDay;
    }
    public Student(int bornYear, int bornMonth) {

        this.bornYear = bornYear;
        this.bornMonth = bornMonth;

    }
    public Student(int bornYear) {

        this.bornYear = bornYear;

    }
    public Integer getBornYear() {
        return bornYear;
    }
    public void setBornYear(int bornYear) {
        this.bornYear = bornYear;
    }
    public Integer getBornMonth() {
        return bornMonth;
    }
    public void setBornMonth(int bornMonth) {
        this.bornMonth = bornMonth;
    }
    public Integer getBornDay() {
        return bornDay;
    }
    public void setBornDay(int bornDay) {
        this.bornDay = bornDay;
    }
    @Override
    public String toString() {
        return "Student [bornYear=" + bornYear + ", bornMonth=" + bornMonth + ", bornDay=" + bornDay + "]";
    }


}
class TestClass
{       

    // Comparator problem in JAVA for sorting objects based on multiple fields 
    public static void main(String[] args)
    {
        int N,c;// Number of threads

        Student s1=new Student(2018,12);
        Student s2=new Student(2018,12);
        Student s3=new Student(2018,11);
        Student s4=new Student(2017,6);
        Student s5=new Student(2017,4);
        Student s6=new Student(2016,8);
        Student s7=new Student(2018);
        Student s8=new Student(2017,8);
        Student s9=new Student(2017,2);
        Student s10=new Student(2017,9);

        List<Student> studentList=new ArrayList<>();
        studentList.add(s1);
        studentList.add(s2);
        studentList.add(s3);
        studentList.add(s4);
        studentList.add(s5);
        studentList.add(s6);
        studentList.add(s7);
        studentList.add(s8);
        studentList.add(s9);
        studentList.add(s10);

        Comparator<Student> byMonth=new Comparator<Student>() {
            @Override
            public int compare(Student st1,Student st2) {
                if(st1.getBornMonth()!=null && st2.getBornMonth()!=null) {
                    return st2.getBornMonth()-st1.getBornMonth();
                }
                else if(st1.getBornMonth()!=null) {
                    return 1;
                }
                else {
                    return -1;
                }
        }};

        Collections.sort(studentList, new Comparator<Student>() {
            @Override
            public int compare(Student st1,Student st2) {
                return st2.getBornYear()-st1.getBornYear();
        }}.thenComparing(byMonth));

        System.out.println("The sorted students list in descending is"+Arrays.deepToString(studentList.toArray()));



    }

}

ÇIKTI

Azalan sırada sıralanmış öğrenci listesi [Student [bornYear = 2018, bornMonth = null, bornDay = null], Student [bornYear = 2018, bornMonth = 12, bornDay = null], Student [bornYear = 2018, bornMonth = 12, bornDay = null], Öğrenci [bornYear = 2018, bornMonth = 11, bornDay = null], Student [bornYear = 2017, bornMonth = 9, bornDay = null], Student [bornYear = 2017, bornMonth = 8, bornDay = null], Student [ bornYear = 2017, bornMonth = 6, bornDay = null], Student [bornYear = 2017, bornMonth = 4, bornDay = null], Student [bornYear = 2017, bornMonth = 2, bornDay = null], Student [bornYear = 2016, bornMonth = 8, bornDay = null]]

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.