/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.util; import java.io.IOException; import java.util.Arrays; import java.util.Random; import junit.framework.TestCase; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.io.DataInputBuffer; import org.apache.hadoop.io.DataOutputBuffer; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.WritableComparator; public class TestIndexedSort extends TestCase { public void sortAllEqual(IndexedSorter sorter) throws Exception { final int SAMPLE = 500; int[] values = new int[SAMPLE]; Arrays.fill(values, 10); SampleSortable s = new SampleSortable(values); sorter.sort(s, 0, SAMPLE); int[] check = s.getSorted(); assertTrue(Arrays.toString(values) + "\ndoesn't match\n" + Arrays.toString(check), Arrays.equals(values, check)); // Set random min/max, re-sort. Random r = new Random(); int min = r.nextInt(SAMPLE); int max = (min + 1 + r.nextInt(SAMPLE - 2)) % SAMPLE; values[min] = 9; values[max] = 11; System.out.println("testAllEqual setting min/max at " + min + "/" + max + "(" + sorter.getClass().getName() + ")"); s = new SampleSortable(values); sorter.sort(s, 0, SAMPLE); check = s.getSorted(); Arrays.sort(values); assertTrue(check[0] == 9); assertTrue(check[SAMPLE - 1] == 11); assertTrue(Arrays.toString(values) + "\ndoesn't match\n" + Arrays.toString(check), Arrays.equals(values, check)); } public void sortSorted(IndexedSorter sorter) throws Exception { final int SAMPLE = 500; int[] values = new int[SAMPLE]; Random r = new Random(); long seed = r.nextLong(); r.setSeed(seed); System.out.println("testSorted seed: " + seed + "(" + sorter.getClass().getName() + ")"); for (int i = 0; i < SAMPLE; ++i) { values[i] = r.nextInt(100); } Arrays.sort(values); SampleSortable s = new SampleSortable(values); sorter.sort(s, 0, SAMPLE); int[] check = s.getSorted(); assertTrue(Arrays.toString(values) + "\ndoesn't match\n" + Arrays.toString(check), Arrays.equals(values, check)); } public void sortSequential(IndexedSorter sorter) throws Exception { final int SAMPLE = 500; int[] values = new int[SAMPLE]; for (int i = 0; i < SAMPLE; ++i) { values[i] = i; } SampleSortable s = new SampleSortable(values); sorter.sort(s, 0, SAMPLE); int[] check = s.getSorted(); assertTrue(Arrays.toString(values) + "\ndoesn't match\n" + Arrays.toString(check), Arrays.equals(values, check)); } public void sortSingleRecord(IndexedSorter sorter) throws Exception { final int SAMPLE = 1; SampleSortable s = new SampleSortable(SAMPLE); int[] values = s.getValues(); sorter.sort(s, 0, SAMPLE); int[] check = s.getSorted(); assertTrue(Arrays.toString(values) + "\ndoesn't match\n" + Arrays.toString(check), Arrays.equals(values, check)); } public void sortRandom(IndexedSorter sorter) throws Exception { final int SAMPLE = 256 * 1024; SampleSortable s = new SampleSortable(SAMPLE); long seed = s.getSeed(); System.out.println("sortRandom seed: " + seed + "(" + sorter.getClass().getName() + ")"); int[] values = s.getValues(); Arrays.sort(values); sorter.sort(s, 0, SAMPLE); int[] check = s.getSorted(); assertTrue("seed: " + seed + "\ndoesn't match\n", Arrays.equals(values, check)); } public void sortWritable(IndexedSorter sorter) throws Exception { final int SAMPLE = 1000; WritableSortable s = new WritableSortable(SAMPLE); long seed = s.getSeed(); System.out.println("sortWritable seed: " + seed + "(" + sorter.getClass().getName() + ")"); String[] values = s.getValues(); Arrays.sort(values); sorter.sort(s, 0, SAMPLE); String[] check = s.getSorted(); assertTrue("seed: " + seed + "\ndoesn't match", Arrays.equals(values, check)); } public void testQuickSort() throws Exception { QuickSort sorter = new QuickSort(); sortRandom(sorter); sortSingleRecord(sorter); sortSequential(sorter); sortSorted(sorter); sortAllEqual(sorter); sortWritable(sorter); // test degenerate case for median-of-three partitioning // a_n, a_1, a_2, ..., a_{n-1} final int DSAMPLE = 500; int[] values = new int[DSAMPLE]; for (int i = 0; i < DSAMPLE; ++i) { values[i] = i; } values[0] = values[DSAMPLE - 1] + 1; SampleSortable s = new SampleSortable(values); values = s.getValues(); final int DSS = (DSAMPLE / 2) * (DSAMPLE / 2); // Worst case is (N/2)^2 comparisons, not including those effecting // the median-of-three partitioning; impl should handle this case MeasuredSortable m = new MeasuredSortable(s, DSS); sorter.sort(m, 0, DSAMPLE); System.out.println("QuickSort degen cmp/swp: " + m.getCmp() + "/" + m.getSwp() + "(" + sorter.getClass().getName() + ")"); Arrays.sort(values); int[] check = s.getSorted(); assertTrue(Arrays.equals(values, check)); } public void testHeapSort() throws Exception { HeapSort sorter = new HeapSort(); sortRandom(sorter); sortSingleRecord(sorter); sortSequential(sorter); sortSorted(sorter); sortAllEqual(sorter); sortWritable(sorter); } // Sortables // private static class SampleSortable implements IndexedSortable { private int[] valindex; private int[] valindirect; private int[] values; private final long seed; public SampleSortable() { this(50); } public SampleSortable(int j) { Random r = new Random(); seed = r.nextLong(); r.setSeed(seed); values = new int[j]; valindex = new int[j]; valindirect = new int[j]; for (int i = 0; i < j; ++i) { valindex[i] = valindirect[i] = i; values[i] = r.nextInt(1000); } } public SampleSortable(int[] values) { this.values = values; valindex = new int[values.length]; valindirect = new int[values.length]; for (int i = 0; i < values.length; ++i) { valindex[i] = valindirect[i] = i; } seed = 0; } public long getSeed() { return seed; } public int compare(int i, int j) { // assume positive return values[valindirect[valindex[i]]] - values[valindirect[valindex[j]]]; } public void swap(int i, int j) { int tmp = valindex[i]; valindex[i] = valindex[j]; valindex[j] = tmp; } public int[] getSorted() { int[] ret = new int[values.length]; for (int i = 0; i < ret.length; ++i) { ret[i] = values[valindirect[valindex[i]]]; } return ret; } public int[] getValues() { int[] ret = new int[values.length]; System.arraycopy(values, 0, ret, 0, values.length); return ret; } } public static class MeasuredSortable implements IndexedSortable { private int comparisions; private int swaps; private final int maxcmp; private final int maxswp; private IndexedSortable s; public MeasuredSortable(IndexedSortable s) { this(s, Integer.MAX_VALUE); } public MeasuredSortable(IndexedSortable s, int maxcmp) { this(s, maxcmp, Integer.MAX_VALUE); } public MeasuredSortable(IndexedSortable s, int maxcmp, int maxswp) { this.s = s; this.maxcmp = maxcmp; this.maxswp = maxswp; } public int getCmp() { return comparisions; } public int getSwp() { return swaps; } public int compare(int i, int j) { assertTrue("Expected fewer than " + maxcmp + " comparisons", ++comparisions < maxcmp); return s.compare(i, j); } public void swap(int i, int j) { assertTrue("Expected fewer than " + maxswp + " swaps", ++swaps < maxswp); s.swap(i, j); } } private static class WritableSortable implements IndexedSortable { private static Random r = new Random(); private final int eob; private final int[] indices; private final int[] offsets; private final byte[] bytes; private final WritableComparator comparator; private final String[] check; private final long seed; public WritableSortable() throws IOException { this(100); } public WritableSortable(int j) throws IOException { seed = r.nextLong(); r.setSeed(seed); Text t = new Text(); StringBuilder sb = new StringBuilder(); indices = new int[j]; offsets = new int[j]; check = new String[j]; DataOutputBuffer dob = new DataOutputBuffer(); for (int i = 0; i < j; ++i) { indices[i] = i; offsets[i] = dob.getLength(); genRandom(t, r.nextInt(15) + 1, sb); t.write(dob); check[i] = t.toString(); } eob = dob.getLength(); bytes = dob.getData(); comparator = WritableComparator.get(Text.class); } public long getSeed() { return seed; } private static void genRandom(Text t, int len, StringBuilder sb) { sb.setLength(0); for (int i = 0; i < len; ++i) { sb.append(Integer.toString(r.nextInt(26) + 10, 36)); } t.set(sb.toString()); } public int compare(int i, int j) { final int ii = indices[i]; final int ij = indices[j]; return comparator.compare(bytes, offsets[ii], ((ii + 1 == indices.length) ? eob : offsets[ii + 1]) - offsets[ii], bytes, offsets[ij], ((ij + 1 == indices.length) ? eob : offsets[ij + 1]) - offsets[ij]); } public void swap(int i, int j) { int tmp = indices[i]; indices[i] = indices[j]; indices[j] = tmp; } public String[] getValues() { return check; } public String[] getSorted() throws IOException { String[] ret = new String[indices.length]; Text t = new Text(); DataInputBuffer dib = new DataInputBuffer(); for (int i = 0; i < ret.length; ++i) { int ii = indices[i]; dib.reset(bytes, offsets[ii], ((ii + 1 == indices.length) ? eob : offsets[ii + 1]) - offsets[ii]); t.readFields(dib); ret[i] = t.toString(); } return ret; } } }