package edu.stanford.nlp.stats; import java.io.PrintWriter; import java.io.Serializable; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import edu.stanford.nlp.util.ErasureUtils; import edu.stanford.nlp.util.Generics; import edu.stanford.nlp.util.MutableDouble; /** * A class for keeping double counts of {@link List}s of a * prespecified length. A depth <i>n</i> GeneralizedCounter can be * thought of as a conditionalized count over <i>n</i> classes of * objects, in a prespecified order. Also offers a read-only view as * a Counter. <p> This class is serializable but no guarantees are * made about compatibility version to version. * * <p/> * This is the worst class. Use TwoDimensionalCounter. If you need a third, * write ThreeDimensionalCounter, but don't use this. * * @author Roger Levy */ public class GeneralizedCounter<K> implements Serializable { private static final long serialVersionUID = 1; private static final Object[] zeroKey = new Object[0]; private Map<K,Object> map = Generics.newHashMap(); private int depth; private double total; /** * GeneralizedCounter must be constructed with a depth parameter */ private GeneralizedCounter() { } /** * Constructs a new GeneralizedCounter of a specified depth * * @param depth the depth of the GeneralizedCounter */ public GeneralizedCounter(int depth) { this.depth = depth; } /** * Returns the set of entries in the GeneralizedCounter. * Here, each key is a read-only {@link * List} of size equal to the depth of the GeneralizedCounter, and * each value is a {@link Double}. Each entry is a {@link java.util.Map.Entry} object, * but these objects * do not support the {@link java.util.Map.Entry#setValue} method; attempts to call * that method with result * in an {@link UnsupportedOperationException} being thrown. */ public Set<Map.Entry<List<K>,Double>> entrySet() { return ErasureUtils.<Set<Map.Entry<List<K>,Double>>>uncheckedCast(entrySet(new HashSet<>(), zeroKey, true)); } /* this is (non-tail) recursive right now, haven't figured out a way * to speed it up */ private Set<Map.Entry<Object,Double>> entrySet(Set<Map.Entry<Object,Double>> s, Object[] key, boolean useLists) { if (depth == 1) { //System.out.println("key is long enough to add to set"); Set<K> keys = map.keySet(); for (K finalKey: keys) { // array doesn't escape K[] newKey = ErasureUtils.<K>mkTArray(Object.class,key.length + 1); if (key.length > 0) { System.arraycopy(key, 0, newKey, 0, key.length); } newKey[key.length] = finalKey; MutableDouble value = (MutableDouble) map.get(finalKey); Double value1 = new Double(value.doubleValue()); if (useLists) { s.add(new Entry<>(Arrays.asList(newKey), value1)); } else { s.add(new Entry<>(newKey[0], value1)); } } } else { Set<K> keys = map.keySet(); //System.out.println("key length " + key.length); //System.out.println("keyset level " + depth + " " + keys); for (K o: keys) { Object[] newKey = new Object[key.length + 1]; if (key.length > 0) { System.arraycopy(key, 0, newKey, 0, key.length); } newKey[key.length] = o; //System.out.println("level " + key.length + " current key " + Arrays.asList(newKey)); conditionalizeHelper(o).entrySet(s, newKey, true); } } //System.out.println("leaving key length " + key.length); return s; } /** * Returns a set of entries, where each key is a read-only {@link * List} of size one less than the depth of the GeneralizedCounter, and * each value is a {@link ClassicCounter}. Each entry is a {@link java.util.Map.Entry} object, but these objects * do not support the {@link java.util.Map.Entry#setValue} method; attempts to call that method with result * in an {@link UnsupportedOperationException} being thrown. */ public Set<Map.Entry<List<K>, ClassicCounter<K>>> lowestLevelCounterEntrySet() { return ErasureUtils.<Set<Map.Entry<List<K>, ClassicCounter<K>>>>uncheckedCast(lowestLevelCounterEntrySet(new HashSet<>(), zeroKey, true)); } /* this is (non-tail) recursive right now, haven't figured out a way * to speed it up */ private Set<Map.Entry<Object, ClassicCounter<K>>> lowestLevelCounterEntrySet(Set<Map.Entry<Object, ClassicCounter<K>>> s, Object[] key, boolean useLists) { Set<K> keys = map.keySet(); if (depth == 2) { // add these counters to set for (K finalKey: keys) { K[] newKey = ErasureUtils.<K>mkTArray(Object.class,key.length + 1); if (key.length > 0) { System.arraycopy(key, 0, newKey, 0, key.length); } newKey[key.length] = finalKey; ClassicCounter<K> c = conditionalizeHelper(finalKey).oneDimensionalCounterView(); if (useLists) { s.add(new Entry<>(Arrays.asList(newKey), c)); } else { s.add(new Entry<>(newKey[0], c)); } } } else { //System.out.println("key length " + key.length); //System.out.println("keyset level " + depth + " " + keys); for (K o: keys) { Object[] newKey = new Object[key.length + 1]; if (key.length > 0) { System.arraycopy(key, 0, newKey, 0, key.length); } newKey[key.length] = o; //System.out.println("level " + key.length + " current key " + Arrays.asList(newKey)); conditionalizeHelper(o).lowestLevelCounterEntrySet(s, newKey, true); } } //System.out.println("leaving key length " + key.length); return s; } private static class Entry<K, V> implements Map.Entry<K, V> { private K key; private V value; Entry(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { throw new UnsupportedOperationException(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Entry)) { return false; } Entry<K,V> e = ErasureUtils.<Entry<K,V>>uncheckedCast(o); Object key1 = e.getKey(); if (!(key != null && key.equals(key1))) { return false; } Object value1 = e.getValue(); if (!(value != null && value.equals(value1))) { return false; } return true; } @Override public int hashCode() { if (key == null || value == null) { return 0; } return key.hashCode() ^ value.hashCode(); } @Override public String toString() { return key.toString() + "=" + value.toString(); } } // end static class Entry /** * returns the total count of objects in the GeneralizedCounter. */ public double totalCount() { if (depth() == 1) { return total; // I think this one is always OK. Not very principled here, though. } else { double result = 0.0; for (K o: topLevelKeySet()) { result += conditionalizeOnce(o).totalCount(); } return result; } } /** * Returns the set of elements that occur in the 0th position of a * {@link List} key in the GeneralizedCounter. * * @see #conditionalize(List) * @see #getCount */ public Set<K> topLevelKeySet() { return map.keySet(); } /** * Returns the set of keys, as read-only {@link List}s of size * equal to the depth of the GeneralizedCounter. */ public Set<List<K>> keySet() { return ErasureUtils.<Set<List<K>>>uncheckedCast(keySet(Generics.newHashSet(), zeroKey, true)); } /* this is (non-tail) recursive right now, haven't figured out a way * to speed it up */ private Set<Object> keySet(Set<Object> s, Object[] key, boolean useList) { if (depth == 1) { //System.out.println("key is long enough to add to set"); Set<K> keys = map.keySet(); for (Object oldKey: keys) { Object[] newKey = new Object[key.length + 1]; if (key.length > 0) { System.arraycopy(key, 0, newKey, 0, key.length); } newKey[key.length] = oldKey; if (useList) { s.add(Arrays.asList(newKey)); } else { s.add(newKey[0]); } } } else { Set<K> keys = map.keySet(); //System.out.println("key length " + key.length); //System.out.println("keyset level " + depth + " " + keys); for (K o: keys) { Object[] newKey = new Object[key.length + 1]; if (key.length > 0) { System.arraycopy(key, 0, newKey, 0, key.length); } newKey[key.length] = o; //System.out.println("level " + key.length + " current key " + Arrays.asList(newKey)); conditionalizeHelper(o).keySet(s, newKey, true); } } //System.out.println("leaving key length " + key.length); return s; } /** * Returns the depth of the GeneralizedCounter (i.e., the dimension * of the distribution). */ public int depth() { return depth; } /** * Returns true if nothing has a count. */ public boolean isEmpty() { return map.isEmpty(); } /** * Equivalent to <code>{@link #getCounts}({o})</code>; works only * for depth 1 GeneralizedCounters */ public double getCount(Object o) { if (depth > 1) { wrongDepth(); } Number count = (Number) map.get(o); if (count != null) { return count.doubleValue(); } else { return 0.0; } } /** * A convenience method equivalent to <code>{@link * #getCounts}({o1,o2})</code>; works only for depth 2 * GeneralizedCounters */ public double getCount(K o1, K o2) { if (depth != 2) { wrongDepth(); } GeneralizedCounter<K> gc1 = ErasureUtils.<GeneralizedCounter<K>>uncheckedCast(map.get(o1)); if (gc1 == null) { return 0.0; } else { return gc1.getCount(o2); } } /** * A convenience method equivalent to <code>{@link * #getCounts}({o1,o2,o3})</code>; works only for depth 3 * GeneralizedCounters */ public double getCount(K o1, K o2, K o3) { if (depth != 3) { wrongDepth(); } GeneralizedCounter<K> gc1 = ErasureUtils.<GeneralizedCounter<K>>uncheckedCast(map.get(o1)); if (gc1 == null) { return 0.0; } else { return gc1.getCount(o2, o3); } } /** * returns a {@code double[]} array of length * {@code depth+1}, containing the conditional counts on a * {@code depth}-length list given each level of conditional * distribution from 0 to {@code depth}. */ public double[] getCounts(List<K> l) { if (l.size() != depth) { wrongDepth(); //throws exception } double[] counts = new double[depth + 1]; GeneralizedCounter<K> next = this; counts[0] = next.totalCount(); Iterator<K> i = l.iterator(); int j = 1; K o = i.next(); while (i.hasNext()) { next = next.conditionalizeHelper(o); counts[j] = next.totalCount(); o = i.next(); j++; } counts[depth] = next.getCount(o); return counts; } /* haven't decided about access for this one yet */ private GeneralizedCounter<K> conditionalizeHelper(K o) { if (depth > 1) { GeneralizedCounter<K> next = ErasureUtils.<GeneralizedCounter<K>>uncheckedCast(map.get(o)); if (next == null) // adds a new GeneralizedCounter if needed { map.put(o, (next = new GeneralizedCounter<>(depth - 1))); } return next; } else { throw new RuntimeException("Error -- can't conditionalize a distribution of depth 1"); } } /** * returns a GeneralizedCounter conditioned on the objects in the * {@link List} argument. The length of the argument {@link List} * must be less than the depth of the GeneralizedCounter. */ public GeneralizedCounter<K> conditionalize(List<K> l) { int n = l.size(); if (n >= depth()) { throw new RuntimeException("Error -- attempted to conditionalize a GeneralizedCounter of depth " + depth() + " on a vector of length " + n); } else { GeneralizedCounter<K> next = this; for (K o: l) { next = next.conditionalizeHelper(o); } return next; } } /** * Returns a GeneralizedCounter conditioned on the given top level object. * This is just shorthand (and more efficient) for <code>conditionalize(new Object[] { o })</code>. */ public GeneralizedCounter<K> conditionalizeOnce(K o) { if (depth() < 1) { throw new RuntimeException("Error -- attempted to conditionalize a GeneralizedCounter of depth " + depth()); } else { return conditionalizeHelper(o); } } /** * equivalent to incrementCount(l,o,1.0). */ public void incrementCount(List<K> l, K o) { incrementCount(l, o, 1.0); } /** * same as incrementCount(List, double) but as if Object o were at the end of the list */ public void incrementCount(List<K> l, K o, double count) { if (l.size() != depth - 1) { wrongDepth(); } GeneralizedCounter<K> next = this; for (K o2: l) { next.addToTotal(count); next = next.conditionalizeHelper(o2); } next.addToTotal(count); next.incrementCount1D(o, count); } /** * Equivalent to incrementCount(l, 1.0). */ public void incrementCount(List<K> l) { incrementCount(l, 1.0); } /** * adds to count for the {@link #depth()}-dimensional key <code>l</code>. */ public void incrementCount(List<K> l, double count) { if (l.size() != depth) { wrongDepth(); //throws exception } GeneralizedCounter<K> next = this; Iterator<K> i = l.iterator(); K o = i.next(); while (i.hasNext()) { next.addToTotal(count); next = next.conditionalizeHelper(o); o = i.next(); } next.incrementCount1D(o, count); } /** * Equivalent to incrementCount2D(first,second,1.0). */ public void incrementCount2D(K first, K second) { incrementCount2D(first, second, 1.0); } /** * Equivalent to incrementCount( new Object[] { first, second }, count ). * Makes the special case easier, and also more efficient. */ public void incrementCount2D(K first, K second, double count) { if (depth != 2) { wrongDepth(); //throws exception } this.addToTotal(count); GeneralizedCounter<K> next = this.conditionalizeHelper(first); next.incrementCount1D(second, count); } /** * Equivalent to incrementCount3D(first,second,1.0). */ public void incrementCount3D(K first, K second, K third) { incrementCount3D(first, second, third, 1.0); } /** * Equivalent to incrementCount( new Object[] { first, second, third }, count ). * Makes the special case easier, and also more efficient. */ public void incrementCount3D(K first, K second, K third, double count) { if (depth != 3) { wrongDepth(); //throws exception } this.addToTotal(count); GeneralizedCounter<K> next = this.conditionalizeHelper(first); next.incrementCount2D(second, third, count); } private void addToTotal(double d) { total += d; } // for more efficient memory usage private transient MutableDouble tempMDouble = null; /** * Equivalent to incrementCount1D(o, 1.0). */ public void incrementCount1D(K o) { incrementCount1D(o, 1.0); } /** * Equivalent to <code>{@link #incrementCount}({o}, count)</code>; * only works for a depth 1 GeneralizedCounter. */ public void incrementCount1D(K o, double count) { if (depth > 1) { wrongDepth(); } addToTotal(count); if (tempMDouble == null) { tempMDouble = new MutableDouble(); } tempMDouble.set(count); MutableDouble oldMDouble = (MutableDouble) map.put(o, tempMDouble); if (oldMDouble != null) { tempMDouble.set(count + oldMDouble.doubleValue()); } tempMDouble = oldMDouble; } /** * Like {@link ClassicCounter}, this currently returns true if the count is * explicitly 0.0 for something */ public boolean containsKey(List<K> key) { // if(! (key instanceof Object[])) // return false; // Object[] o = (Object[]) key; GeneralizedCounter<K> next = this; for (int i=0; i<key.size()-1; i++) { next = next.conditionalizeHelper(key.get(i)); if (next==null) return false; } return next.map.containsKey(key.get(key.size()-1)); } public GeneralizedCounter<K> reverseKeys() { GeneralizedCounter<K> result = new GeneralizedCounter<>(); Set<Map.Entry<List<K>,Double>> entries = entrySet(); for (Map.Entry<List<K>,Double> entry: entries) { List<K> list = entry.getKey(); double count = entry.getValue(); Collections.reverse(list); result.incrementCount(list, count); } return result; } private void wrongDepth() { throw new RuntimeException("Error -- attempt to operate with key of wrong length. depth=" + depth); } /** * Returns a read-only synchronous view (not a snapshot) of * <code>this</code> as a {@link ClassicCounter}. Any calls to * count-changing or entry-removing operations will result in an * {@link UnsupportedOperationException}. At some point in the * future, this view may gain limited writable functionality. */ public ClassicCounter<List<K>> counterView() { return new CounterView(); } private class CounterView extends ClassicCounter<List<K>> { /** * */ private static final long serialVersionUID = -1241712543674668918L; @Override public double incrementCount(List<K> o, double count) { throw new UnsupportedOperationException(); } @Override public void setCount(List<K> o, double count) { throw new UnsupportedOperationException(); } @Override public double totalCount() { return GeneralizedCounter.this.totalCount(); } @Override public double getCount(Object o) { List<K> l = (List<K>)o; if (l.size() != depth) { return 0.0; } else { return GeneralizedCounter.this.getCounts(l)[depth]; } } @Override public int size() { return GeneralizedCounter.this.map.size(); } @Override public Set<List<K>> keySet() { return GeneralizedCounter.this.keySet(); } @Override public double remove(List<K> o) { throw new UnsupportedOperationException(); } @Override public boolean containsKey(List<K> key) { return GeneralizedCounter.this.containsKey(key); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public boolean isEmpty() { return GeneralizedCounter.this.isEmpty(); } @Override public Set<Map.Entry<List<K>, Double>> entrySet() { return GeneralizedCounter.this.entrySet(); } @Override public boolean equals(Object o) { if (o == this) { return true; } //return false; if (!(o instanceof ClassicCounter)) { return false; } else { // System.out.println("it's a counter!"); // Set e = entrySet(); // Set e1 = ((Counter) o).entrySet(); // System.out.println(e + "\n" + e1); return entrySet().equals(((ClassicCounter<?>) o).entrySet()); } } @Override public int hashCode() { int total = 17; for (Object o: entrySet()) { total = 37 * total + o.hashCode(); } return total; } @Override public String toString() { StringBuffer sb = new StringBuffer("{"); for (Iterator<Map.Entry<List<K>, Double>> i = entrySet().iterator(); i.hasNext();) { Map.Entry<List<K>, Double> e = i.next(); sb.append(e.toString()); if (i.hasNext()) { sb.append(","); } } sb.append("}"); return sb.toString(); } } // end class CounterView /** * Returns a read-only synchronous view (not a snapshot) of * <code>this</code> as a {@link ClassicCounter}. Works only with one-dimensional * GeneralizedCounters. Exactly like {@link #counterView}, except * that {@link #getCount} operates on primitive objects of the counter instead * of singleton lists. Any calls to * count-changing or entry-removing operations will result in an * {@link UnsupportedOperationException}. At some point in the * future, this view may gain limited writable functionality. */ public ClassicCounter<K> oneDimensionalCounterView() { if (depth != 1) { throw new UnsupportedOperationException(); } return new OneDimensionalCounterView(); } private class OneDimensionalCounterView extends ClassicCounter<K> { /** * */ private static final long serialVersionUID = 5628505169749516972L; @Override public double incrementCount(K o, double count) { throw new UnsupportedOperationException(); } @Override public void setCount(K o, double count) { throw new UnsupportedOperationException(); } @Override public double totalCount() { return GeneralizedCounter.this.totalCount(); } @Override public double getCount(Object o) { return GeneralizedCounter.this.getCount(o); } @Override public int size() { return GeneralizedCounter.this.map.size(); } @Override public Set<K> keySet() { return ErasureUtils.<Set<K>>uncheckedCast(GeneralizedCounter.this.keySet(Generics.newHashSet(), zeroKey, false)); } @Override public double remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsKey(Object key) { return GeneralizedCounter.this.map.containsKey(key); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public boolean isEmpty() { return GeneralizedCounter.this.isEmpty(); } @Override public Set<Map.Entry<K, Double>> entrySet() { return ErasureUtils.<Set<Map.Entry<K, Double>>>uncheckedCast(GeneralizedCounter.this.entrySet(new HashSet<>(), zeroKey, false)); } @Override public boolean equals(Object o) { if (o == this) { return true; } //return false; if (!(o instanceof ClassicCounter)) { return false; } else { // System.out.println("it's a counter!"); // Set e = entrySet(); // Set e1 = ((Counter) o).map.entrySet(); // System.out.println(e + "\n" + e1); return entrySet().equals(((ClassicCounter<?>) o).entrySet()); } } @Override public int hashCode() { int total = 17; for (Object o: entrySet()) { total = 37 * total + o.hashCode(); } return total; } @Override public String toString() { StringBuilder sb = new StringBuilder("{"); for (Iterator<Map.Entry<K, Double>> i = entrySet().iterator(); i.hasNext();) { Map.Entry<K, Double> e = i.next(); sb.append(e.toString()); if (i.hasNext()) { sb.append(","); } } sb.append("}"); return sb.toString(); } } // end class OneDimensionalCounterView @Override public String toString() { return map.toString(); } public String toString(String param) { switch (param) { case "contingency": { StringBuilder sb = new StringBuilder(); for (K obj : ErasureUtils.sortedIfPossible(topLevelKeySet())) { sb.append(obj); sb.append(" = "); GeneralizedCounter<K> gc = conditionalizeOnce(obj); sb.append(gc); sb.append("\n"); } return sb.toString(); } case "sorted": { StringBuilder sb = new StringBuilder(); sb.append("{\n"); for (K obj : ErasureUtils.sortedIfPossible(topLevelKeySet())) { sb.append(obj); sb.append(" = "); GeneralizedCounter<K> gc = conditionalizeOnce(obj); sb.append(gc); sb.append("\n"); } sb.append("}\n"); return sb.toString(); } default: return toString(); } } /** * for testing purposes only */ public static void main(String[] args) { Object[] a1 = new Object[]{"a", "b"}; Object[] a2 = new Object[]{"a", "b"}; System.out.println(Arrays.equals(a1, a2)); GeneralizedCounter<String> gc = new GeneralizedCounter<>(3); gc.incrementCount(Arrays.asList(new String[]{"a", "j", "x"}), 3.0); gc.incrementCount(Arrays.asList(new String[]{"a", "l", "x"}), 3.0); gc.incrementCount(Arrays.asList(new String[]{"b", "k", "y"}), 3.0); gc.incrementCount(Arrays.asList(new String[]{"b", "k", "z"}), 3.0); System.out.println("incremented counts."); System.out.println(gc.dumpKeys()); System.out.println("string representation of generalized counter:"); System.out.println(gc.toString()); gc.printKeySet(); System.out.println("entry set:\n" + gc.entrySet()); arrayPrintDouble(gc.getCounts(Arrays.asList(new String[]{"a", "j", "x"}))); arrayPrintDouble(gc.getCounts(Arrays.asList(new String[]{"a", "j", "z"}))); arrayPrintDouble(gc.getCounts(Arrays.asList(new String[]{"b", "k", "w"}))); arrayPrintDouble(gc.getCounts(Arrays.asList(new String[]{"b", "k", "z"}))); GeneralizedCounter<String> gc1 = gc.conditionalize(Arrays.asList(new String[]{"a"})); gc1.incrementCount(Arrays.asList(new String[]{"j", "x"})); gc1.incrementCount2D("j", "z"); GeneralizedCounter<String> gc2 = gc1.conditionalize(Arrays.asList(new String[]{"j"})); gc2.incrementCount1D("x"); System.out.println("Pretty-printing gc after incrementing gc1:"); gc.prettyPrint(); System.out.println("Total: " + gc.totalCount()); gc1.printKeySet(); System.out.println("another entry set:\n" + gc1.entrySet()); ClassicCounter<List<String>> c = gc.counterView(); System.out.println("string representation of counter view:"); System.out.println(c.toString()); double d1 = c.getCount(Arrays.asList(new String[]{"a", "j", "x"})); double d2 = c.getCount(Arrays.asList(new String[]{"a", "j", "w"})); System.out.println(d1 + " " + d2); ClassicCounter<List<String>> c1 = gc1.counterView(); System.out.println("Count of {j,x} -- should be 3.0\t" + c1.getCount(Arrays.asList(new String[]{"j", "x"}))); System.out.println(c.keySet() + " size " + c.keySet().size()); System.out.println(c1.keySet() + " size " + c1.keySet().size()); System.out.println(c1.equals(c)); System.out.println(c.equals(c1)); System.out.println(c.equals(c)); System.out.println("### testing equality of regular Counter..."); ClassicCounter<String> z1 = new ClassicCounter<>(); ClassicCounter<String> z2 = new ClassicCounter<>(); z1.incrementCount("a1"); z1.incrementCount("a2"); z2.incrementCount("b"); System.out.println(z1.equals(z2)); System.out.println(z1.toString()); System.out.println(z1.keySet().toString()); } // below is testing code private void printKeySet() { Set<?> keys = keySet(); System.out.println("printing keyset:"); for (Object o: keys) { //System.out.println(Arrays.asList((Object[]) i.next())); System.out.println(o); } } private static void arrayPrintDouble(double[] o) { for (double anO : o) { System.out.print(anO + "\t"); } System.out.println(); } private Set<?> dumpKeys() { return map.keySet(); } /** * pretty-prints the GeneralizedCounter to {@link System#out}. */ public void prettyPrint() { prettyPrint(new PrintWriter(System.out, true)); } /** * pretty-prints the GeneralizedCounter, using a buffer increment of two spaces. */ public void prettyPrint(PrintWriter pw) { prettyPrint(pw, " "); } /** * pretty-prints the GeneralizedCounter. */ public void prettyPrint(PrintWriter pw, String bufferIncrement) { prettyPrint(pw, "", bufferIncrement); } private void prettyPrint(PrintWriter pw, String buffer, String bufferIncrement) { if (depth == 1) { for (Map.Entry<?, Double> e: entrySet()) { Object key = e.getKey(); double count = e.getValue(); pw.println(buffer + key + "\t" + count); } } else { for (K key: topLevelKeySet()) { GeneralizedCounter<K> gc1 = conditionalize(Arrays.asList(ErasureUtils.<K[]>uncheckedCast(new Object[]{key}))); pw.println(buffer + key + "\t" + gc1.totalCount()); gc1.prettyPrint(pw, buffer + bufferIncrement, bufferIncrement); } } } }