1/*
2 * Copyright 2015 Goldman Sachs.
3 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.
9 *
10 * This code is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13 * version 2 for more details (a copy is included in the LICENSE file that
14 * accompanied this code).
15 *
16 * You should have received a copy of the GNU General Public License version
17 * 2 along with this work; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21 * or visit www.oracle.com if you need additional information or have any
22 * questions.
23 */
24/*
25 * @test
26 * @bug 8154049
27 * @summary Tests the sorting of a large array of sorted primitive values,
28 *          predominently for cases where the array is nearly sorted. This tests
29 *          code that detects patterns in the array to determine if it is nearly
30 *          sorted and if so employs and optimizes merge sort rather than a
31 *          Dual-Pivot QuickSort.
32 *
33 * @run testng SortingNearlySortedPrimitive
34 */
35
36import org.testng.annotations.DataProvider;
37import org.testng.annotations.Test;
38
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.List;
42import java.util.StringJoiner;
43import java.util.function.IntFunction;
44import java.util.stream.IntStream;
45import java.util.stream.Stream;
46
47public class SortingNearlySortedPrimitive {
48
49    static final int BASE = 3;
50    static final int WIDTH = 4;
51    // Should be > DualPivotQuicksort.QUICKSORT_THRESHOLD
52    static final int PAD = 300;
53
54    Stream<int[]> createCombinations() {
55        // Create all combinations for the BASE value and double the WIDTH
56        // elements
57        // This is create various combinations of ascending, descending and
58        // equal runs to exercise the nearly sorted code paths
59        return IntStream.range(0, (int) Math.pow(BASE, 2 * WIDTH)).
60                mapToObj(this::createArray);
61    }
62
63    // Create an array which at either end is filled with -ve and +ve elements
64    // according to the base value and padded with zeros in between
65    int[] createArray(int v) {
66        int[] a = new int[WIDTH + PAD + WIDTH];
67
68        // Fill head of array
69        for (int j = 0; j < WIDTH; j++) {
70            a[j] = (v % BASE) - (BASE / 2);
71            v /= BASE;
72        }
73        // Fill tail of array
74        for (int j = 0; j < WIDTH; j++) {
75            a[WIDTH + PAD + j] = (v % BASE) - (BASE / 2);
76            v /= BASE;
77        }
78        return a;
79    }
80
81    @Test
82    public void testCombination() {
83        createCombinations().forEach(a -> {
84            try {
85                // Clone source array to ensure it is not modified
86                this.sortAndAssert(a.clone());
87                this.sortAndAssert(floatCopyFromInt(a));
88                this.sortAndAssert(doubleCopyFromInt(a));
89                this.sortAndAssert(longCopyFromInt(a));
90                this.sortAndAssert(shortCopyFromInt(a));
91                this.sortAndAssert(charCopyFromInt(a));
92            } catch (AssertionError sae) {
93                AssertionError ae = new AssertionError("Sort failed for " + arrayToString(a));
94                ae.addSuppressed(sae);
95                throw ae;
96            }
97        });
98    }
99
100    String arrayToString(int[] a) {
101        int[] l = Arrays.copyOfRange(a, 0, WIDTH + 2);
102        int[] r = Arrays.copyOfRange(a, a.length - (WIDTH + 2), a.length);
103        StringJoiner sj = new StringJoiner(",", "[", "]");
104        for (int i : l) {
105            sj.add(Integer.toString(i));
106        }
107        sj.add("...");
108        for (int i : r) {
109            sj.add(Integer.toString(i));
110        }
111        return sj.toString();
112    }
113
114
115    @DataProvider(name = "shapes")
116    public Object[][] createShapes() {
117        Stream<List<Object>> baseCases = Stream.of(
118                List.of("hiZeroLowTest", (IntFunction<int[]>) this::hiZeroLowData),
119                List.of("endLessThanTest", (IntFunction<int[]>) this::endLessThanData),
120                List.of("highFlatLowTest", (IntFunction<int[]>) this::highFlatLowData),
121                List.of("identicalTest", (IntFunction<int[]>) this::identicalData),
122                List.of("sortedReversedSortedTest", (IntFunction<int[]>) this::sortedReversedSortedData),
123                List.of("pairFlipTest", (IntFunction<int[]>) this::pairFlipData),
124                List.of("zeroHiTest", (IntFunction<int[]>) this::zeroHiData)
125        );
126
127        // Ensure the following inequality holds for certain sizes
128        // DualPivotQuicksort.QUICKSORT_THRESHOLD <= size - 1
129        //   < DualPivotQuicksort.COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR
130        // This guarantees that code paths are taken for checking nearly sorted
131        // arrays for all primitive types
132        List<Integer> sizes = List.of(100, 1_000, 10_000, 1_000_000);
133        return baseCases.
134                flatMap(l -> sizes.stream().map(s -> append(l, s))).
135                toArray(Object[][]::new);
136    }
137
138    Object[] append(List<Object> l, Object value) {
139        List<Object> nl = new ArrayList<>(l);
140        nl.add(value);
141        return nl.toArray();
142    }
143
144    @Test(dataProvider = "shapes")
145    public void testShapes(String testName, IntFunction<int[]> dataMethod, int size) {
146        int[] intSourceArray = dataMethod.apply(size);
147
148        // Clone source array to ensure it is not modified
149        this.sortAndAssert(intSourceArray.clone());
150        this.sortAndAssert(floatCopyFromInt(intSourceArray));
151        this.sortAndAssert(doubleCopyFromInt(intSourceArray));
152        this.sortAndAssert(longCopyFromInt(intSourceArray));
153        this.sortAndAssert(shortCopyFromInt(intSourceArray));
154        this.sortAndAssert(charCopyFromInt(intSourceArray));
155    }
156
157    private float[] floatCopyFromInt(int[] src) {
158        float[] result = new float[src.length];
159        for (int i = 0; i < result.length; i++) {
160            result[i] = src[i];
161        }
162        return result;
163    }
164
165    private double[] doubleCopyFromInt(int[] src) {
166        double[] result = new double[src.length];
167        for (int i = 0; i < result.length; i++) {
168            result[i] = src[i];
169        }
170        return result;
171    }
172
173    private long[] longCopyFromInt(int[] src) {
174        long[] result = new long[src.length];
175        for (int i = 0; i < result.length; i++) {
176            result[i] = src[i];
177        }
178        return result;
179    }
180
181    private short[] shortCopyFromInt(int[] src) {
182        short[] result = new short[src.length];
183        for (int i = 0; i < result.length; i++) {
184            result[i] = (short) src[i];
185        }
186        return result;
187    }
188
189    private char[] charCopyFromInt(int[] src) {
190        char[] result = new char[src.length];
191        for (int i = 0; i < result.length; i++) {
192            result[i] = (char) src[i];
193        }
194        return result;
195    }
196
197    private void sortAndAssert(int[] array) {
198        Arrays.sort(array);
199        for (int i = 1; i < array.length; i++) {
200            if (array[i] < array[i - 1]) {
201                throw new AssertionError("not sorted");
202            }
203        }
204    }
205
206    private void sortAndAssert(char[] array) {
207        Arrays.sort(array);
208        for (int i = 1; i < array.length; i++) {
209            if (array[i] < array[i - 1]) {
210                throw new AssertionError("not sorted");
211            }
212        }
213    }
214
215    private void sortAndAssert(short[] array) {
216        Arrays.sort(array);
217        for (int i = 1; i < array.length; i++) {
218            if (array[i] < array[i - 1]) {
219                throw new AssertionError("not sorted");
220            }
221        }
222    }
223
224    private void sortAndAssert(double[] array) {
225        Arrays.sort(array);
226        for (int i = 1; i < array.length; i++) {
227            if (array[i] < array[i - 1]) {
228                throw new AssertionError("not sorted");
229            }
230        }
231    }
232
233    private void sortAndAssert(float[] array) {
234        Arrays.sort(array);
235        for (int i = 1; i < array.length; i++) {
236            if (array[i] < array[i - 1]) {
237                throw new AssertionError("not sorted");
238            }
239        }
240    }
241
242    private void sortAndAssert(long[] array) {
243        Arrays.sort(array);
244        for (int i = 1; i < array.length; i++) {
245            if (array[i] < array[i - 1]) {
246                throw new AssertionError("not sorted");
247            }
248        }
249    }
250
251    private int[] zeroHiData(int size) {
252        int[] array = new int[size];
253
254        int threeQuarters = (int) (size * 0.75);
255        for (int i = 0; i < threeQuarters; i++) {
256            array[i] = 0;
257        }
258        int k = 1;
259        for (int i = threeQuarters; i < size; i++) {
260            array[i] = k;
261            k++;
262        }
263
264        return array;
265    }
266
267    private int[] hiZeroLowData(int size) {
268        int[] array = new int[size];
269
270        int oneThird = size / 3;
271        for (int i = 0; i < oneThird; i++) {
272            array[i] = i;
273        }
274        int twoThirds = oneThird * 2;
275        for (int i = oneThird; i < twoThirds; i++) {
276            array[i] = 0;
277        }
278        for (int i = twoThirds; i < size; i++) {
279            array[i] = oneThird - i + twoThirds;
280        }
281        return array;
282    }
283
284    private int[] highFlatLowData(int size) {
285        int[] array = new int[size];
286
287        int oneThird = size / 3;
288        for (int i = 0; i < oneThird; i++) {
289            array[i] = i;
290        }
291        int twoThirds = oneThird * 2;
292        int constant = oneThird - 1;
293        for (int i = oneThird; i < twoThirds; i++) {
294            array[i] = constant;
295        }
296        for (int i = twoThirds; i < size; i++) {
297            array[i] = constant - i + twoThirds;
298        }
299
300        return array;
301    }
302
303    private int[] identicalData(int size) {
304        int[] array = new int[size];
305        int listNumber = 24;
306
307        for (int i = 0; i < size; i++) {
308            array[i] = listNumber;
309        }
310
311        return array;
312    }
313
314    private int[] endLessThanData(int size) {
315        int[] array = new int[size];
316
317        for (int i = 0; i < size - 1; i++) {
318            array[i] = 3;
319        }
320        array[size - 1] = 1;
321
322        return array;
323    }
324
325    private int[] sortedReversedSortedData(int size) {
326        int[] array = new int[size];
327
328        for (int i = 0; i < size / 2; i++) {
329            array[i] = i;
330        }
331        int num = 0;
332        for (int i = size / 2; i < size; i++) {
333            array[i] = size - num;
334            num++;
335        }
336
337        return array;
338    }
339
340    private int[] pairFlipData(int size) {
341        int[] array = new int[size];
342
343        for (int i = 0; i < size; i++) {
344            array[i] = i;
345        }
346        for (int i = 0; i < size; i += 2) {
347            int temp = array[i];
348            array[i] = array[i + 1];
349            array[i + 1] = temp;
350        }
351
352        return array;
353    }
354}
355