web-dev-qa-db-fra.com

Comment trier une liste selon différents paramètres à différentes heures

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

Un grand nombre d'objets Person- sont stockés dans un ArrayList<Person>. Je veux trier cette liste selon plusieurs paramètres de tri, et différents de temps en temps. Par exemple, je pourrais un jour vouloir trier name par ordre croissant, puis address par ordre décroissant, et une autre fois, simplement par id par ordre décroissant.

Et je ne veux pas créer mes propres méthodes de tri (c’est-à-dire, je veux utiliser Collections.sort(personList, someComparator). Quelle est la solution la plus élégante qui réalise cela?

93
runaros

Je pense que votre approche enum est fondamentalement valable, mais les déclarations de commutateur ont vraiment besoin d’une approche plus orientée objet. Considérer:

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

Vous pouvez créer des comparateurs pour chacune des propriétés que vous souhaitez trier, puis essayez "Chaînage des comparateurs" :-)

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;
    }
}
26
Tadeusz Kopec

Une solution consiste à créer une variable Comparator qui prend comme argument une liste de propriétés à trier, comme le montre l'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);
16
runaros

Une approche serait de composer Comparators. Cela pourrait être une méthode de bibliothèque (je suis sûr que cela existe quelque part).

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

Utilisation:

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

Sinon, notez que Collections.sort est un type stable. Si la performance n'est pas absolument cruciale, vous pouvez trier l'ordre secondaire avant le primaire.

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

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 Person elle-même, soit dans une classe Service associée à votre besoin.
Exemples, utilisant des 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 également structurer le code de votre comparateur comme bon vous semble. Par exemple, vous pourriez:

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

Mon approche est basée sur celle de Yishai. La principale lacune est qu’il n’ya aucun moyen de trier d’abord par ordre croissant pour un attribut et ensuite par décroissance pour un autre. Cela ne peut pas être fait avec des énumérations. Pour cela j'ai utilisé des cours. Parce que SortOrder dépend fortement du type que j'ai préféré implémenter comme classe interne de personne. 

La classe 'Person' avec la classe interne 'SortOrder':

import Java.util.Comparator;

public class Person {
    private int id;
    private String firstName; 
    private String secondName;

    public Person(int id, String firstName, String secondName) {
        this.id = id;
        this.firstName = firstName;
        this.secondName = secondName;   
    }

    public abstract static class SortOrder implements Comparator<Person> {
        public static SortOrder PERSON_ID = new SortOrder() {
            public int compare(Person p1, Person p2) {
                return Integer.valueOf(p1.getId()).compareTo(p2.getId());
            }
        };
        public static SortOrder PERSON_FIRST_NAME = new SortOrder() {
            public int compare(Person p1, Person p2) {
                return p1.getFirstName().compareTo(p2.getFirstName());
            }
        };
        public static SortOrder PERSON_SECOND_NAME = new SortOrder() {
            public int compare(Person p1, Person p2) {
                return p1.getSecondName().compareTo(p2.getSecondName());
            }
        };

        public static SortOrder invertOrder(final SortOrder toInvert) {
            return new SortOrder() {
                public int compare(Person p1, Person p2) {
                    return -1 * toInvert.compare(p1, p2);
                }
            };
        }

        public static Comparator<Person> combineSortOrders(final SortOrder... multipleSortOrders) {
            return new Comparator<Person>() {
                public int compare(Person p1, Person p2) {
                    for (SortOrder personComparator: multipleSortOrders) {
                        int result = personComparator.compare(p1, p2);
                        if (result != 0) {
                            return result;
                        }
                    }
                    return 0;
                }
            };
        }
    }

    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();

        result.append("Person with id: ");
        result.append(id);
        result.append(" and firstName: ");
        result.append(firstName);
        result.append(" and secondName: ");
        result.append(secondName);
        result.append(".");

        return result.toString();
    }
}

