Tri en Java

Tri en Java

1. Vue d'ensemble

Cet article explique comment appliquer le tri àArray,List,Set etMap dans Java 7 et Java 8.

2. Tri avecArray

Commençons par trier les tableaux d'entiers en utilisant la méthodeArrays.sort().

Nous allons définir les tableauxint suivants dans une méthode jUnit de@Before:

@Before
public void initVariables () {
    toSort = new int[]
      { 5, 1, 89, 255, 7, 88, 200, 123, 66 };
    sortedInts = new int[]
      {1, 5, 7, 66, 88, 89, 123, 200, 255};
    sortedRangeInts = new int[]
      {5, 1, 89, 7, 88, 200, 255, 123, 66};
    ...
}

2.1. Tri d'un tableau complet

Utilisons maintenant la simple APIArray.sort():

@Test
public void givenIntArray_whenUsingSort_thenSortedArray() {
    Arrays.sort(toSort);

    assertTrue(Arrays.equals(toSort, sortedInts));
}

Le tableau non trié est maintenant entièrement trié:

[1, 5, 7, 66, 88, 89, 123, 200, 255]

As mentioned in the official JavaDoc,Arrays.sort utilise Quicksort à double pivot surprimitives. Il offre des performances O (n log (n)) et est généralement plus rapide que les implémentations Quicksort traditionnelles (à un pivot). Cependant, il utilise une implémentation itérative, adaptative et stable demergesort algorithm for Array of Objects.

2.2. Tri d'une partie d'un tableau

Arrays.sort possède une APIsort supplémentaire - dont nous parlerons ici:

Arrays.sort(int[] a, int fromIndex, int toIndex)

Cela ne fera que trier une partie du tableau, entre les deux index.

Prenons un exemple rapide:

@Test
public void givenIntArray_whenUsingRangeSort_thenRangeSortedArray() {
    Arrays.sort(toSort, 3, 7);

    assertTrue(Arrays.equals(toSort, sortedRangeInts));
}

Le tri sera effectué uniquement sur les éléments de sous-tableau suivants (toIndex serait exclusif):

[255, 7, 88, 200]

Le sous-tableau trié résultant, inclus avec le tableau principal, serait:

[5, 1, 89, 7, 88, 200, 255, 123, 66]

2.3. Java 8Arrays.sort vsArrays.parallelSort

Java 8 est livré avec une nouvelle API -parallelSort - avec une signature similaire à l'APIArrays.sort():

@Test
public void givenIntArray_whenUsingParallelSort_thenArraySorted() {
    Arrays.parallelSort(toSort);

    assertTrue(Arrays.equals(toSort, sortedInts));
}

