/*******************************************************************************
* Copyright 2012-2014 Analog Devices, Inc.
*
* 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.
********************************************************************************/
package com.analog.lyric.collect;
import java.util.Comparator;
/**
* Provides efficient implementations of variants of the <em>selection</em> problem.
* <p>
* The selection problem involves efficiently finding the nth element in a set of unordered
* elements for some ordering relation. Obviously this can be done trivially by sorting,
* which requires O(n log n) operations, but this class implements an algorithm that
* takes O(n) time.
* <p>
* @since 0.06
*/
public abstract class Selection
{
/**
* Returns the indices of the k lowest elements.
* <p>
* Returns a newly allocated array of the indices of the {@code k} lowest
* elements in {@linkplain Comparable natural order}.
* <p>
* @param array is a non-empty array in any order.
* @param k is in the range [1, array.length]
*/
public static int [] findFirstKIndices(double [] array, int k)
{
//select the kth
double val = select(array,k-1);
int [] result = new int[k];
for (int index = 0, i = -1; index < k; ++index)
{
while (array[++i] > val) {}
result[index] = i;
}
return result;
}
/**
* Returns the indices of the k lowest elements.
* <p>
* Returns a newly allocated array of the indices of the {@code k} lowest
* elements in {@linkplain Comparable natural order}.
* <p>
* @param array is a non-empty array in any order.
* @param k is in the range [1, array.length]
*/
public static <T extends Comparable<T>> int [] findFirstKIndices(T[] array, int k)
{
//select the kth
T obj = select(array,k-1);
int [] result = new int[k];
for (int index = 0, i = -1; index < k; index++)
{
while (array[++i].compareTo(obj) > 0) {}
result[index] = i;
}
return result;
}
/**
* Returns the indices of the k highest elements.
* <p>
* Returns a newly allocated array of the indices of the {@code k} highest
* elements in {@linkplain Comparable natural order}.
* <p>
* @param array is a non-empty array in any order.
* @param k is in the range [1, array.length]
*/
public static int [] findLastKIndices(double [] array, int k)
{
double val = select(array,array.length-k);
int [] result = new int[k];
for (int index = 0, i = -1; index < k; index++)
{
while (array[++i] < val) {}
result[index] = i;
}
return result;
}
/**
* Returns the indices of the k highest elements.
* <p>
* Returns a newly allocated array of the indices of the {@code k} highest
* elements in {@linkplain Comparable natural order}.
* <p>
* @param array is a non-empty array in any order.
* @param k is in the range [1, array.length]
*/
public static <T extends Comparable<T>> int [] findLastKIndices(T[] array, int k)
{
T obj = select(array,array.length-k);
int [] result = new int[k];
for (int index = 0, i = -1; index < k; index++)
{
while (array[++i].compareTo(obj) < 0) {}
result[index] = i;
}
return result;
}
/**
* Returns the kth element in array in natural order.
* <p>
* Returns index of kth element in {@linkplain Comparable natural order}.
* <p>
* @param array a non-empty array in any order.
* @param k a number in the range [0, array.length-1]
*/
public static double select(double [] array, int k)
{
return selectMutably(array.clone(), k);
}
/**
* Returns the kth element in array in natural order.
* <p>
* Returns index of kth element in {@linkplain Comparable natural order}.
* <p>
* The {@code array} will be rearranged so that the kth element is at position
* k and all elements less than the kth element will be before it in the array.
* <p>
* @param array a non-empty array in any order.
* @param k a number in the range [0, array.length-1]
*/
public static double selectMutably(double [] array, int k)
{
return select(array,0,array.length-1,k);
}
/**
* Returns the kth element in array in natural order.
* <p>
* Returns index of kth element in {@linkplain Comparable natural order}.
* <p>
* @param array a non-empty array in any order.
* @param k a number in the range [0, array.length-1]
*/
public static <T extends Comparable<T>> T select(T[] array, int k)
{
return selectMutably(array.clone(), k);
}
/**
* Returns the kth element in array in natural order.
* <p>
* Returns index of kth element in {@linkplain Comparable natural order}.
* <p>
* The {@code array} will be rearranged so that the kth element is at position
* k and all elements less than the kth element will be before it in the array.
* <p>
* @param array a non-empty array in any order.
* @param k a number in the range [0, array.length-1]
*/
public static <T extends Comparable<T>> T selectMutably(T[] array, int k)
{
return select(array,0,array.length-1,k);
}
/**
* Returns the kth element in array using specified ordering..
* <p>
* Returns index of kth element in ordering defined by {@code comparator}.
* <p>
* @param array a non-empty array in any order.
* @param k a number in the range [0, array.length-1]
*/
public static <T> T select(Comparator<T> comparator, T[] array, int k)
{
return selectMutably(comparator, array.clone(), k);
}
/**
* Returns the kth element in array using specified ordering..
* <p>
* Returns index of kth element in ordering defined by {@code comparator}.
* <p>
* The {@code array} will be rearranged so that the kth element is at position
* k and all elements less than the kth element will be before it in the array.
* <p>
* @param array a non-empty array in any order.
* @param k a number in the range [0, array.length-1]
*/
public static <T> T selectMutably(Comparator<T> comparator, T[] array, int k)
{
return select(comparator, array, 0, array.length-1, k);
}
/*-----------------
* Private methods
*/
/**
* Given a list, a left index, a right index, and a pivot index, this method
* will create a partition between all elements less than the pivot value
* and all elements greater than the pivot value. The operation is done in
* place and returns the new position of the original pivot index.
*
* @param list is a non-empty unordered array
* @param left is in the range [0, list.length-1]
* @param right is in the range [0, list.length-1]
* @param pivotIndex is in the range [0, list.length-1]
*/
private static <T extends Comparable<T>> int partition(T[] list, int left, int right, int pivotIndex)
{
final T pivotValue = list[pivotIndex];
//swap list[pivotIndex] and list[right] // Move pivot to end
list[pivotIndex] = list[right];
list[right] = pivotValue;
int storeIndex = left;
for (int i = left; i <= right; i++)
{
final T cur = list[i];
if (cur.compareTo(pivotValue) < 0)
{
//swap list[storeIndex] and list[i]
list[i] = list[storeIndex];
list[storeIndex] = cur;
storeIndex++;
}
}
//swap list[right] and list[storeIndex] // Move pivot to its final place
final T tmp = list[right];
list[right] = list[storeIndex];
list[storeIndex] = tmp;
return storeIndex;
}
/**
* Given a list, a left index, a right index, and an index, k, this method
* will return the kth smallest object.
*
* @param list is a non-empty unordered array
* @param left is in the range [0, list.length-1]
* @param right is in the range [0, list.length-1]
* @param k is in the range [0,list.length-1]
*/
private static <T extends Comparable<T>> T select(T[] list, int left, int right, int k)
{
++k; // Convert to one-based indexing
while (true)
{
//select pivotIndex between left and right
int pivotIndex = (right-left)/2+left;
int pivotNewIndex = partition(list, left, right, pivotIndex);
int pivotDist = pivotNewIndex - left + 1;
if (pivotDist == k)
{
return list[pivotNewIndex];
}
else if ( k < pivotDist)
{
right = pivotNewIndex - 1;
}
else
{
k = k - pivotDist;
left = pivotNewIndex + 1;
}
}
}
/**
* Given a list, a left index, a right index, and a pivot index, this method
* will create a partition between all elements less than the pivot value
* and all elements greater than the pivot value. The operation is done in
* place and returns the new position of the original pivot index.
*
* @param list is a non-empty unordered array
* @param left is in the range [0, list.length-1]
* @param right is in the range [0, list.length-1]
* @param pivotIndex is in the range [0, list.length-1]
*/
private static <T> int partition(Comparator<T> comparator, T[] list, int left, int right, int pivotIndex)
{
final T pivotValue = list[pivotIndex];
//swap list[pivotIndex] and list[right] // Move pivot to end
list[pivotIndex] = list[right];
list[right] = pivotValue;
int storeIndex = left;
for (int i = left; i <= right; i++)
{
final T cur = list[i];
if (comparator.compare(cur, pivotValue) < 0)
{
//swap list[storeIndex] and list[i]
list[i] = list[storeIndex];
list[storeIndex] = cur;
storeIndex++;
}
}
//swap list[right] and list[storeIndex] // Move pivot to its final place
final T tmp = list[right];
list[right] = list[storeIndex];
list[storeIndex] = tmp;
return storeIndex;
}
/**
* Given a list, a left index, a right index, and an index, k, this method
* will return the kth smallest object.
*
* @param list is a non-empty unordered array
* @param left is in the range [0, list.length-1]
* @param right is in the range [0, list.length-1]
* @param k is in the range [0,list.length-1]
*/
private static <T> T select(Comparator<T> comparator, T[] list, int left, int right, int k)
{
++k; // Convert to one-based indexing
while (true)
{
//select pivotIndex between left and right
int pivotIndex = (right-left)/2+left;
int pivotNewIndex = partition(comparator, list, left, right, pivotIndex);
int pivotDist = pivotNewIndex - left + 1;
if (pivotDist == k)
{
return list[pivotNewIndex];
}
else if ( k < pivotDist)
{
right = pivotNewIndex - 1;
}
else
{
k = k - pivotDist;
left = pivotNewIndex + 1;
}
}
}
/**
* Given a list, a left index, a right index, and a pivot index, this method
* will create a partition between all elements less than the pivot value
* and all elements greater than the pivot value. The operation is done in
* place and returns the new position of the original pivot index.
*
* @param list is a non-empty unordered array
* @param left is in the range [0, list.length-1]
* @param right is in the range [0, list.length-1]
* @param pivotIndex is in the range [0, list.length-1]
*/
private static int partition(double[] list, int left, int right, int pivotIndex)
{
final double pivotValue = list[pivotIndex];
//swap list[pivotIndex] and list[right] // Move pivot to end
list[pivotIndex] = list[right];
list[right] = pivotValue;
int storeIndex = left;
for (int i = left; i <= right; i++)
{
final double cur = list[i];
if (cur < pivotValue)
{
//swap list[storeIndex] and list[i]
list[i] = list[storeIndex];
list[storeIndex] = cur;
storeIndex++;
}
}
//swap list[right] and list[storeIndex] // Move pivot to its final place
final double tmp = list[right];
list[right] = list[storeIndex];
list[storeIndex] = tmp;
return storeIndex;
}
/**
* Given a list, a left index, a right index, and an index, k, this method
* will return the kth smallest object.
*
* @param list is a non-empty unordered array
* @param left is in the range [0, list.length-1]
* @param right is in the range [0, list.length-1]
* @param k is in the range [0,list.length-1]
*/
private static double select(double[] list, int left, int right, int k)
{
++k; // Convert to one-based indexing
while (true)
{
//select pivotIndex between left and right
int pivotIndex = (right-left)/2+left;
int pivotNewIndex = partition(list, left, right, pivotIndex);
int pivotDist = pivotNewIndex - left + 1;
if (pivotDist == k)
{
return list[pivotNewIndex];
}
else if ( k < pivotDist)
{
right = pivotNewIndex - 1;
}
else
{
k = k - pivotDist;
left = pivotNewIndex + 1;
}
}
}
}