web-dev-qa-db-fra.com

Collections.sort avec plusieurs champs

J'ai une liste d'objets "Report" avec trois champs (type All String) -

ReportKey
StudentNumber
School

J'ai un code de tri va comme-

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

});

Pour une raison quelconque, je n'ai pas l'ordre trié. On a conseillé de mettre des espaces entre les champs, mais pourquoi?

Voyez-vous quelque chose qui ne va pas avec le code?

57
Milli Szabo

Voyez-vous quelque chose qui ne va pas avec le code?

Oui. Pourquoi additionnez-vous les trois champs avant de les comparer?

Je ferais probablement quelque chose comme ceci: (en supposant que les champs sont dans l'ordre dans lequel vous souhaitez les trier)

@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;
}
101
Jason S

Je ferais un comparateur en utilisant Guava 's 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();
  }
}
44
ColinD

(from Façons de trier des listes d'objets en Java en fonction de plusieurs champs )

Code de travail dans this Gist

En désordre et alambiqué: le tri à la main

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

Cela nécessite beaucoup de frappe, de maintenance et est sujet aux erreurs.

La méthode de réflexion: Tri avec BeanComparator

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

Collections.sort(pizzas, chain);  

Évidemment, ceci est plus concis, mais encore plus sujet aux erreurs car vous perdez votre référence directe aux champs en utilisant plutôt des chaînes (pas de sécurité de type, de refactorisation automatique). Désormais, si un champ est renommé, le compilateur ne signalera même pas un problème. De plus, comme cette solution utilise la réflexion, le tri est beaucoup plus lent.

Comment y arriver: trier avec la chaîne de comparaison Google Guava

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

C'est beaucoup mieux, mais nécessite le code de plaque de chaudière pour le cas d'utilisation le plus courant: les valeurs nulles doivent être évaluées moins par défaut. Pour les champs nuls, vous devez fournir une directive supplémentaire à Guava pour savoir quoi faire dans ce cas. C'est un mécanisme flexible si vous voulez faire quelque chose de spécifique, mais vous voulez souvent le cas par défaut (c'est-à-dire 1, a, b, z, null).

Tri avec Apache Commons 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();  
    }  
});  

Comme pour ComparisonChain de Guava, cette classe de bibliothèque trie facilement plusieurs champs, mais définit également le comportement par défaut des valeurs NULL (c.-à-d. 1, a, b, z, null). Cependant, vous ne pouvez rien spécifier d’autre, à moins que vous ne fournissiez votre propre comparateur.

Ainsi

En fin de compte, il en va de la saveur et du besoin de flexibilité (Guava’s ComparisonChain) par rapport à un code concis (Apache’s CompareToBuilder).

Méthode bonus

J'ai trouvé une solution de Nice qui combine plusieurs comparateurs par ordre de priorité sur CodeReview dans une 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));
    }
}

Bien sûr, Apache Commons Collections a déjà une utilité:

ComparatorUtils.chainedComparator (comparatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
39
Benny Bottema

C'est une vieille question, je ne vois donc pas d'équivalent Java 8. Voici un exemple pour ce cas particulier.

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 + '\'' +
                   '}';
        }
    }
}
15
gaoagong

Si vous voulez trier par clé de rapport, puis numéro d'étudiant, puis école, vous devriez faire quelque chose comme ceci:

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

Bien entendu, cela suppose qu'aucune des valeurs ne peut être nulle. Cela devient plus compliqué si vous devez autoriser les valeurs nulles pour le rapport, la clé de rapport, le numéro d'élève ou l'école.

Bien que vous puissiez obtenir que la version de concaténation de chaînes fonctionne avec des espaces, elle échouerait quand même dans des cas étranges si vous aviez des données impaires qui incluaient des espaces, etc. Le code ci-dessus est le code logical que vous voulez .. comparez d’abord par clé de rapport, puis ne vous embêtez pas avec le numéro d’étudiant si les clés de rapport sont les mêmes, etc.

13
Jon Skeet

Tri avec plusieurs champs en Java8

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 + "]";
    }

}
5
Lakshman Miani

Je suggère d'utiliser l'approche Java 8 Lambda:

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

Utilisez l'interface Comparator avec les méthodes introduites dans JDK1.8: comparing et thenComparing, ou des méthodes plus concrètes: comparingXXX et thenComparingXXX.

Par exemple, si nous voulons d'abord trier une liste de personnes par leur identifiant, alors leur âge, leur nom:

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

Si vous souhaitez effectuer un tri basé sur ReportKey, puis sur Numéro d'étudiant, puis sur Ecole, vous devez comparer chaque chaîne au lieu de les concaténer. Votre méthode fonctionnera peut-être si vous insérez des espaces dans les chaînes afin que chaque rapportKey ait la même longueur, etc., mais cela ne vaut pas la peine. Au lieu de cela, changez simplement la méthode de comparaison pour comparer les ReportKeys. Si compareTo renvoie 0, essayez StudentNumber, puis School.

3
jzd

Si le numéro d'étudiant est numérique, il ne sera pas trié numérique mais alphanumérique. 

"2" < "11"

ce sera:

"11" < "2"
3
FrVaBe

Beaucoup de réponses ci-dessus ont comparé les champs dans la méthode du comparateur unique qui ne fonctionne pas réellement. Il existe quelques réponses mais avec différents comparateurs mis en œuvre pour chaque domaine, je publie ceci car cet exemple serait beaucoup plus clair et simple à comprendre, je le crois.

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



    }

}

SORTIE

La liste des élèves triés par ordre décroissant est [Étudiant [bornYear = 2018, bornMonth = null, bornDay = null], Étudiant [bornYear = 2018, bornMonth = 12, bornDay = null], Étudiant [bornYear = 2018, bornMonth = 12, bornDay = null], Étudiant [bornYear = 2018, bornMonth = 11, bornDay = null], Étudiant [bornYear = 2017, bornMonth = 9, bornDay = null], Étudiant [bornYear = 2017, bornMonth = 8, bornDay = null], Étudiant [ bornYear = 2017, bornMonth = 6, bornDay = null], étudiant [bornYear = 2017, bornMonth = 4, bornDay = null], étudiant [bornYear = 2017, bornMonth = 2, bornDay = null], étudiant [bornYear = 2016, bornMonth = 8, bornDay = null]]

0
Naseer Mohammad

Voici un exemple complet comparant 2 champs dans un objet, un String et un int, utilisant également Collator pour trier.

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

}

Sortie:

costrels 1039737

coûts 310831

coûts 310832

Coûts 1570019

0
live-love