Dans les coulisses deparallelSort(),, il divise le tableau en différents sous-tableaux (selon la granularité de l'algorithme deparallelSort). Chaque sous-tableau est trié avecArrays.sort() dans différents threads afin quesort puisse être exécuté en parallèle et fusionné finalement en un tableau trié.

Notez que leForJoin common pool est utilisé pour exécuter ces tâches parallèles puis pour fusionner les résultats.

Le résultat desArrays.parallelSort sera le même que celui desArray.sort bien sûr, il s’agit simplement de tirer parti du multi-threading.

Enfin, il existe également des variantes similaires d'APIArrays.sort dansArrays.parallelSort:

Arrays.parallelSort (int [] a, int fromIndex, int toIndex);

3. Tri d'unList

Utilisons maintenant l'APICollections.sort() dansjava.utils.Collections - pour trier unList d'entiers:

@Test
public void givenList_whenUsingSort_thenSortedList() {
    List toSortList = Ints.asList(toSort);
    Collections.sort(toSortList);

    assertTrue(Arrays.equals(toSortList.toArray(),
    ArrayUtils.toObject(sortedInts)));
}

LesList avant le tri contiendront les éléments suivants:

[5, 1, 89, 255, 7, 88, 200, 123, 66]

Et naturellement, après le tri:

[1, 5, 7, 66, 88, 89, 123, 200, 255]

As mentioned in Oracle JavaDoc pourCollections.Sort, il utilise un tri de fusion modifié et offre des performancesn log(n) garanties.

4. Tri d'unSet

Ensuite, utilisonsCollections.sort() pour trier unLinkedHashSet.

Nous utilisons lesLinkedHashSetcar ils conservent l'ordre d'insertion.

Remarquez comment, pour utiliser l'APIsort dansCollections -we’re first wrapping the set in a list:

@Test
public void givenSet_whenUsingSort_thenSortedSet() {
    Set integersSet = new LinkedHashSet<>(Ints.asList(toSort));
    Set descSortedIntegersSet = new LinkedHashSet<>(
      Arrays.asList(new Integer[]
        {255, 200, 123, 89, 88, 66, 7, 5, 1}));

    List list = new ArrayList(integersSet);
    Collections.sort(list, (i1, i2) -> {
        return i2 - i1;
    });
    integersSet = new LinkedHashSet<>(list);

    assertTrue(Arrays.equals(
      integersSet.toArray(), descSortedIntegersSet.toArray()));
}

5. TriMap

Dans cette section, nous allons commencer à examinersorting a Map – both by keys and by values.

Définissons d'abord la carte que nous allons trier:

@Before
public void initVariables () {
    ....
    HashMap map = new HashMap<>();
    map.put(55, "John");
    map.put(22, "Apple");
    map.put(66, "Earl");
    map.put(77, "Pearl");
    map.put(12, "George");
    map.put(6, "Rocky");
    ....
}

5.1. Tri desMap par clés

Nous allons maintenant extraire les entréeskeys etvalues desHashMap et les trier en fonction des valeurs des clés dans cet exemple:

@Test
public void givenMap_whenSortingByKeys_thenSortedMap() {
    Integer[] sortedKeys = new Integer[] { 6, 12, 22, 55, 66, 77 };

    List> entries
      = new ArrayList<>(map.entrySet());
    Collections.sort(entries, new Comparator>() {
        @Override
        public int compare(
          Entry o1, Entry o2) {
            return o1.getKey().compareTo(o2.getKey());
        }
    });
    Map sortedMap = new LinkedHashMap<>();
    for (Map.Entry entry : entries) {
        sortedMap.put(entry.getKey(), entry.getValue());
    }

    assertTrue(Arrays.equals(sortedMap.keySet().toArray(), sortedKeys));
}

Notez comment nous avons utilisé lesLinkedHashMap lors de la copie desEntries triés en fonction des clés (carHashSet ne garantit pas l'ordre des clés).

LesMap avant le tri:

[Key: 66 , Value: Earl]
[Key: 22 , Value: Apple]
[Key: 6 , Value: Rocky]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]

LesMap après le tri desby keys:

[Key: 6 , Value: Rocky]
[Key: 12 , Value: George]
[Key: 22 , Value: Apple]
[Key: 55 , Value: John]
[Key: 66 , Value: Earl]
[Key: 77 , Value: Pearl]

5.2. Tri desMap par valeurs

Ici, nous comparerons les valeurs des entréesHashMap pour le tri en fonction des valeurs deHashMap:

@Test
public void givenMap_whenSortingByValues_thenSortedMap() {
    String[] sortedValues = new String[]
      { "Apple", "Earl", "George", "John", "Pearl", "Rocky" };

    List> entries
      = new ArrayList<>(map.entrySet());
    Collections.sort(entries, new Comparator>() {
        @Override
        public int compare(
          Entry o1, Entry o2) {
            return o1.getValue().compareTo(o2.getValue());
        }
    });
    Map sortedMap = new LinkedHashMap<>();
    for (Map.Entry entry : entries) {
        sortedMap.put(entry.getKey(), entry.getValue());
    }

    assertTrue(Arrays.equals(sortedMap.values().toArray(), sortedValues));
}

LesMap avant le tri:

[Key: 66 , Value: Earl]
[Key: 22 , Value: Apple]
[Key: 6 , Value: Rocky]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]

LesMap après le tri desby values:

[Key: 22 , Value: Apple]
[Key: 66 , Value: Earl]
[Key: 12 , Value: George]
[Key: 55 , Value: John]
[Key: 77 , Value: Pearl]
[Key: 6 , Value: Rocky]

