package processing.data; import java.util.Arrays; import java.util.Iterator; import java.util.Random; import processing.core.PApplet; // splice, slice, subset, concat, reverse // trim, join for String versions /** * Helper class for a list of ints. Lists are designed to have some of the * features of ArrayLists, but to maintain the simplicity and efficiency of * working with arrays. * * Functions like sort() and shuffle() always act on the list itself. To get * a sorted copy, use list.copy().sort(). * * @webref data:composite * @see FloatList * @see StringList */ public class IntList implements Iterable<Integer> { protected int count; protected int[] data; public IntList() { data = new int[10]; } /** * @nowebref */ public IntList(int length) { data = new int[length]; } /** * @nowebref */ public IntList(int[] source) { count = source.length; data = new int[count]; System.arraycopy(source, 0, data, 0, count); } /** * Construct an IntList from an iterable pile of objects. * For instance, a float array, an array of strings, who knows). * Un-parseable or null values will be set to 0. * @nowebref */ public IntList(Iterable<Object> iter) { this(10); for (Object o : iter) { if (o == null) { append(0); // missing value default } else if (o instanceof Number) { append(((Number) o).intValue()); } else { append(PApplet.parseInt(o.toString().trim())); } } crop(); } /** * Construct an IntList from a random pile of objects. * Un-parseable or null values will be set to zero. */ public IntList(Object... items) { final int missingValue = 0; // nuts, can't be last/final/second arg count = items.length; data = new int[count]; int index = 0; for (Object o : items) { int value = missingValue; if (o != null) { if (o instanceof Number) { value = ((Number) o).intValue(); } else { value = PApplet.parseInt(o.toString().trim(), missingValue); } } data[index++] = value; } } static public IntList fromRange(int stop) { return fromRange(0, stop); } static public IntList fromRange(int start, int stop) { int count = stop - start; IntList newbie = new IntList(count); for (int i = 0; i < count; i++) { newbie.set(i, start+i); } return newbie; } /** * Improve efficiency by removing allocated but unused entries from the * internal array used to store the data. Set to private, though it could * be useful to have this public if lists are frequently making drastic * size changes (from very large to very small). */ private void crop() { if (count != data.length) { data = PApplet.subset(data, 0, count); } } /** * Get the length of the list. * * @webref intlist:method * @brief Get the length of the list */ public int size() { return count; } public void resize(int length) { if (length > data.length) { int[] temp = new int[length]; System.arraycopy(data, 0, temp, 0, count); data = temp; } else if (length > count) { Arrays.fill(data, count, length, 0); } count = length; } /** * Remove all entries from the list. * * @webref intlist:method * @brief Remove all entries from the list */ public void clear() { count = 0; } /** * Get an entry at a particular index. * * @webref intlist:method * @brief Get an entry at a particular index */ public int get(int index) { if (index >= this.count) { throw new ArrayIndexOutOfBoundsException(index); } return data[index]; } /** * Set the entry at a particular index. If the index is past the length of * the list, it'll expand the list to accommodate, and fill the intermediate * entries with 0s. * * @webref intlist:method * @brief Set the entry at a particular index */ public void set(int index, int what) { if (index >= count) { data = PApplet.expand(data, index+1); for (int i = count; i < index; i++) { data[i] = 0; } count = index+1; } data[index] = what; } /** Just an alias for append(), but matches pop() */ public void push(int value) { append(value); } public int pop() { if (count == 0) { throw new RuntimeException("Can't call pop() on an empty list"); } int value = get(count-1); count--; return value; } /** * Remove an element from the specified index * * @webref intlist:method * @brief Remove an element from the specified index */ public int remove(int index) { if (index < 0 || index >= count) { throw new ArrayIndexOutOfBoundsException(index); } int entry = data[index]; // int[] outgoing = new int[count - 1]; // System.arraycopy(data, 0, outgoing, 0, index); // count--; // System.arraycopy(data, index + 1, outgoing, 0, count - index); // data = outgoing; // For most cases, this actually appears to be faster // than arraycopy() on an array copying into itself. for (int i = index; i < count-1; i++) { data[i] = data[i+1]; } count--; return entry; } // Remove the first instance of a particular value, // and return the index at which it was found. public int removeValue(int value) { int index = index(value); if (index != -1) { remove(index); return index; } return -1; } // Remove all instances of a particular value, // and return the number of values found and removed public int removeValues(int value) { int ii = 0; for (int i = 0; i < count; i++) { if (data[i] != value) { data[ii++] = data[i]; } } int removed = count - ii; count = ii; return removed; } /** * Add a new entry to the list. * * @webref intlist:method * @brief Add a new entry to the list */ public void append(int value) { if (count == data.length) { data = PApplet.expand(data); } data[count++] = value; } public void append(int[] values) { for (int v : values) { append(v); } } public void append(IntList list) { for (int v : list.values()) { // will concat the list... append(v); } } /** Add this value, but only if it's not already in the list. */ public void appendUnique(int value) { if (!hasValue(value)) { append(value); } } // public void insert(int index, int value) { // if (index+1 > count) { // if (index+1 < data.length) { // } // } // if (index >= data.length) { // data = PApplet.expand(data, index+1); // data[index] = value; // count = index+1; // // } else if (count == data.length) { // if (index >= count) { // //int[] temp = new int[count << 1]; // System.arraycopy(data, 0, temp, 0, index); // temp[index] = value; // System.arraycopy(data, index, temp, index+1, count - index); // data = temp; // // } else { // // data[] has room to grow // // for() loop believed to be faster than System.arraycopy over itself // for (int i = count; i > index; --i) { // data[i] = data[i-1]; // } // data[index] = value; // count++; // } // } public void insert(int index, int value) { insert(index, new int[] { value }); } // same as splice public void insert(int index, int[] values) { if (index < 0) { throw new IllegalArgumentException("insert() index cannot be negative: it was " + index); } if (index >= data.length) { throw new IllegalArgumentException("insert() index " + index + " is past the end of this list"); } int[] temp = new int[count + values.length]; // Copy the old values, but not more than already exist System.arraycopy(data, 0, temp, 0, Math.min(count, index)); // Copy the new values into the proper place System.arraycopy(values, 0, temp, index, values.length); // if (index < count) { // The index was inside count, so it's a true splice/insert System.arraycopy(data, index, temp, index+values.length, count - index); count = count + values.length; // } else { // // The index was past 'count', so the new count is weirder // count = index + values.length; // } data = temp; } public void insert(int index, IntList list) { insert(index, list.values()); } // below are aborted attempts at more optimized versions of the code // that are harder to read and debug... // if (index + values.length >= count) { // // We're past the current 'count', check to see if we're still allocated // // index 9, data.length = 10, values.length = 1 // if (index + values.length < data.length) { // // There's still room for these entries, even though it's past 'count'. // // First clear out the entries leading up to it, however. // for (int i = count; i < index; i++) { // data[i] = 0; // } // data[index] = // } // if (index >= data.length) { // int length = index + values.length; // int[] temp = new int[length]; // System.arraycopy(data, 0, temp, 0, count); // System.arraycopy(values, 0, temp, index, values.length); // data = temp; // count = data.length; // } else { // // } // // } else if (count == data.length) { // int[] temp = new int[count << 1]; // System.arraycopy(data, 0, temp, 0, index); // temp[index] = value; // System.arraycopy(data, index, temp, index+1, count - index); // data = temp; // // } else { // // data[] has room to grow // // for() loop believed to be faster than System.arraycopy over itself // for (int i = count; i > index; --i) { // data[i] = data[i-1]; // } // data[index] = value; // count++; // } /** Return the first index of a particular value. */ public int index(int what) { /* if (indexCache != null) { try { return indexCache.get(what); } catch (Exception e) { // not there return -1; } } */ for (int i = 0; i < count; i++) { if (data[i] == what) { return i; } } return -1; } // !!! TODO this is not yet correct, because it's not being reset when // the rest of the entries are changed // protected void cacheIndices() { // indexCache = new HashMap<Integer, Integer>(); // for (int i = 0; i < count; i++) { // indexCache.put(data[i], i); // } // } /** * @webref intlist:method * @brief Check if a number is a part of the list */ public boolean hasValue(int value) { // if (indexCache == null) { // cacheIndices(); // } // return index(what) != -1; for (int i = 0; i < count; i++) { if (data[i] == value) { return true; } } return false; } /** * @webref intlist:method * @brief Add one to a value */ public void increment(int index) { if (count <= index) { resize(index + 1); } data[index]++; } private void boundsProblem(int index, String method) { final String msg = String.format("The list size is %d. " + "You cannot %s() to element %d.", count, method, index); throw new ArrayIndexOutOfBoundsException(msg); } /** * @webref intlist:method * @brief Add to a value */ public void add(int index, int amount) { if (index < count) { data[index] += amount; } else { boundsProblem(index, "add"); } } /** * @webref intlist:method * @brief Subtract from a value */ public void sub(int index, int amount) { if (index < count) { data[index] -= amount; } else { boundsProblem(index, "sub"); } } /** * @webref intlist:method * @brief Multiply a value */ public void mult(int index, int amount) { if (index < count) { data[index] *= amount; } else { boundsProblem(index, "mult"); } } /** * @webref intlist:method * @brief Divide a value */ public void div(int index, int amount) { if (index < count) { data[index] /= amount; } else { boundsProblem(index, "div"); } } private void checkMinMax(String functionName) { if (count == 0) { String msg = String.format("Cannot use %s() on an empty %s.", functionName, getClass().getSimpleName()); throw new RuntimeException(msg); } } /** * @webref intlist:method * @brief Return the smallest value */ public int min() { checkMinMax("min"); int outgoing = data[0]; for (int i = 1; i < count; i++) { if (data[i] < outgoing) outgoing = data[i]; } return outgoing; } // returns the index of the minimum value. // if there are ties, it returns the first one found. public int minIndex() { checkMinMax("minIndex"); int value = data[0]; int index = 0; for (int i = 1; i < count; i++) { if (data[i] < value) { value = data[i]; index = i; } } return index; } /** * @webref intlist:method * @brief Return the largest value */ public int max() { checkMinMax("max"); int outgoing = data[0]; for (int i = 1; i < count; i++) { if (data[i] > outgoing) outgoing = data[i]; } return outgoing; } // returns the index of the maximum value. // if there are ties, it returns the first one found. public int maxIndex() { checkMinMax("maxIndex"); int value = data[0]; int index = 0; for (int i = 1; i < count; i++) { if (data[i] > value) { value = data[i]; index = i; } } return index; } public int sum() { long amount = sumLong(); if (amount > Integer.MAX_VALUE) { throw new RuntimeException("sum() exceeds " + Integer.MAX_VALUE + ", use sumLong()"); } if (amount < Integer.MIN_VALUE) { throw new RuntimeException("sum() less than " + Integer.MIN_VALUE + ", use sumLong()"); } return (int) amount; } public long sumLong() { long sum = 0; for (int i = 0; i < count; i++) { sum += data[i]; } return sum; } /** * Sorts the array in place. * * @webref intlist:method * @brief Sorts the array, lowest to highest */ public void sort() { Arrays.sort(data, 0, count); } /** * Reverse sort, orders values from highest to lowest. * * @webref intlist:method * @brief Reverse sort, orders values from highest to lowest */ public void sortReverse() { new Sort() { @Override public int size() { return count; } @Override public float compare(int a, int b) { return data[b] - data[a]; } @Override public void swap(int a, int b) { int temp = data[a]; data[a] = data[b]; data[b] = temp; } }.run(); } // use insert() // public void splice(int index, int value) { // } // public void subset(int start) { // subset(start, count - start); // } // // // public void subset(int start, int num) { // for (int i = 0; i < num; i++) { // data[i] = data[i+start]; // } // count = num; // } /** * @webref intlist:method * @brief Reverse the order of the list elements */ public void reverse() { int ii = count - 1; for (int i = 0; i < count/2; i++) { int t = data[i]; data[i] = data[ii]; data[ii] = t; --ii; } } /** * Randomize the order of the list elements. Note that this does not * obey the randomSeed() function in PApplet. * * @webref intlist:method * @brief Randomize the order of the list elements */ public void shuffle() { Random r = new Random(); int num = count; while (num > 1) { int value = r.nextInt(num); num--; int temp = data[num]; data[num] = data[value]; data[value] = temp; } } /** * Randomize the list order using the random() function from the specified * sketch, allowing shuffle() to use its current randomSeed() setting. */ public void shuffle(PApplet sketch) { int num = count; while (num > 1) { int value = (int) sketch.random(num); num--; int temp = data[num]; data[num] = data[value]; data[value] = temp; } } public IntList copy() { IntList outgoing = new IntList(data); outgoing.count = count; return outgoing; } /** * Returns the actual array being used to store the data. For advanced users, * this is the fastest way to access a large list. Suitable for iterating * with a for() loop, but modifying the list will have terrible consequences. */ public int[] values() { crop(); return data; } @Override public Iterator<Integer> iterator() { // public Iterator<Integer> valueIterator() { return new Iterator<Integer>() { int index = -1; public void remove() { IntList.this.remove(index); index--; } public Integer next() { return data[++index]; } public boolean hasNext() { return index+1 < count; } }; } /** * Create a new array with a copy of all the values. * * @return an array sized by the length of the list with each of the values. * @webref intlist:method * @brief Create a new array with a copy of all the values */ public int[] array() { return array(null); } /** * Copy values into the specified array. If the specified array is null or * not the same size, a new array will be allocated. * @param array */ public int[] array(int[] array) { if (array == null || array.length != count) { array = new int[count]; } System.arraycopy(data, 0, array, 0, count); return array; } // public int[] toIntArray() { // int[] outgoing = new int[count]; // for (int i = 0; i < count; i++) { // outgoing[i] = (int) data[i]; // } // return outgoing; // } // public long[] toLongArray() { // long[] outgoing = new long[count]; // for (int i = 0; i < count; i++) { // outgoing[i] = (long) data[i]; // } // return outgoing; // } // public float[] toFloatArray() { // float[] outgoing = new float[count]; // System.arraycopy(data, 0, outgoing, 0, count); // return outgoing; // } // public double[] toDoubleArray() { // double[] outgoing = new double[count]; // for (int i = 0; i < count; i++) { // outgoing[i] = data[i]; // } // return outgoing; // } // public String[] toStringArray() { // String[] outgoing = new String[count]; // for (int i = 0; i < count; i++) { // outgoing[i] = String.valueOf(data[i]); // } // return outgoing; // } /** * Returns a normalized version of this array. Called getPercent() for * consistency with the Dict classes. It's a getter method because it needs * to returns a new list (because IntList/Dict can't do percentages or * normalization in place on int values). */ public FloatList getPercent() { double sum = 0; for (float value : array()) { sum += value; } FloatList outgoing = new FloatList(count); for (int i = 0; i < count; i++) { double percent = data[i] / sum; outgoing.set(i, (float) percent); } return outgoing; } // /** // * Count the number of times each entry is found in this list. // * Converts each entry to a String so it can be used as a key. // */ // public IntDict getTally() { // IntDict outgoing = new IntDict(); // for (int i = 0; i < count; i++) { // outgoing.increment(String.valueOf(data[i])); // } // return outgoing; // } public IntList getSubset(int start) { return getSubset(start, count - start); } public IntList getSubset(int start, int num) { int[] subset = new int[num]; System.arraycopy(data, start, subset, 0, num); return new IntList(subset); } public String join(String separator) { if (count == 0) { return ""; } StringBuilder sb = new StringBuilder(); sb.append(data[0]); for (int i = 1; i < count; i++) { sb.append(separator); sb.append(data[i]); } return sb.toString(); } public void print() { for (int i = 0; i < count; i++) { System.out.format("[%d] %d%n", i, data[i]); } } /** * Return this dictionary as a String in JSON format. */ public String toJSON() { return "[ " + join(", ") + " ]"; } @Override public String toString() { return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); } }