{
/**
* @see prisms.util.ArrayUtils.DifferenceListenerE#identity(java.lang.Object,
* java.lang.Object)
*/
boolean identity(T1 o1, T2 o2);
/**
* @see prisms.util.ArrayUtils.DifferenceListenerE#added(java.lang.Object,
* int, int)
*/
T1 added(T2 o, int mIdx, int retIdx);
/**
* @see prisms.util.ArrayUtils.DifferenceListenerE#removed(java.lang.Object,
* int, int, int)
*/
T1 removed(T1 o, int oIdx, int incMod, int retIdx);
/**
* @see prisms.util.ArrayUtils.DifferenceListenerE#set(java.lang.Object,
* int, int, java.lang.Object, int, int)
*/
T1 set(T1 o1, int idx1, int incMod, T2 o2, int idx2, int retIdx);
}
/**
*
* This listener contains all information needed to reconcile two arrays
* using the
* {@link ArrayUtils#adjust(Object[], Object[], prisms.util.ArrayUtils.DifferenceListenerE)}
* method.
*
*
*
* There are many different indices that may or may not be relevant to a
* particular listener:
*
* - oIdx: The index of the element in the original array
* - incMod: The index of the element in a copy of the original
* array that has been modified incrementally for each add, remove, and
* reorder operation
* - mIdx: The index of the element in the modifier array
* - retIdx: The index that the returned element (if not null) will
* be at in the result array
*
* oIdx and mIdx are self-explanatory. incMod and retIdx are useful for
* listeners that modify an external ordered set incrementally with each
* listener method invocation.
*
*
*
* An Example
* Suppose there is a list, L, whose data can only be modified
* incrementally by the methods add(value, index), remove(index), and
* move(fromIndex, toIndex), but can be accessed in batch by the getData
* method, which returns an array of all data in the list. Suppose the list
* needs to be modified to represent a new arbitrary set of data, d.
* d may or may not contain elements represented in L. These
* elements, if present, may be in different order and new items not present
* in L may be present in d. To modify L to represent
* the data in d in the correct order, the
* {@link ArrayUtils#adjust(Object[], Object[], DifferenceListenerE)} method
* should be called with L.getData(), d, and a listener whose
* methods perform the following operations:
*
* - {@link #identity(Object, Object)}: Tests the two elements to see if
* the item from L represents the item in d, possibly by
* simply calling {@link Object#equals(Object)}
* - {@link #added(Object, int, int)}: Invokes L.add(o2, retIdx).
* - {@link #removed(Object, int, int, int)}: Invokes
* L.remove(incMod). Using incMod accounts for any previous
* modifications that may have been performed on the list from the listener.
*
* - {@link #set(Object, int, int, Object, int, int)}: Invokes
* L.move(incMod, retIdx). This again accounts for all previous
* changes to the list and moves the element to the index intended by the
* adjust method.
*
* The end result of this invocation will be that L contains all the
* data in d, only the data in d, and is ordered identically
* to d. The returned array may be ignored.
*
*
*
* Alternate Operation:
*
* - If it is intended that the modification operation should only add
* data to L, the remove method may do nothing and return o. The
* return value will cause the adjust method to update future indexes,
* assuming that the value is kept and not removed.
* - If order is not important or no move(fromIndex, toIndex) method
* exists, the set method may do nothing and return o1. This will have no
* effect on the result of the operation other than leaving the items
* originally in L (those that were also present in d) in
* their original order.
* - Other permutations may exist depending on the business logic used by
* the listener.
*
*
*
*
* The listener is entirely free to choose whether items are added or
* removed from the original array and its representations by modifying
* whether the return value from those methods is null. The adjust method
* does not care what is returned from the add/remove/set methods except
* whether the value is null. The listener is free to perform index moving
* operations or not. This method will never cause an index out of bounds
* error assuming the related data sets are not modified by anything other
* than the listener and the data in the arrays passed into the adjust
* method do not change.
*
*
* @param
* The type of the original array
* @param
* The type of the modifying array
* @param
* The type of exception that may be thrown
*/
public static interface DifferenceListenerE {
/**
* Tests the identity of an item from each of the two sets. This method
* should return true if one item is a representation of the other or
* both are a representation of the same piece of data.
*
* @param o1
* The object from the first set
* @param o2
* The object from the second set
* @return Whether the two objects are fundamentally equivalent
* @throws E
* If an error occurs in this method
*/
boolean identity(T1 o1, T2 o2) throws E;
/**
* Called when a value is found in the second set that is not present in
* the first set
*
* @param o
* The unmatched object
* @param mIdx
* The index in the second array where the unmatched object
* was found
* @param retIdx
* The index that the new element will be inserted into the
* final array
* @return The new T1-type element to insert into the return array, or
* null if the new element is not to be inserted
* @throws E
* If an error occurs in this method
*/
T1 added(T2 o, int mIdx, int retIdx) throws E;
/**
* Called when a value is found in the first set that is not present in
* the second set
*
* @param o
* The unmatched object
* @param oIdx
* The index in the first array where the unmatched object
* was found
* @param incMod
* The index where the unmatched element would occur the
* incrementally modified original array
* @param retIdx
* The index in the final array where the replacement will be
* located if a non-null value is returned
* @return null if the original object is to be removed, otherwise its
* replacement
* @throws E
* If an error occurs in this method
*/
T1 removed(T1 o, int oIdx, int incMod, int retIdx) throws E;
/**
* Called when elements in the two arrays match
*
* @param o1
* The element in the first array
* @param idx1
* The index of the element in the first array
* @param incMod
* The index where the element in the original array would
* occur the incrementally modified original array
* @param o2
* The element in the second array
* @param idx2
* The index of the element in the second array
* @param retIdx
* The index of the returned element in the final array
* @return The element to be inserted in the final array, or null if the
* element is to be removed
* @throws E
* If an error occurs in this method
*/
T1 set(T1 o1, int idx1, int incMod, T2 o2, int idx2, int retIdx)
throws E;
}
/**
*
* Reconciles differences between two ordered sets of objects. Allows a
* programmer highly customized control between two different
* representations of a data set. The arrays are compared and the listener
* is notified when any differences are encountered, allowing it to
* determine the composition of the returned array and/or perform
* well-defined operations represented by the differences or similarities.
*
*
* @param
* The type of the original array
* @param
* The type of the modifying array
* @param
* The type of exception that may be thrown
* @param original
* The original array
* @param modifier
* The modifying array
* @param dl
* The listener to determine how to deal with differences between
* the two arrays
* @return A final array that is the result of applying select changes
* between the original and the modifying arrays
* @throws E
* If the {@link DifferenceListenerE} throws an exception
*/
public static T1[] adjust(T1[] original,
T2[] modifier, DifferenceListenerE dl) throws E {
ArrayAdjuster adjuster = new ArrayAdjuster(
original, modifier, dl);
return adjuster.adjust();
}
static final Object NULL = new Object();
/**
* Adjusts arrays. This is the more complicated and capable structure used
* by {@link ArrayUtils#adjust(Object[], Object[], DifferenceListenerE)}
*
* @param
* The type of the original array
* @param
* The type of the modifying array
* @param
* The type of exception that may be thrown
*/
public static class ArrayAdjuster {
private final T1[] original;
private final int[] oIdxAdj;
private final int[] oMappings;
private final T2[] modifier;
private final int[] mMappings;
private final boolean[] entriesSet;
private final DifferenceListenerE dl;
private int maxLength;
private boolean isNullElement;
/**
* Creates an adjuster that can adjust one array by another
*
* @param o
* The original array
* @param m
* The modified array
* @param listener
* The listener to determine how to deal with differences
* between the two arrays
* @see ArrayUtils#adjust(Object[], Object[], DifferenceListenerE)
*/
public ArrayAdjuster(T1[] o, T2[] m,
DifferenceListenerE listener) {
original = o;
oIdxAdj = new int[o.length];
oMappings = new int[o.length];
modifier = m;
mMappings = new int[m.length];
entriesSet = new boolean[m.length];
dl = listener;
}
private void init() throws E {
int o, m, r = original.length + modifier.length;
for (m = 0; m < modifier.length; m++)
mMappings[m] = -1;
for (o = 0; o < original.length; o++) {
oIdxAdj[o] = o;
oMappings[o] = -1;
for (m = 0; m < modifier.length; m++) {
if (mMappings[m] >= 0)
continue;
if (dl.identity(original[o], modifier[m])) {
oMappings[o] = m;
mMappings[m] = o;
r--;
break;
}
}
}
maxLength = r;
}
/**
* Adjusts the arrays set from the constructor
*
* @return The adjusted array
* @throws E
* If an error occurs in one of the listener's methods
* @see ArrayUtils#adjust(Object[], Object[], DifferenceListenerE)
*/
public T1[] adjust() throws E {
init();
int m, o, r = 0;
Object[] ret = new Object[maxLength];
for (o = 0; o < original.length && oMappings[o] < 0; o++)
if (remove(o, -1, ret, r))
r++;
for (m = 0; m < modifier.length; m++) {
// Add or set each modifier
o = mMappings[m];
if (o >= 0) {
if (set(o, m, ret, r))
r++;
/* Remove the originals that occur before the next match */
for (o++; o < original.length && oMappings[o] < 0; o++) {
if (ret[r] != null) {
Object temp = ret[r];
for (int r2 = r; r < ret.length - 1 && temp != null; r2++) {
Object temp2 = ret[r2 + 1];
ret[r2 + 1] = temp;
temp = temp2;
}
}
if (remove(o, m + 1, ret, r))
r++;
}
} else {
if (ret[r] != null) {
Object temp = ret[r];
for (int r2 = r; r < ret.length - 1 && temp != null; r2++) {
Object temp2 = ret[r2 + 1];
ret[r2 + 1] = temp;
temp = temp2;
}
}
if (add(m, ret, r))
r++;
}
}
for (int i = 0; i < r; i++)
if (ret[i] == NULL)
ret[i] = null;
T1[] actualRet = (T1[]) Array.newInstance(original.getClass()
.getComponentType(), r);
System.arraycopy(ret, 0, actualRet, 0, r);
return actualRet;
}
/**
* Marks the current value as a null value. If an actual null were
* returned, adjust would interpret this as meaning the element should
* be removed from the array, with subsequent indices being affected. If
* this method is called from {@link DifferenceListenerE}.add, remove,
* or set, the element's place will be saved and that element in the
* returned array will be null.
*/
public void nullElement() {
isNullElement = true;
}
static void mergeSort(int[] order, int[] distances, int start, int end) {
if (end - start <= 1)
return;
int mid = (start + end + 1) / 2;
mergeSort(order, distances, start, mid);
mergeSort(order, distances, mid, end);
while (start < mid && mid < end) {
if (distances[start] < distances[mid]) // Reverse order
{
int temp = distances[mid];
int temp2 = order[mid];
for (int i = mid; i > start; i--) {
distances[i] = distances[i - 1];
order[i] = order[i - 1];
}
distances[start] = temp;
order[start] = temp2;
mid++;
}
start++;
}
}
private boolean add(int m, Object[] ret, int r) throws E {
entriesSet[m] = true;
T1 item = dl.added(modifier[m], m, r);
if (isNullElement) {
item = (T1) NULL;
isNullElement = false;
}
// Adjust the incremental modification indexes
if (item != null) {
ret[r] = item;
for (int i = 0; i < oIdxAdj.length; i++)
if (oIdxAdj[i] >= r)
oIdxAdj[i]++;
} else {
for (; r < ret.length - 1; r++)
ret[r] = ret[r + 1];
}
return item != null;
}
private boolean remove(int o, int m, Object[] ret, int r) throws E {
T1 item = dl.removed(original[o], o, oIdxAdj[o], r);
if (isNullElement) {
item = (T1) NULL;
isNullElement = false;
}
// Adjust the incremental modification indexes
if (item == null) {
oIdxAdj[o] = -1;
for (; o < oIdxAdj.length; o++)
oIdxAdj[o]--;
} else
ret[r] = item;
return item != null;
}
private boolean set(int o, int m, Object[] ret, int r) throws E {
if (entriesSet[m])
return ret[r] != null;
if (oIdxAdj[o] > r && oIdxAdj[o] - r <= 10) {
/*
* If a few elements have been moved forward several indices, we
* want to fire a few move events on those elements rather than
* many move events for a few index differences.
*/
int o2;
for (o2 = o - 1; o2 >= 0; o2--)
if (oMappings[o2] > m)
set(o2, oMappings[o2], ret, r + oMappings[o2] - m);
}
entriesSet[m] = true;
T1 item = dl.set(original[o], o, oIdxAdj[o], modifier[m], m, r);
if (isNullElement) {
item = (T1) NULL;
isNullElement = false;
}
// Adjust the incremental modification indexes
if (item != null) {
ret[r] = item;
int oAdj = oIdxAdj[o];
if (r > oAdj) { // Element moved forward--decrement incMods up
// to the new index
for (int i = 0; i < oIdxAdj.length; i++)
if (oIdxAdj[i] >= oAdj && oIdxAdj[i] <= r)
oIdxAdj[i]--;
} else if (r < oAdj) { // Element moved backward--increment
// incMods up to the original index
for (int i = 0; i < oIdxAdj.length; i++)
if (oIdxAdj[i] >= r && oIdxAdj[i] < oAdj)
oIdxAdj[i]++;
}
} else {
oIdxAdj[o] = -1;
for (int i = 0; i < oIdxAdj.length; i++)
if (oIdxAdj[i] > r)
oIdxAdj[i]--;
for (; r < ret.length - 1; r++)
ret[r] = ret[r + 1];
}
return item != null;
}
}
/**
* A listener used for {@link #sort(Object [], SortListener)}
*
* @param
* The type of object to sort
*/
public static interface SortListener extends java.util.Comparator {
/**
* Called just after two elements are swapped.
*
* @param o1
* The first element being moved
* @param idx1
* The index (in the pre-swap array) of the first element
* @param o2
* The second element being moved
* @param idx2
* The index (in the pre-swap array) of the second element
*/
void swapped(T o1, int idx1, T o2, int idx2);
}
/**
* Sorts an array, allowing for complex operations during the sort, such as
* sorting arrays in parallel.
*
* This is an implementation of selection sort. Selection sort is used
* because although it may perform many more comparison operations than
* other methods, there is no other method that performs fewer swaps. It is
* anticipated that this algorithm will be used most often in the case that
* multiple operations must take place during a swap, while comparison
* should be fairly quick.
*
* @param
* The type of the array to sort
* @param array
* The array to sort
* @param listener
* The listener to use for comparisons and to notify of swapping
*/
public static void sort(T[] array, SortListener listener) {
for (int i = 0; i < array.length - 1; i++) {
int min = findMin(array, i, listener);
if (min != i) {
T ta = array[i];
T tb = array[min];
array[i] = tb;
array[min] = ta;
listener.swapped(ta, i, tb, min);
}
}
}
private static int findMin(T[] array, int start,
SortListener listener) {
int minIdx = start;
for (int i = start + 1; i < array.length; i++)
if (listener.compare(array[i], array[minIdx]) < 0)
minIdx = i;
return minIdx;
}
/**
* Represents some statistical information calculated on a set of float data
*/
public static class ArrayStatistics {
/**
* The smallest non-NaN, non-infinite datum encountered by this
* statistics set
*/
public float min;
/**
* The largest non-NaN, non-infinite datum encountered by this
* statistics set
*/
public float max;
/**
* The number of NaN data encountered by this statistics set
*/
public int naNCount;
/**
* The number of +Inf data encountered by this statistics set
*/
public int posInfCount;
/**
* The number of -Inf data encountered by this statistics set
*/
public int negInfCount;
/**
* The number of non-NaN, non-infinite data encountered by this
* statistics set
*/
public int validCount;
/**
* The mean value of all non-NaN, non-infinite data encountered by this
* statistics set
*/
public float mean;
/**
* A running data measure that allows the standard deviation of all
* non-NaN, non-infinite data encountered by this statistics set
*/
private float q;
/**
* Creates an ArrayStatistics set
*/
public ArrayStatistics() {
min = Float.MAX_VALUE;
max = Float.MIN_VALUE;
mean = 0;
q = Float.NaN;
validCount = 0;
naNCount = 0;
posInfCount = 0;
negInfCount = 0;
}
/**
* Adds a value to this statistics set
*
* @param value
* The value to analyze
*/
public void inputValue(float value) {
if (Float.isNaN(value))
naNCount++;
else if (Float.isInfinite(value)) {
if (value > 0)
posInfCount++;
else
negInfCount++;
} else if (validCount == 0) {
mean = value;
q = 0;
validCount++;
} else {
if (value < min)
min = value;
if (value > max)
max = value;
q = q + (validCount * (mean - value) * (mean - value))
/ (validCount + 1);
validCount++;
mean = mean + (value - mean) / validCount;
}
}
/**
* @return The standard deviation of all non-NaN, non-infinite data
* encountered by this statistics set
*/
public float getSigma() {
return (float) Math.sqrt(q / (validCount - 1));
}
/**
* @return The total number of data encountered by this statistics set
*/
public int getTotalCount() {
return validCount + naNCount + posInfCount + negInfCount;
}
@Override
public String toString() {
StringBuilder ret = new StringBuilder();
ret.append(getTotalCount());
ret.append(" values ranged ");
ret.append(min);
ret.append(" to ");
ret.append(max);
ret.append("; mean=");
ret.append(mean);
ret.append(", st.dev.=");
ret.append(getSigma());
ret.append("; ");
ret.append(naNCount);
ret.append("NaN, ");
ret.append(posInfCount);
ret.append("+Inf, ");
ret.append(negInfCount);
ret.append("-Inf");
return ret.toString();
}
}
/**
* Performs a simple statistical analysis of an array of floats
*
* @param array
* An n-dimensional array of floats
* @param stats
* The ArrayStatistics object to populate, or null to create a
* new one
* @return The statistics object
*/
public static ArrayStatistics getStatistics(Object array,
ArrayStatistics stats) {
if (array == null)
return null;
if (stats == null)
stats = new ArrayStatistics();
if (array instanceof Object[]) {
for (Object subArray : (Object[]) array)
getStatistics(subArray, stats);
return stats;
}
if (!(array instanceof float[]))
throw new IllegalArgumentException(
"Cannot statistically analyze a "
+ array.getClass().getName());
for (float f : (float[]) array)
stats.inputValue(f);
return stats;
}
/**
* A testing method
*
* @param args
* The command-line arguments. The first argument is used to
* determine what test to run.
*/
public static void main(String[] args) {
String test;
if (args.length == 0)
test = "intSort";
else
test = args[0];
if (test.equals("intSort")) {
int[] random = new int[10000];
int[] random2 = new int[random.length];
for (int i = 0; i < random.length; i++)
random[i] = (int) (Math.random() * Integer.MAX_VALUE);
ArrayAdjuster.mergeSort(random2, random, 0, random.length);
boolean sorted = true;
for (int i = 0; i < random.length - 1; i++)
if (random[i] < random[i + 1]) {
sorted = false;
break;
}
System.out.println("Sort " + (sorted ? "succeeded" : "failed"));
} else if (test.equals("adjust")) {
final Integer[] start = new Integer[25];
for (int i = 0; i < start.length; i++)
start[i] = new Integer(i);
Integer[] modifier = new Integer[start.length];
for (int i = 0; i < modifier.length; i++) {
if (i % 5 != 0)
modifier[i] = new Integer(i);
else
modifier[i] = new Integer(i / 5 * start.length);
}
Integer[] result = adjust(start, modifier,
new DifferenceListener() {
public boolean identity(Integer o1, Integer o2) {
return o1.equals(o2);
}
public Integer added(Integer o, int mIdx, int retIdx) {
return o;
}
public Integer removed(Integer o, int oIdx,
int oIdxAdj, int retIdx) {
return o;
}
public Integer set(Integer o1, int idx1, int oIdxAdj,
Integer o2, int idx2, int retIdx) {
if (o1.intValue() % 5 == 2)
return null;
return o1;
}
});
System.out.println("Original array=" + toString(start));
System.out.println("Modifier array=" + toString(modifier));
System.out.println("Adjusted array=" + toString(result));
} else
throw new IllegalArgumentException("Unrecognized test: " + test);
}
}