6. Tri des objets personnalisés

Travaillons maintenant avec un objet personnalisé:

public class Employee implements Comparable {
    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        ...
    }

    // standard getters, setters and toString
}

Nous utiliserons le tableauEmployee suivant pour l'exemple de tri dans les sections suivantes:

@Before
public void initVariables () {
    ....
    employees = new Employee[] {
      new Employee("John", 23, 5000), new Employee("Steve", 26, 6000),
      new Employee("Frank", 33, 7000), new Employee("Earl", 43, 10000),
      new Employee("Jessica", 23, 4000), new Employee("Pearl", 33, 6000)};

    employeesSorted = new Employee[] {
      new Employee("Earl", 43, 10000), new Employee("Frank", 33, 70000),
      new Employee("Jessica", 23, 4000), new Employee("John", 23, 5000),
      new Employee("Pearl", 33, 4000), new Employee("Steve", 26, 6000)};

    employeesSortedByAge = new Employee[] {
      new Employee("John", 23, 5000), new Employee("Jessica", 23, 4000),
      new Employee("Steve", 26, 6000), new Employee("Frank", 33, 70000),
      new Employee("Pearl", 33, 4000), new Employee("Earl", 43, 10000)};
}

Nous pouvons trier des tableaux ou des collections d’objets personnalisés:

  1. dans l'ordre naturel (en utilisant l'interfaceComparable) ou

  2. dans l'ordre fourni par unComparatorInterface

6.1. Using Comparable

The natural order in java signifie un ordre dans lequel la primitive ou l'objet doit être trié de manière ordonnée dans un tableau ou une collection donnée.

java.util.Arrays etjava.util.Collections ont tous deux une méthodesort() etIt’s highly recommended that natural orders should be consistent with the semantics of equals.

Dans cet exemple, nous considérerons les employés avec les mêmesname comme égaux:

@Test
public void givenEmpArray_SortEmpArray_thenSortedArrayinNaturalOrder() {
    Arrays.sort(employees);

    assertTrue(Arrays.equals(employees, employeesSorted));
}

Vous pouvez définir l'ordre naturel des éléments en implémentant une interfaceComparable qui a la méthodecompareTo() pour comparer l'objet actuel et l'objet passé en argument.

Pour comprendre cela clairement, voyons un exemple de classeEmployee qui implémente l'interfaceComparable:

public class Employee implements Comparable {
    ...

    @Override
    public boolean equals(Object obj) {
        return ((Employee) obj).getName().equals(getName());
    }

    @Override
    public int compareTo(Object o) {
        Employee e = (Employee) o;
        return getName().compareTo(e.getName());
    }
}

Généralement, la logique de comparaison s'écrira la méthodecompareTo. Ici, nous comparons l'ordre des employés ou lesname du champ employé. Deux employés seront égaux s'ils ont le même nom.

Maintenant, lorsqueArrays.sort(employees); est appelé dans le code ci-dessus, nous savons maintenant quelle est la logique et l'ordre qui vont dans le tri des employés en fonction de l'âge:

[("Earl", 43, 10000),("Frank", 33, 70000), ("Jessica", 23, 4000),
 ("John", 23, 5000),("Pearl", 33, 4000), ("Steve", 26, 6000)]

Nous pouvons voir que le tableau est trié par nom de l'employé - qui devient maintenant un ordre naturel pour la classeEmployee.

6.2. Utilisation deComparator

Maintenant, trions les éléments à l'aide d'une implémentation d'interfaceComparator - où nous transmettons la classe interne anonyme à la volée à l'APIArrays.sort():

@Test
public void givenIntegerArray_whenUsingSort_thenSortedArray() {
    Integer [] integers = ArrayUtils.toObject(toSort);
    Arrays.sort(integers, new Comparator() {
        @Override
        public int compare(Integer a, Integer b) {
            return a - b;
        }
    });

    assertTrue(Arrays.equals(integers, ArrayUtils.toObject(sortedInts)));
}

Maintenant, trions les employés en fonction desalary - et transmettons une autre implémentation de comparateur:

