/*
* Copyright 2004, 2005, 2006 Odysseus Software GmbH
*
* Licensed 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.
*/
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
/**
* Utility class providing some useful static sort methods. The sort routines
* all return index permutations p such that data[p[0]],..,data[p[data.length-1]]
* is in sorted order. The data array itself is not modified.
* To actually rearrange the array elements, the inverse of p can be used to
* permute the array, such that data[0],..,data[data.length-1] is in sorted
* order. Use getIterator(p, data)
to iterate in sorted order.
* A code example may show you what to do next:
*
* String[] colors = { "red", "green", "blue" };
* int[] p = SortUtils.sort(colors, new StringComparator());
* // --> (colors[p[0]], colors[p[1]], colors[p[2]]) == ("blue","green","red")
* Iterator iter = SortUtils.getIterator(p, colors)
* // --> (iter.next(), iter.next(), iter.next()) == ("blue","green","red")
* SortUtils.permute(SortUtils.inverse(p), colors, true);
* // --> (colors[0], colors[1], colors[2]) == ("blue","green","red")
*
* Stable sorts (preserving order of equal elements) are supported.
* Sorting is done using quick-sort mith median of 3 (and insertion-sort
* for small ranges).
*
* @author Christoph Beck
*/
public class SortUtils {
/**
* Helper class used to perform quicksort.
*
* @author Christoph Beck
*/
static final class QuickSorter {
private static final int INSERTIONSORT_THRESHOLD = 7;
private final Object[] data;
QuickSorter(Object[] data) {
this.data = data;
}
private int compare(Comparator cmp, boolean stable, int i, int j) {
int result = cmp.compare(data[i], data[j]);
if (result == 0 && stable && i != j) {
result = i < j ? -1 : 1;
}
return result;
}
private int med3(Comparator cmp, int a, int b, int c) {
return (compare(cmp, false, a, b) < 0 ?
(compare(cmp, false, b, c) < 0 ? b : compare(cmp, false, a, c) < 0 ? c : a) :
(compare(cmp, false, b, c) > 0 ? b : compare(cmp, false, a, c) < 0 ? c : a));
}
private int pivot(int[] indices, Comparator cmp, int lo, int hi) {
return med3(cmp, indices[lo + 1], indices[(lo + hi) / 2], indices[hi - 1]);
}
private void swap(int[] indices, int i, int j) {
int tmp = indices[i];
indices[i] = indices[j];
indices[j] = tmp;
}
private void insertionSort(int[] indices, Comparator cmp, boolean stable, int lo, int hi) {
for (int i = lo; i <= hi; i++) {
for (int j = i; j > lo && compare(cmp, stable, indices[j-1], indices[j]) > 0; j--) {
swap(indices, j-1, j);
}
}
}
private void quickSort(int[] indices, Comparator cmp, boolean stable, int lo0, int hi0) {
int pivot = pivot(indices, cmp, lo0, hi0);
int lo = lo0, hi = hi0;
while (lo <= hi) {
while (lo < hi0 && compare(cmp, stable, pivot, indices[lo]) > 0)
++lo;
while (hi > lo0 && compare(cmp, stable, pivot, indices[hi]) < 0)
--hi;
if (lo <= hi) {
swap(indices, lo++, hi--);
}
}
sort(indices, cmp, stable, lo0, hi);
sort(indices, cmp, stable, lo, hi0);
}
void sort(int[] indices, Comparator cmp, boolean stable, int lo, int hi) {
if (hi - lo < INSERTIONSORT_THRESHOLD) {
insertionSort(indices, cmp, stable, lo, hi);
} else {
quickSort(indices, cmp, stable, lo, hi);
}
}
void sort(int[] indices, Comparator cmp, boolean stable) {
sort(indices, cmp, stable, 0, indices.length - 1);
}
int[] sort(Comparator cmp, boolean stable) {
int[] indices = identity(data.length);
sort(indices, cmp, stable);
return indices;
}
}
/**
* Create identity permutation, that is {0, 1, ..., n}
*/
public static int[] identity(int n) {
int[] indices = new int[n];
for (int i = 0; i < n; i++)
indices[i] = i;
return indices;
}
/**
* Create reverse permutation, that is {n-1, .... 1, 0}
*/
public static int[] reverse(int n) {
int[] indices = new int[n];
for (int i = 0; i < n; i++)
indices[i] = n - i - 1;
return indices;
}
/**
* Compute inverse permutation
*/
public static int[] inverse(int[] p) {
int[] pi = new int[p.length];
for (int i = 0; i < pi.length; i++)
pi[p[i]] = i;
return pi;
}
/**
* Rearrange the specified data according to the specified permutation.
* That is, the array is rearranged, such that
* data_after[p[i]] == data_before[i]
.
* @param data data to be permuted
* @param p the permutation
* @param clone if true, rearrange a clone instead of the original data;
* @return the permuted array (which is the original reference if clone == false)
*/
public static Object[] permute(int[] p, Object[] data, boolean clone) {
Object[] permuted = null;
if (clone) {
permuted = (Object[])data.clone();
for (int i = 0; i < data.length; i++)
permuted[p[i]] = data[i];
} else {
// run thru cycles
int i = 0;
while (i < p.length) {
if (p[i] < 0 || p[i] == i) // skip already handled and cycles of length 1
++i;
else { // start a new cycle
int j = p[i];
Object save = data[i];
while (p[j] >= 0) {
Object tmp = data[j];
data[j] = save;
save = tmp;
i = j;
j = p[j];
p[i] = -1;
}
}
}
permuted = data;
}
return permuted;
}
/**
* Answer iterator, which iterates over specified data array according
* to the specified permutation, that is
* data[p[0]],..,data[p[data.length-1]]
*/
public static Iterator getIterator(final int[] p, final Object[] data) {
return new Iterator() {
int pos = 0;
public boolean hasNext() {
return pos < data.length;
}
public Object next() {
return data[p[pos++]];
}
public void remove() {
throw new UnsupportedOperationException("Cannot remove from immutable iterator!");
}
};
}
/**
* Answer iterator, which iterates over specified data list according
* to the specified permutation, that is
* data.get(p[0]),..,data.get(p[data.length-1])
*/
public static Iterator getIterator(final int[] p, final List data) {
return new Iterator() {
int pos = 0;
public boolean hasNext() {
return pos < data.size();
}
public Object next() {
return data.get(p[pos++]);
}
public void remove() {
throw new UnsupportedOperationException("Cannot remove from immutable iterator!");
}
};
}
// /**
// * An improved heap builder.
// * Assumes children of i at 2i and 2i+1 (requires i>0)
// */
// private static void cheap(int[] indices, Object[] data, Comparator comparator, int i, int j) {
// int k = (i << 1);
// if (k > j)
// return;
// while (k < j) {
// if (comparator.compare(data[indices[k]], data[indices[k + 1]]) < 0)
// k++;
// k <<= 1;
// }
// if (k > j)
// k >>= 1;
// while (comparator.compare(data[indices[k]], data[indices[i]]) < 0)
// k >>= 1;
// int t1 = indices[i], t2;
// while (k > i) {
// t2 = indices[k];
// indices[k] = t1;
// k >>= 1;
// t1 = indices[k];
// indices[k] = t2;
// k >>= 1;
// }
// if (k == i)
// indices[i] = t1;
// }
//
// /**
// * Do a (clever) heapsort.
// *
// * @param comparator Comparator object specifying the sort order.
// */
// public static void cheapSort(int[] indices, Object[] data, Comparator comparator) {
// int n = data.length;
// if (n > 1) {
// int i;
// int m = 0;
// for (i = 1; i < n; i++)
// if (comparator.compare(data[indices[i]], data[indices[m]]) < 0)
// m = i;
// if (m > 0) {
// int t = indices[0];
// indices[0] = indices[m];
// indices[m] = t;
// }
// if (n > 2) {
// for (i = n / 2; i > 1; i--)
// cheap(indices, data, comparator, i, n - 1);
// for (i = n - 1; i > 1; i--) {
// cheap(indices, data, comparator, 1, i);
// int t = indices[1];
// indices[1] = indices[i];
// indices[i] = t;
// }
// }
// }
// }
//
// /**
// * Perform a cheapsort
// */
// public static int[] cheapSort(Object[] data, Comparator comparator) {
// int[] indices = identity(data.length);
// cheapSort(indices, data, comparator);
// return indices;
// }
/**
* Do a sort on indices.
* @param data data to be sorted
* @param comparator comparator to use
* @param stable do a stable sort iff true
* @param indices into data (any permutation of 0,..data.length-1).
*/
public static void sort(int[] indices, Object[] data, Comparator comparator, boolean stable) {
new QuickSorter(data).sort(indices, comparator, stable);
}
/**
* Do a sort on indices.
* @param data data to be sorted
* @param comparator comparator to use
* @param stable do a stable sort iff true
* @return permutation p such that data[p[0]],..,data[p[data.length-1]] is in sorted order
*/
public static int[] sort(Object[] data, Comparator comparator, boolean stable) {
int[] indices = identity(data.length);
sort(indices, data, comparator, stable);
return indices;
}
/**
* Do an unstable sort.
* @param data data to be sorted
* @param comparator comparator to use
* @return permutation p such that data[p[0]],..,data[p[data.length-1]] is in sorted order
*/
public static int[] sort(Object[] data, Comparator comparator) {
return sort(data, comparator, false);
}
/**
* Do an unstable sort.
* @param data data to be sorted
* @param indices into data (permutation of 0,..data.length-1).
*/
public static void sort(int[] indices, Object[] data, Comparator comparator) {
sort(indices, data, comparator, false);
}
/**
* Test method
*/
public static void main(String[] args) {
Comparator cmp = new Comparator() {
public int compare(Object o1, Object o2) {
return ((Comparable)o1).compareTo(o2);
}
};
int n = 1000000;
if (args.length == 1)
try {
n = Integer.parseInt(args[0]);
} catch (Exception e) {
System.err.println(e);
}
System.out.println("Generating " + n + " random integers...");
java.util.Random random = new java.util.Random();
Integer[] data = new Integer[n];
for (int i = 0; i < n; i++) {
data[i] = new Integer(Math.abs(random.nextInt()));
// data[i] = new Integer(i);
}
int[] indices;
long time;
System.out.print("Arrays.sort...");
time = System.currentTimeMillis();
Integer[] clone = (Integer[])data.clone();
Arrays.sort(clone, cmp);
System.out.println(System.currentTimeMillis()-time + "ms");
System.out.print("quicksort...");
indices = identity(n);
time = System.currentTimeMillis();
sort(indices, data, cmp, false);
System.out.println(System.currentTimeMillis()-time + "ms");
for (int i = 1; i < n; i++)
if (cmp.compare(data[indices[i-1]], data[indices[i]]) > 0)
System.err.println("proplem: quickSort at " + i);
System.out.print("quicksort stable...");
// indices = identity(n);
time = System.currentTimeMillis();
sort(indices, data, cmp, true);
System.out.println(System.currentTimeMillis()-time + "ms");
for (int i = 1; i < n; i++) {
int res = cmp.compare(data[indices[i-1]], data[indices[i]]);
if (res > 0)
System.err.println("proplem: quickSort stable at " + i);
if (res == 0 && indices[i-1] > indices[i])
System.err.println("proplem: quickSort stable (not stable) at " + i);
}
// System.out.print("cheapsort...");
// time = System.currentTimeMillis();
// indices = cheapSort(data, cmp);
// System.out.println(System.currentTimeMillis()-time + "ms");
// for (int i = 1; i < n; i++)
// if (cmp.compare(data[indices[i-1]], data[indices[i]]) > 0)
// System.err.println("proplem: cheapSort at " + i);
System.out.print("permutate copy...");
time = System.currentTimeMillis();
Object[] data_copy = permute(inverse(indices), data, true);
System.out.println(System.currentTimeMillis()-time + "ms");
for (int i = 1; i < n; i++)
if (cmp.compare(data_copy[i-1], data_copy[i]) > 0)
System.err.println("proplem: permute copy at " + i);
System.out.print("permutate original...");
time = System.currentTimeMillis();
permute(inverse(indices), data, false);
System.out.println(System.currentTimeMillis()-time + "ms");
for (int i = 1; i < n; i++)
if (cmp.compare(data[i-1], data[i]) > 0)
System.err.println("proplem: permute original at " + i);
}
}