Un exemple d'utilisation de la classe Person et de son ordre de tri:

import static multiplesortorder.Person.SortOrder.*;

import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Collections;
import Java.util.List;

import multiplesortorder.Person;

public class Application {

    public static void main(String[] args) {
        List<Person> listPersons = new ArrayList<Person>(Arrays.asList(
                 new Person(0, "...", "..."),
                 new Person(1, "...", "...")
             ));

         Collections.sort(listPersons, combineSortOrders(PERSON_FIRST_NAME, invertOrder(PERSON_ID)));

         for (Person p: listPersons) {
             System.out.println(p.toString());
         }
    }
}

oRUMOo

2
oRUMOo

Je pense que coupler les trieurs à la classe Person, comme dans votre réponse, n’est pas une bonne idée, car il associe la comparaison (généralement axée sur les entreprises) et l’objet modèle à une proximité les uns des autres ... à chaque fois que vous le souhaitez. Si vous changez/ajoutez quelque chose à la trieuse, vous devez toucher la classe de personnes, ce que vous ne voulez généralement pas faire.

L'utilisation d'un service ou de quelque chose de similaire, qui fournit des instances de Comparator, comme proposé par KLE, semble beaucoup plus flexible et extensible.

2
gia

Supposons qu'il existe une classe Coordinate et qu'il faut la trier dans les deux sens en fonction de la coordonnée X et de la coordonnée Y. Deux comparateurs de differnet sont nécessaires pour cela. Voici l'échantillon

class Coordinate
{

    int x,y;

    public Coordinate(int x, int y) {
        this.x = x;
        this.y = y;
    }

    static Comparator<Coordinate> getCoordinateXComparator() {
        return new Comparator<Coordinate>() {

            @Override
            public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
                if(Coordinate1.x < Coordinate2.x)
                    return 1;
                else
                    return 0;
            }
            // compare using Coordinate x
        };
    }

    static Comparator<Coordinate> getCoordinateYComparator() {
        return new Comparator<Coordinate>() {

            @Override
            public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
                if(Coordinate1.y < Coordinate2.y)
                    return 1;
                else
                    return 0;
            }
            // compare using Coordinate y
        };
    }
}
0
ravi ranjan

J'ai récemment écrit un comparateur pour trier plusieurs champs dans un enregistrement String délimité. Il vous permet de définir le délimiteur, la structure des enregistrements et les règles de tri (dont certaines sont spécifiques à un type). Vous pouvez utiliser ceci en convertissant un enregistrement Personne en une chaîne délimitée.

Les informations requises sont transmises au comparateur lui-même, soit par programme, soit via un fichier XML.

XML est validé par un fichier XSD intégré au package. Par exemple, ci-dessous est une disposition d’enregistrement délimitée par des tabulations avec quatre champs (dont deux sont triables):

<?xml version="1.0" encoding="ISO-8859-1"?> 
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <delimiter>&#009;</delimiter>

    <column xsi:type="Decimal">
        <name>Column One</name>
    </column>

    <column xsi:type="Integer">
        <name>Column Two</name>
    </column>

    <column xsi:type="String">
        <name>Column Three</name>
        <sortOrder>2</sortOrder>
        <trim>true</trim>
        <caseSensitive>false</caseSensitive>        
        <stripAccents>true</stripAccents>
    </column>

    <column xsi:type="DateTime">
        <name>Column Four</name>
        <sortOrder>1</sortOrder>
        <ascending>true</ascending>
        <nullLowSortOrder>true</nullLowSortOrder>
        <trim>true</trim>
        <pattern>yyyy-MM-dd</pattern>
    </column>

</row>

Vous utiliseriez alors ceci en Java comme ceci: 

Comparator<String> comparator = new RowComparator(
              new XMLStructureReader(new File("layout.xml")));

La bibliothèque peut être trouvée ici:

http://sourceforge.net/projects/multicolumnrowcomparator/

0
Constantin