Comment trier par deux champs en Java?


171

J'ai un tableau d'objets person (int age; String name;) .

Comment puis-je trier ce tableau par ordre alphabétique par nom, puis par âge?

Quel algorithme utiliseriez-vous pour cela?

Réponses:


221

Vous pouvez utiliser Collections.sortcomme suit:

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> est maintenant trié par nom, puis par âge.

String.compareTo"Compare deux chaînes lexicographiquement" - à partir de la documentation .

Collections.sortest une méthode statique dans la bibliothèque native Collections. Il fait le tri proprement dit, il vous suffit de fournir un comparateur qui définit comment deux éléments de votre liste doivent être comparés: ceci est réalisé en fournissant votre propre implémentation de la compareméthode.


10
Vous pouvez également ajouter un paramètre de type à Comparatorpour éviter d'avoir à convertir les entrées.
biziclop

@Ralph: J'ai modifié ma réponse et ajouté une brève description.
Richard H

Puisque l'OP a déjà sa propre classe d'objets, il serait plus logique de l'implémenter Comparable. Voir la réponse de @ berry120
Zulaxia

1
Mini revue de code: la clause else est redondante car le premier retour agit comme une clause de garde. Excellente réponse cependant, a fonctionné un régal pour moi.
Tom Saleeba

30
Comme cette question / réponse est toujours liée, veuillez noter qu'avec Java SE 8, cela est devenu beaucoup plus simple. S'il y a des getters, vous pouvez écrireComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparingInt(Person::getAge);
Puce

143

Pour ceux qui sont capables d'utiliser l'API de streaming Java 8, il existe une approche plus soignée qui est bien documentée ici: Lambdas et tri

Je cherchais l'équivalent du C # LINQ:

.ThenBy(...)

J'ai trouvé le mécanisme en Java 8 sur le comparateur:

.thenComparing(...)

Voici donc l'extrait de code qui illustre l'algorithme.

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

Consultez le lien ci-dessus pour une manière plus soignée et une explication sur la façon dont l'inférence de type de Java la rend un peu plus difficile à définir par rapport à LINQ.

Voici le test unitaire complet pour référence:

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

Quelle serait la complexité de ce type de chaînage de comparateurs? Sommes-nous essentiellement en train de trier chaque fois que nous enchaînons les comparateurs? Alors on fait une opération NlogN pour chaque comparateur?
John Baum

17
S'il y a des getters, vous pouvez écrireComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);
Puce

1
Utiliser thenComparingIntpour l'âge (int)
Puce

La syntaxe avec le labda '->' ne fonctionne pas pour moi. Le Person :: getLastName le fait.
Noldy

Après avoir créé le comparateur, quel est le besoin de créer un flux, de le trier avec le comparateur puis de le collecter? Pouvez-vous simplement utiliser à la Collections.sort(people, comparator);place?
Anish Sana du

107

Utilisation de l'approche Java 8 Streams ...

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

Et l'approche 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());
        } 
    });

Enfin...

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

19

Vous devez implémenter le vôtre Comparator, puis l'utiliser: par exemple

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

Votre comparateur pourrait ressembler un peu à ceci:

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

Le comparateur compare d'abord les noms, s'ils ne sont pas égaux, il renvoie le résultat de leur comparaison, sinon il renvoie le résultat de la comparaison lors de la comparaison des âges des deux personnes.

Ce code n'est qu'un brouillon: comme la classe est immuable, vous pourriez penser à en créer un singleton, en créant plutôt une nouvelle instance pour chaque tri.


16

Vous pouvez utiliser l'approche Java 8 Lambda pour y parvenir. Comme ça:

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

15

Demandez à votre classe de personne d'implémenter Comparable<Person>, puis d'implémenter la méthode compareTo, par exemple:

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

Cela triera d'abord par nom (insensible à la casse), puis par âge. Vous pouvez ensuite exécuter Arrays.sort()ou Collections.sort()sur la collection ou le tableau d'objets Person.


Je préfère généralement cela à la création d'un comparateur, car, comme le dit berry120, vous pouvez ensuite trier avec des méthodes intégrées, plutôt que de toujours utiliser votre comparateur personnalisé.
Zulaxia

4

Guava's ComparisonChainfournit une manière propre de le faire. Reportez-vous à ce lien .

Un utilitaire pour exécuter une instruction de comparaison chaînée. Par exemple:

   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

Vous pouvez faire comme ceci:

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

Utilisez Comparatorpuis mettez des objets dans Collection, puisCollections.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

Créez autant de comparateurs que nécessaire. Ensuite, appelez la méthode "thenComparing" pour chaque catégorie de commande. C'est une façon de faire par Streams. Voir:

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

Look: Trier l'objet défini par l'utilisateur sur plusieurs champs - Comparateur (flux lambda)


3

Je ferais attention lors de l'utilisation de Guava ComparisonChaincar cela crée une instance de celui-ci par élément comparé afin que vous regardiez une création de N x Log Nchaînes de comparaison juste pour comparer si vous triez, ouN instances si vous itérez et vérifiez l'égalité.

Je créerais plutôt un statique en Comparatorutilisant la dernière API Java 8 si possible ou l' OrderingAPI Guava qui vous permet de le faire, voici un exemple avec 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);
}

Voici comment utiliser l' OrderingAPI de Guava : https://github.com/google/guava/wiki/OrderingExplained


1
"... crée une instance de celui-ci par élément comparé ..." - ce n'est pas vrai. Au moins dans les versions modernes de Guava, l'appel à la compareméthode ne crée rien, mais renvoie l'une des instances singleton LESS, GREATERou ACTIVEselon le résultat de la comparaison. Il s'agit d'une approche hautement optimisée et n'ajoute aucune surcharge de mémoire ou de performances.
Yoory N.

Oui; Je viens de regarder le code source maintenant, je vois ce que vous voulez dire, mais je serais plus enclin à utiliser la nouvelle API de comparaison Java 8 pour le bien des dépendances.
Guido Medina

2

Ou vous pouvez exploiter le fait que Collections.sort()(ou Arrays.sort()) est stable (il ne réorganise pas les éléments égaux) et utiliser unComparator pour trier d'abord par âge, puis un autre pour trier par nom.

Dans ce cas précis, ce n'est pas une très bonne idée, mais si vous devez être en mesure de modifier l'ordre de tri au moment de l'exécution, cela peut être utile.


2

Vous pouvez utiliser le comparateur en série générique pour trier les collections selon plusieurs champs.

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

}

Version mise à jour:

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;

   }
 }

La gestion des exceptions nullpointer est agréable et indique clairement qu'elle ne fonctionnerait pas avec null, mais elle serait quand même déclenchée
Ralph

Tu as tout à fait raison. J'ai récemment utilisé pour vérifier certaines valeurs à copier d'un endroit à l'autre et maintenant je continue à le faire partout.
fmucar le

0

Pour une classe Bookcomme celle-ci:

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

tri de la classe principale avec des objets simulés

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

Je ne suis pas sûr que ce soit moche d'écrire le compartiment dans la classe Person dans ce cas. A fait ça comme ça:

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()); 
     }  
}    
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.