Javaでのソート

Javaでのソート

1. 概要

この記事では、Java 7およびJava8でArrayListSet、およびMapにソートを適用する方法について説明します。

2. Arrayで並べ替え

まず、Arrays.sort()メソッドを使用して整数配列を並べ替えることから始めましょう。

@Before jUnitメソッドで次のint配列を定義します。

@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. 完全な配列の並べ替え

単純なArray.sort()APIを使用してみましょう。

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

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

並べ替えられていない配列が完全に並べ替えられました。

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

As mentioned in the official JavaDocArrays.sortは、primitivesでデュアルピボットクイックソートを使用します。 O(n log(n))のパフォーマンスを提供し、通常、従来の(1ピボットの)Quicksort実装よりも高速です。 ただし、mergesort algorithm for Array of Objectsの安定した、適応性のある、反復的な実装を使用します。

2.2. 配列の一部を並べ替える

Arrays.sortにはもう1つのsort APIがあります。これについてはここで説明します:

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

これは、2つのインデックス間の配列の一部のみをソートします。

簡単な例を見てみましょう。

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

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

並べ替えは、次のサブ配列要素でのみ実行されます(toIndexは排他的です)。

[255, 7, 88, 200]

結果のソートされたメイン配列を含むサブ配列は、次のようになります。

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

2.3. Java 8Arrays.sortArrays.parallelSort

Java 8には、Arrays.sort() APIと同様の署名を持つ新しいAPI –parallelSort –が付属しています。

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

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

parallelSort(),の舞台裏では、配列を異なるサブ配列に分割します(parallelSortのアルゴリズムの粒度に従って)。 各サブ配列は、異なるスレッドでArrays.sort()を使用して並べ替えられるため、sortを並列に実行し、最終的に並べ替えられた配列としてマージできます。

ForJoin common poolは、これらの並列タスクを実行し、結果をマージするために使用されることに注意してください。

もちろん、Arrays.parallelSortの結果はArray.sortと同じになります。これは、マルチスレッドを活用するだけの問題です。

最後に、Arrays.parallelSortにもAPIArrays.sortの同様のバリアントがあります。

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

3. Listの並べ替え

次に、java.utils.CollectionsCollections.sort() APIを使用して、整数のListを並べ替えます。

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

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

ソート前のListには、次の要素が含まれます。

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

そして当然、ソート後:

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

Collections.Sortの場合はAs mentioned in Oracle JavaDocであり、変更されたマージソートを使用し、n log(n)のパフォーマンスを保証します。

4. Setの並べ替え

次に、Collections.sort()を使用してLinkedHashSetを並べ替えましょう。

挿入順序を維持するため、LinkedHashSetを使用しています。

Collectionswe’re first wrapping the set in a listsortAPIを使用する方法に注意してください。

@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. Mapの並べ替え

このセクションでは、sorting a Map – both by keys and by values.について見ていきます。

まず、並べ替えるマップを定義しましょう。

@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. キーによるMapの並べ替え

次に、HashMapからkeysおよびvaluesエントリを抽出し、この例のキーの値に基づいて並べ替えます。

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

キーに基づいてソートされたEntriesをコピーするときに、LinkedHashMapをどのように使用したかに注意してください(HashSetはキーの順序を保証しないため)。

ソート前のMap

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

by keysをソートした後のMap

[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. Mapを値で並べ替える

ここでは、HashMapの値に基づいてソートするために、HashMapエントリの値を比較します。

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

ソート前のMap

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

by valuesをソートした後のMap

[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. カスタムオブジェクトの並べ替え

次に、カスタムオブジェクトを操作してみましょう。

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
}

次のセクションでは、例を並べ替えるために次のEmployee配列を使用します。

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

カスタムオブジェクトの配列またはコレクションを並べ替えることができます。

  1. 自然な順序で(Comparableインターフェイスを使用)または

  2. ComparatorInterfaceによって提供される順序で

6.1. Using Comparable

The natural order in javaは、プリミティブまたはオブジェクトを特定の配列またはコレクションで順番に並べ替える順序を意味します。

java.util.Arraysjava.util.Collectionsの両方にsort()メソッドがあり、It’s highly recommended that natural orders should be consistent with the semantics of equals.

この例では、同じnameを持つ従業員を等しいと見なします。

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

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

現在のオブジェクトと引数として渡されたオブジェクトを比較するためのcompareTo()メソッドを持つComparableインターフェイスを実装することにより、要素の自然な順序を定義できます。

これを明確に理解するために、Comparableインターフェイスを実装するEmployeeクラスの例を見てみましょう。

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

通常、比較のロジックはメソッドcompareToで記述されます。 ここでは、従業員の注文または従業員フィールドのnameを比較しています。 同じ名前の場合、2人の従業員は平等です。

上記のコードでArrays.sort(employees);が呼び出されると、年齢に応じて従業員を並べ替えるロジックと順序がわかります。

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

配列が従業員の名前でソートされていることがわかります。これは、Employeeクラスの自然な順序になります。

6.2. Comparatorの使用

それでは、Comparatorインターフェースの実装を使用して要素を並べ替えましょう。ここでは、匿名の内部クラスをオンザフライでArrays.sort()APIに渡します。

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

次に、salaryに基づいて従業員を並べ替え、別のコンパレータ実装を渡します。

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

salaryに基づいてソートされたEmployees配列は次のようになります。

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

同様の方法でCollections.sort()を使用して、上記の配列について説明したように、オブジェクトのListSetを自然またはカスタムの順序で並べ替えることができることに注意してください。

7. ラムダで並べ替え

Java 8から始めて、Lambdasを使用してComparator関数型インターフェースを実装できます。

構文をブラッシュアップするために、Lambdas in Java 8の記述を見ることができます。

古いコンパレータを交換しましょう。

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

同等の実装で、Lambda式を使用:

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

最後に、テストを書いてみましょう。

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

ご覧のとおり、ここでははるかに簡潔で簡潔なロジックです。

8. Comparator.comparingおよびComparator.thenComparingの使用

Java 8には、ソートに役立つ2つの新しいAPIが付属しています。Comparatorインターフェースのcomparing()thenComparing()です。

これらは、Comparatorの複数の条件を連鎖させるのに非常に便利です。

Employeeageで比較し、次にnameで比較したいシナリオを考えてみましょう。

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

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

この例では、Employee::getAgeは、比較機能を備えた機能インターフェイスを実装するComparatorインターフェイスのソートキーです。

並べ替え後の従業員の配列は次のとおりです。

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

ここでは、従業員はageに基づいてソートされています。

JohnJessicaは同じ年齢であることがわかります。つまり、順序ロジックでそれらの名前を考慮に入れる必要があります。これは、thenComparing()で実現できます。

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

上記のコードスニペットで並べ替えた後、従業員配列の要素は次のように並べ替えられます。

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

したがって、comparing()thenComparing()を使用すると、より複雑な並べ替えシナリオを実装するのがはるかに簡単になります。

参考文献:

Kotlinの並べ替えガイド

Kotlin標準ライブラリを使用したソートの簡単なガイド。

9. 結論

この記事では、ArrayListSet、およびMapに並べ替えを適用する方法を説明しました。

また、Java 8の機能が、ラムダ、comparing()thenComparing()parallelSort()の使用法などの並べ替えにどのように役立つかについて簡単に紹介しました。

この記事で使用されているすべての例は、利用可能なover on GitHubです。