Arrays.sort(employees, new Comparator() {
    @Override
    public int compare(Employee o1, Employee o2) {
       return (int) (o1.getSalary() - o2.getSalary());
    }
 });

Les tableaux Employés triés basés sursalary seront:

[(Jessica,23,4000.0), (John,23,5000.0), (Pearl,33,6000.0), (Steve,26,6000.0),
(Frank,33,7000.0), (Earl,43,10000.0)]

Notez que nous pouvons utiliserCollections.sort() de la même manière pour trier lesList etSet des objets dans l'ordre naturel ou personnalisé comme décrit ci-dessus pour les tableaux.

7. Tri avec Lambdas

Commencez avec Java 8, nous pouvons utiliser Lambdas pour implémenter l'interface fonctionnelle deComparator.

Vous pouvez jeter un œil à l'écriture deLambdas in Java 8 pour peaufiner la syntaxe.

Remplaçons l'ancien comparateur:

Comparator c  = new Comparator<>() {
    @Override
    public int compare(Integer a, Integer b) {
        return a - b;
    }
}

Avec l'implémentation équivalente, en utilisant l'expression Lambda:

Comparator c = (a, b) -> a - b;

Enfin, écrivons le test:

@Test
public void givenArray_whenUsingSortWithLambdas_thenSortedArray() {
    Integer [] integersToSort = ArrayUtils.toObject(toSort);
    Arrays.sort(integersToSort, (a, b) -> {
        return a - b;
    });

    assertTrue(Arrays.equals(integersToSort,
      ArrayUtils.toObject(sortedInts)));
}

Comme vous pouvez le constater, une logique beaucoup plus nette et concise ici.

8. Utilisation deComparator.comparing etComparator.thenComparing

Java 8 est livré avec deux nouvelles API utiles pour le tri -comparing() etthenComparing() dans l'interfaceComparator.

Celles-ci sont très pratiques pour le chaînage de plusieurs conditions desComparator.

Considérons un scénario où nous pourrions vouloir comparerEmployee parage puis parname:

@Test
public void givenArrayObjects_whenUsingComparing_thenSortedArrayObjects() {
    List employeesList = Arrays.asList(employees);
    employees.sort(Comparator.comparing(Employee::getAge));

    assertTrue(Arrays.toString(employees.toArray())
      .equals(sortedArrayString));
}

Dans cet exemple,Employee::getAge est la clé de tri pour l'interfaceComparator implémentant une interface fonctionnelle avec une fonction de comparaison.

Voici le tableau des employés après le tri:

[(John,23,5000.0), (Jessica,23,4000.0), (Steve,26,6000.0), (Frank,33,7000.0),
(Pearl,33,6000.0), (Earl,43,10000.0)]

Ici, les employés sont triés en fonction deage.

Nous pouvons voir queJohn etJessica sont du même âge - ce qui signifie que la logique de commande devrait maintenant prendre en compte leurs noms - ce que nous pouvons réaliser avecthenComparing():

...
employees.sort(Comparator.comparing(Employee::getAge)
  .thenComparing(Employee::getName));
...

Après avoir trié avec l'extrait de code ci-dessus, les éléments du tableau employee seront triés comme suit:

[(Jessica,23,4000.0),
 (John,23,5000.0),
 (Steve,26,6000.0),
 (Frank,33,7000.0),
 (Pearl,33,6000.0),
 (Earl,43,10000.0)
]

Ainsi,comparing() etthenComparing() rendent certainement les scénarios de tri plus complexes beaucoup plus propres à implémenter.

Lectures complémentaires:

Guide de tri à Kotlin

Un bref guide pour trier à l'aide de la bibliothèque standard Kotlin.

Read more

9. Conclusion

Dans cet article, nous avons vu comment appliquer le tri àArray,List,Set etMap.

Nous avons également vu une brève introduction sur la manière dont les fonctionnalités de Java 8 pourraient être utiles dans le tri, comme l'utilisation de Lambdas,comparing() etthenComparing() etparallelSort().

Tous les exemples utilisés dans l'article sont disponiblesover on GitHub.