94 votes

Comment trier une liste avec plusieurs paramètres de tri ?

J'ai une classe nommée Person avec plusieurs propriétés, par exemple :

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

Beaucoup de Person -Les objets sont stockés dans un ArrayList<Person> . Je souhaite trier cette liste en fonction de plusieurs paramètres de tri, différents selon les cas. Par exemple, je pourrais vouloir trier par name ascendant et ensuite address en descendant, et une autre fois juste en id en descendant.

Et je ne veux pas créer mes propres méthodes de tri (c'est-à-dire que je veux utiliser Collections.sort(personList, someComparator) . Quelle est la solution la plus élégante pour y parvenir ?

193voto

Yishai Points 42417

Je pense que votre approche enum est fondamentalement saine, mais les instructions switch ont vraiment besoin d'une approche plus orientée objet. Pensez-y :

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

Un exemple d'utilisation (avec une importation statique).

public static void main(String[] args) {
    List<Person> list = null;
    Collections.sort(list, decending(getComparator(NAME_SORT, ID_SORT)));
}

26voto

Tadeusz Kopec Points 7625

Vous pouvez créer des comparateurs pour chacune des propriétés que vous souhaitez trier, puis essayer de "chaîner les comparateurs" :-) comme ceci :

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

16voto

runaros Points 664

Une façon de procéder consiste à créer un Comparator qui prend comme arguments une liste de propriétés à trier, comme le montre cet exemple.

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

Il peut ensuite être utilisé dans le code, par exemple comme ceci :

cp = Person.getComparator(Person.SortParameter.ADDRESS_ASCENDING,
                          Person.SortParameter.NAME_DESCENDING);
Collections.sort(personList, cp);

8voto

Une approche consisterait à composer Comparator s. Cela pourrait être une méthode de bibliothèque (je suis sûr qu'elle existe quelque part).

public static <T> Comparator<T> compose(
    final Comparator<? super T> primary,
    final Comparator<? super T> secondary
) {
    return Comparator<T> {
        public int compare(T a, T b) {
            int result = primary.compare(a, b);
            return result==0 ? secondary.compare(a, b) : result;
        }
        [...]
    }
}

Utilisez :

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

Alternativement, notez que Collections.sort est une sorte stable. Si la performance n'est pas absolument cruciale, vous trierez l'ordre secondaire avant l'ordre primaire.

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

4voto

KLE Points 11711

Les comparateurs vous permettent de le faire très facilement et naturellement. Vous pouvez créer des instances uniques de comparateurs, soit dans votre classe Personne elle-même, soit dans une classe Service associée à votre besoin.
Exemples, utilisation de classes internes anonymes :

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

Si vous en avez beaucoup, vous pouvez aussi structurer le code de vos comparateurs comme vous le souhaitez. Par exemple, vous pourriez :

  • hériter d'un autre comparateur,
  • avoir un CompositeComparator qui regroupe certains comparateurs existants
  • avoir un NullComparator qui gère les cas de nullité, puis délègue à un autre comparateur
  • etc...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X