diff --git a/core/src/processing/data/FloatHash.java b/core/src/processing/data/FloatHash.java index 3dc428691..644ced16c 100644 --- a/core/src/processing/data/FloatHash.java +++ b/core/src/processing/data/FloatHash.java @@ -464,15 +464,15 @@ public class FloatHash { public float compare(int a, int b) { float diff = 0; if (useKeys) { - diff = values[a] - values[b]; - if (diff == 0) { - diff = keys[a].compareToIgnoreCase(keys[b]); - } - } else { // sort values diff = keys[a].compareToIgnoreCase(keys[b]); if (diff == 0) { return values[a] - values[b]; } + } else { // sort values + diff = values[a] - values[b]; + if (diff == 0) { + diff = keys[a].compareToIgnoreCase(keys[b]); + } } return reverse ? diff : -diff; } diff --git a/core/src/processing/data/IntHash.java b/core/src/processing/data/IntHash.java index ea14624cd..6c098fff8 100644 --- a/core/src/processing/data/IntHash.java +++ b/core/src/processing/data/IntHash.java @@ -359,15 +359,15 @@ public class IntHash { public float compare(int a, int b) { int diff = 0; if (useKeys) { - diff = values[a] - values[b]; - if (diff == 0) { - diff = keys[a].compareToIgnoreCase(keys[b]); - } - } else { // sort values diff = keys[a].compareToIgnoreCase(keys[b]); if (diff == 0) { return values[a] - values[b]; } + } else { // sort values + diff = values[a] - values[b]; + if (diff == 0) { + diff = keys[a].compareToIgnoreCase(keys[b]); + } } return reverse ? diff : -diff; } diff --git a/core/src/processing/data/StringHash.java b/core/src/processing/data/StringHash.java index 5a54249a7..630105bdd 100644 --- a/core/src/processing/data/StringHash.java +++ b/core/src/processing/data/StringHash.java @@ -2,6 +2,7 @@ package processing.data; import java.io.*; import java.util.HashMap; +import java.util.Iterator; import processing.core.PApplet; @@ -12,18 +13,9 @@ import processing.core.PApplet; public class StringHash { /** Number of elements in the table */ - public int count; + protected int count; - /** - * List of keys, available for sake of speed, - * but should be manipulated (consider it read-only). - */ protected String[] keys; - - /** - * List of values, available for sake of speed, - * but should be manipulated (consider it read-only). - */ protected String[] values; /** Internal implementation for faster lookups */ @@ -37,6 +29,11 @@ public class StringHash { } + /** + * Create a new lookup pre-allocated to a specific length. This will not + * change the size(), but is more efficient than not specifying a length. + * Use it when you know the rough size of the thing you're creating. + */ public StringHash(int length) { count = 0; keys = new String[length]; @@ -44,43 +41,27 @@ public class StringHash { } - public StringHash(PApplet parent, String filename) { - String[] lines = parent.loadStrings(filename); + /** + * Read a set of entries from a Reader that has each key/value pair on + * a single line, separated by a tab. + */ + public StringHash(BufferedReader reader) { + String[] lines = PApplet.loadStrings(reader); keys = new String[lines.length]; values = new String[lines.length]; -// boolean csv = (lines[0].indexOf('\t') == -1); - for (int i = 0; i < lines.length; i++) { -// String[] pieces = csv ? Table.splitLineCSV(lines[i]) : PApplet.split(lines[i], '\t'); String[] pieces = PApplet.split(lines[i], '\t'); if (pieces.length == 2) { -// keys[count] = pieces[0]; -// values[count] = pieces[1]; -// count++; - create(pieces[0], pieces[1]); + keys[count] = pieces[0]; + values[count] = pieces[1]; + count++; } } } - public void write(PApplet parent, String filename) { - PrintWriter writer = parent.createWriter(filename); - boolean csv = - (filename.toLowerCase().endsWith(".csv") || - filename.toLowerCase().endsWith(".csv.gz")); - for (int i = 0; i < count; i++) { - if (csv) { -// String k = key(i); -// if (k.indexOf(")) - } else { - writer.println(key(i) + "\t" + value(i)); - } - } - } - - - public int getCount() { + public int size() { return count; } @@ -90,7 +71,7 @@ public class StringHash { } - public void crop() { + protected void crop() { if (count != keys.length) { keys = PApplet.subset(keys, 0, count); values = PApplet.subset(values, 0, count); @@ -98,13 +79,38 @@ public class StringHash { } - /** - * Return the internal array being used to store the keys. Allocated but - * unused entries will be removed. This array should not be modified. - */ - public String[] keys() { - crop(); - return keys; +// /** +// * Return the internal array being used to store the keys. Allocated but +// * unused entries will be removed. This array should not be modified. +// */ +// public String[] keys() { +// crop(); +// return keys; +// } + + + public Iterable keys() { + return new Iterable() { + + @Override + public Iterator iterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + } + + public String next() { + return key(++index); + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + }; } @@ -112,7 +118,14 @@ public class StringHash { * Return a copy of the internal keys array. This array can be modified. */ public String[] keyArray() { - String[] outgoing = new String[count]; + return keyArray(null); + } + + + public String[] keyArray(String[] outgoing) { + if (outgoing == null || outgoing.length != count) { + outgoing = new String[count]; + } System.arraycopy(keys, 0, outgoing, 0, count); return outgoing; } @@ -123,95 +136,100 @@ public class StringHash { } - public String[] values() { - crop(); - return values; + public Iterable values() { + return new Iterable() { + + @Override + public Iterator iterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + } + + public String next() { + return value(++index); + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + }; } - public String[] valueArray() { - String[] outgoing = new String[count]; - System.arraycopy(values, 0, outgoing, 0, count); - return outgoing; + /** + * Create a new array and copy each of the values into it. + */ + public int[] valueArray() { + return valueArray(null); } - public String get(String what) { - int index = keyIndex(what); + /** + * Fill an already-allocated array with the values (more efficient than + * creating a new array each time). If 'array' is null, or not the same + * size as the number of values, a new array will be allocated and returned. + */ + public int[] valueArray(int[] array) { + if (array == null || array.length != size()) { + array = new int[count]; + } + System.arraycopy(values, 0, array, 0, count); + return array; + } + + + /** + * Return a value for the specified key. + */ + public String get(String key) { + int index = index(key); if (index == -1) return null; return values[index]; } - public void set(String key, String val) { - int index = keyIndex(key); + public void set(String key, String amount) { + int index = index(key); if (index == -1) { - create(key, val); + create(key, amount); } else { - values[index] = val; + values[index] = amount; } } - public void append(String key, String val) { - int index = keyIndex(key); - if (index == -1) { - create(key, val); - } else { - values[index] += val; - } - } - - - public int keyIndex(String what) { + public int index(String what) { Integer found = indices.get(what); return (found == null) ? -1 : found.intValue(); } - public int valueIndex(String what) { - for (int i = 0; i < count; i++) { - if (values[i].equals(what)) { - return i; - } - } - return -1; - } - - - protected void create(String k, String v) { + protected void create(String key, String value) { if (count == keys.length) { keys = PApplet.expand(keys); values = PApplet.expand(values); } - indices.put(k, new Integer(count)); - keys[count] = k; - values[count] = v; + indices.put(key, new Integer(count)); + keys[count] = key; + values[count] = value; count++; } - public void print() { - write(new PrintWriter(System.out)); + public void remove(String key) { + removeIndex(index(key)); } - public void write(PrintWriter writer) { - for (int i = 0; i < count; i++) { - writer.println(keys[i] + "\t" + values[i]); - } - writer.flush(); - } - - - public void remove(String which) { - remove(keyIndex(which)); - } - - - public void remove(int which) { - indices.remove(keys[which]); - for (int i = which; i < count-1; i++) { + public void removeIndex(int index) { + //System.out.println("index is " + which + " and " + keys[which]); + indices.remove(keys[index]); + for (int i = index; i < count-1; i++) { keys[i] = keys[i+1]; values[i] = values[i+1]; indices.put(keys[i], i); @@ -222,7 +240,7 @@ public class StringHash { } - public void swap(int a, int b) { + protected void swap(int a, int b) { String tkey = keys[a]; String tvalue = values[a]; keys[a] = keys[b]; @@ -235,28 +253,17 @@ public class StringHash { } + /** + * Sort the keys alphabetically (ignoring case). Uses the value as a + * tie-breaker (only really possible with a key that has a case change). + */ public void sortKeys() { - Sort s = new Sort() { - @Override - public int size() { - return count; - } + sortImpl(true, false); + } - @Override - public float compare(int a, int b) { - int result = keys[a].compareToIgnoreCase(keys[b]); - if (result != 0) { - return result; - } - return values[a].compareToIgnoreCase(values[b]); - } - @Override - public void swap(int a, int b) { - StringHash.this.swap(a, b); - } - }; - s.run(); + public void sortKeysReverse() { + sortImpl(true, true); } @@ -264,18 +271,16 @@ public class StringHash { * Sort by values in descending order (largest value will be at [0]). */ public void sortValues() { - sortValues(true, true); + sortImpl(false, false); } - public void sortValues(final boolean descending) { - sortValues(descending, true); + public void sortValuesReverse() { + sortImpl(false, true); } - // ascending puts the largest value at the end - // descending puts the largest value at 0 - public void sortValues(final boolean descending, final boolean tiebreaker) { + protected void sortImpl(final boolean useKeys, final boolean reverse) { Sort s = new Sort() { @Override public int size() { @@ -284,13 +289,19 @@ public class StringHash { @Override public float compare(int a, int b) { - float diff = values[a].compareToIgnoreCase(values[b]); - if (tiebreaker) { + int diff = 0; + if (useKeys) { + diff = keys[a].compareToIgnoreCase(keys[b]); + if (diff == 0) { + diff = values[a].compareToIgnoreCase(values[b]); + } + } else { // sort values + diff = values[a].compareToIgnoreCase(values[b]); if (diff == 0) { diff = keys[a].compareToIgnoreCase(keys[b]); } } - return descending ? diff : -diff; + return reverse ? diff : -diff; } @Override @@ -300,4 +311,43 @@ public class StringHash { }; s.run(); } + + + /** Returns a duplicate copy of this object. */ + public StringHash copy() { + StringHash outgoing = new StringHash(count); + System.arraycopy(keys, 0, outgoing.keys, 0, count); + System.arraycopy(values, 0, outgoing.values, 0, count); + for (int i = 0; i < count; i++) { + outgoing.indices.put(keys[i], i); + } + return outgoing; + } + + + /** + * Write tab-delimited entries out to + * @param writer + */ + public void write(PrintWriter writer) { + for (int i = 0; i < count; i++) { + writer.println(keys[i] + "\t" + values[i]); + } + writer.flush(); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getName() + " size= " + size() + " { "); + for (int i = 0; i < size(); i++) { + if (i != 0) { + sb.append(", "); + } + sb.append("\"" + keys[i] + "\": " + values[i]); + } + sb.append(" }"); + return sb.toString(); + } }