From c48106695ace7bf0f4c172aa421af0cb7505a5dd Mon Sep 17 00:00:00 2001 From: "ray.j.greenwell" Date: Sat, 16 Jan 2010 03:00:23 +0000 Subject: [PATCH] Ok, Charlie Groves has done a good job convincing me I'm crazy, but I'll check this in anyway, at least for posterity. The advantages over the 'CountingMap' (currently in yohoho): - Faster, because adding to a count doesn't add to a boxed int, nor does it hash twice (except on the intial use of a particular key). The disadvantages: - More complicated implementation (CountingMap builds upon google collections) - If a non-HashMap is desired, the fact that the underlying map is is exposed. - When iterating over the entrySet, each entry is a new object created solely for your iteration. So.. yeah. I'll check it in. Maybe I'll blow it away. We should probably think about how samskivert will move forward- do we want to depend on google-collect or guava? Do we want to deprecate huge swaths of this library in preference to those? A lot of what's in here is a bit outdated, but maintained to support code built atop it. git-svn-id: https://samskivert.googlecode.com/svn/trunk@2704 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- src/java/com/samskivert/util/CountMap.java | 240 +++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 src/java/com/samskivert/util/CountMap.java diff --git a/src/java/com/samskivert/util/CountMap.java b/src/java/com/samskivert/util/CountMap.java new file mode 100644 index 00000000..fcc23c4e --- /dev/null +++ b/src/java/com/samskivert/util/CountMap.java @@ -0,0 +1,240 @@ +// +// $Id$ + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * A CountMap maps keys to non-null Integers and provides methods for efficiently adding + * to the count. + */ +public class CountMap extends AbstractMap +{ + /** + * Create a new CountMap backed by a HashMap. + */ + public CountMap () + { + this(new HashMap()); + } + + /** + * For subclassing, etc. Not yet public. + */ + protected CountMap (Map backing) + { + if (!backing.isEmpty()) { + throw new IllegalArgumentException("Map is non-empty"); + } + _backing = backing; + } + + /** + * Add 1 to the count for the specified key. + */ + public int increment (K key) + { + return add(key, 1); + } + + /** + * Subtract 1 from the count for the specified key. + */ + public int decrement (K key) + { + return add(key, -1); + } + + /** + * Add the specified amount to the count for the specified key. + */ + public int add (K key, int amount) + { + int[] val = _backing.get(key); + if (val == null) { + _backing.put(key, val = new int[1]); + } + val[0] += amount; + return val[0]; + } + + /** + * Get the count for the specified key. If the key is not present, 0 is returned. + */ + public int getCount (K key) + { + int[] val = _backing.get(key); + return (val == null) ? 0 : val[0]; + } + + /** + * Remove any keys for which the count is currently 0. + */ + public void compress () + { + for (Iterator it = _backing.values().iterator(); it.hasNext(); ) { + if (it.next()[0] == 0) { + it.remove(); + } + } + } + + @Override + public Set> entrySet () + { + if (_entrySet == null) { + _entrySet = new EntrySet(); + } + return _entrySet; + } + + @Override + public Integer put (K key, Integer value) + { + return integer(_backing.put(key, new int[] { value.intValue() })); + } + + @Override + public boolean containsKey (Object key) + { + return _backing.containsKey(key); + } + + @Override + public Integer get (Object key) + { + return integer(_backing.get(key)); + } + + @Override + public Integer remove (Object key) + { + return integer(_backing.remove(key)); + } + + @Override + public void clear () + { + _backing.clear(); + } + + @Override + public int size () + { + return _backing.size(); + } + + @Override + public boolean isEmpty () + { + return _backing.isEmpty(); + } + + /** + * Our EntrySet. + */ + protected class EntrySet extends AbstractSet> + { + public Iterator> iterator () { + return new Iterator>() { + public boolean hasNext () { + return _it.hasNext(); + } + + public Map.Entry next () { + // I don't see any way around creating a new Entry here + return adaptEntry(_it.next()); + } + + public void remove () { + _it.remove(); + } + protected Iterator> _it = _backing.entrySet().iterator(); + }; + } + + public boolean contains (Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry entry = (Map.Entry) o; + Integer value = CountMap.this.get(entry.getKey()); + // we don't allow storing null, so getting a null means there's no mapping, + // and we don't have to check containsKey + return (value != null) && value.equals(entry.getValue()); + } + + public boolean remove (Object o) { + if (contains(o)) { + CountMap.this.remove(((Map.Entry) o).getKey()); + return true; + } + return false; + } + + public int size () { + return CountMap.this.size(); + } + + public void clear () { + CountMap.this.clear(); + } + } + + /** + * Return null or the boxed value contained in the count. + */ + protected static final Integer integer (int[] val) + { + return (val == null) ? null : val[0]; + } + + /** + * Adapt an entry from our internal backing map to one visible to users of this class. + * Grumble. + */ + protected static Map.Entry adaptEntry (final Map.Entry entry) + { + return new Map.Entry() { + public K getKey () { + return entry.getKey(); + } + public Integer getValue () { + return integer(entry.getValue()); + } + public Integer setValue (Integer newVal) { + int[] val = entry.getValue(); + Integer ret = integer(val); + val[0] = newVal.intValue(); + return ret; + } + public int hashCode () { + K key = getKey(); + return ((key == null) ? 0 : key.hashCode()) ^ getValue().hashCode(); + } + public boolean equals (Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry e = (Map.Entry) o; + K key = getKey(); + Object key2 = e.getKey(); + return ((key == null) ? (key2 == null) : key.equals(key2)) && + getValue().equals(e.getValue()); + } + public String toString () { + return getKey() + "=" + entry.getValue()[0]; + } + }; + } + + /** Our backing map */ + protected Map _backing; + + /** The entrySet, if created. */ + protected transient volatile Set> _entrySet = null; +}