001    /*
002    NGramJ - n-gram based text classification
003    Copyright (C) 2001 Frank S. Nestel (frank at spieleck.de)
004    
005    This program is free software; you can redistribute it and/or modify
006    it under the terms of the GNU Lesser General Public License as published 
007    by the Free Software Foundation; either version 2.1 of the License, or
008    (at your option) any later version.
009    
010    This program is distributed in the hope that it will be useful,
011    but WITHOUT ANY WARRANTY; without even the implied warranty of
012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013    GNU General Public License for more details.
014    
015    You should have received a copy of the GNU Lesser General Public License
016    along with this program (lesser.txt); if not, write to the Free Software
017    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
018    */
019    
020    package de.spieleck.util;
021    
022    import java.lang.StringBuffer;
023    import java.util.Enumeration;
024    import java.util.Iterator;
025    
026    /**
027     * The IntMap provides a simple hashmap from keys to integers.  
028     * The API is simplified of the HashMap/Hashtable collection API.
029     * Additionally there are call to do simple arithmetic with entries
030     * in the map.
031     *
032     * <p>The convenience of IntMap is avoiding all wrapping of integers.
033     * </p>
034     * <B>Note:</B> This class is completely unsynchronized for speed!
035     */
036    public class IntMap 
037    {
038        /**
039         * Encoding of a null entry.  Since NULL is equal to Integer.MIN_VALUE, 
040         * it's impossible to distinguish between the two.
041         */
042        public final static int NULL = Integer.MIN_VALUE;
043    
044        protected Object [] keys;
045        protected int nullValue;
046        protected int []values;
047        protected int size;
048        protected int mask;
049        protected int limit;
050    
051        /**
052         * Create a new IntMap.  Default size is 16.
053         */
054        public IntMap()
055        {
056            this(16);
057        }
058    
059        public IntMap(int size)
060        {
061            this(size, NULL);
062        }
063    
064        public IntMap(int size, int nullValue)
065        {
066            if ( size < 8 ) 
067                size = 8;
068            int z = 0;
069            while ( size > 1 )
070            {
071                z++;
072                size /= 2;
073            }
074            size = 2 << z;
075            keys = new Object[size];
076            values = new int[size];
077    
078            mask = keys.length - 1;
079            limit = 3 * keys.length / 4;
080            size = 0;
081            this.nullValue = nullValue;
082        }
083    
084        /**
085         *
086         */
087        public int getNullValue()
088        {
089            return nullValue;
090        }
091    
092        /**
093         * This only works with binary indizes
094         */
095        protected int firstIndex(Object key)
096        {
097            return key.hashCode() & mask;
098        }
099    
100        protected int nextIndex(int index)
101        {
102            return (index + 5) & mask;
103        }
104    
105        /**
106         * Clear the hashmap.
107         * XXX Or should we let the GC the work an use keys = new Object[..]???
108         */
109        public void clear()
110        {
111            for (int i = 0; i < values.length; i++) 
112                keys[i] = null;
113            size = 0;
114        }
115    
116        /**
117         * Returns the current number of entries in the map.
118         */
119        public int size() 
120        { 
121            return size;
122        }
123    
124        /**
125         * Get an Element
126         */
127        public int getElement(Object key)
128        {
129            return get(key);
130        }
131    
132        /**
133         * Get an Element
134         */
135        public int get(Object key)
136        {
137            if ( key == null )
138                return nullValue;
139            for (int i = firstIndex(key); keys[i] != null; i = nextIndex(i))
140            {
141                if ( keys[i] == key || key.equals(keys[i]) )
142                    return values[i];
143            }
144            return nullValue;
145        }
146    
147        /** 
148         * Optimized operations.
149         * avoid get change put cycles.
150         */
151        public int inc(Object key)
152        {
153            return add(key, 1);
154        }
155    
156        /** 
157         * Optimized operations.
158         * avoid get change put cycles.
159         */
160        public int dec(Object key)
161        {
162            return add(key, -1);
163        }
164    
165        /** 
166         * Optimized operations.
167         * avoid get change put cycles.
168         */
169        public int add(Object key, int off)
170        {
171            if ( key == null )
172                return nullValue;
173            int i;
174            for (i = firstIndex(key); keys[i] != null; i = nextIndex(i))
175            {
176                if ( keys[i] == key || key.equals(keys[i]) )
177                {
178                    values[i] += off;
179                    return values[i];
180                }
181            }
182            return nullValue;
183        }
184    
185    
186        /**
187         * Expands the table
188         */
189        protected void grow()
190        {
191            int newSize = 2 * keys.length;
192            Object[] oldKeys = keys;
193            int[] oldValues = values;
194            //
195            keys = new Object[newSize];
196            values = new int[newSize];
197            mask = newSize - 1;
198            size = 0; // We recound the size below!
199            for (int i = 0; i < oldKeys.length; i++) 
200                if (oldKeys[i] != null)
201                    internalPut(oldKeys[i], oldValues[i]);
202            limit = 3 * newSize / 4;
203        }
204    
205        /**
206         * Puts a new value in the property table with the appropriate flags
207         */
208        public int putElement(Object key, int value)
209        {
210            return put(key, value);
211        }
212    
213        public int put(Object key, int value)
214        {
215            if (key == null) 
216                return nullValue;
217            if ( size >= limit )
218                grow();
219            return internalPut(key, value);
220        }
221    
222        protected int internalPut(Object key, int value)
223        {
224            for(int i = firstIndex(key); true; i = nextIndex(i))
225            {
226                Object testKey = keys[i];
227                if ( testKey == null )
228                {
229                    keys[i] = key;
230                    values[i] = value;
231                    size++;
232                    return nullValue;
233                }
234                else if ( key == testKey || testKey.equals(key))
235                {
236                    int old = values[i];
237                    values[i] = value;
238                    return old;
239                }
240            }
241        }
242    
243        /**
244         * Deletes the entry.  
245         */
246        public int remove(Object key)
247        {
248            if (key == null || size == 0) 
249                return nullValue;
250    
251            int i;
252            for (i = firstIndex(key); keys[i] != null; i = nextIndex(i))
253            {
254                Object testKey = keys[i];
255                if (key == testKey || key.equals(testKey) )
256                {
257                    int value = values[i];
258                    do 
259                    {
260                        keys[i] = null;
261                        int j = i;
262                        int r;
263                        do 
264                        {
265                            i = nextIndex(i);
266                            if (keys[i] == null)
267                                break;
268                            r = firstIndex(keys[i]);
269                        } 
270                        while (   (i <= r && r < j) 
271                               || (r < j && j < i) 
272                               || (j < i && i <= r)
273                               );
274                        keys[j] = keys[i];
275                        values[j] = values[i];
276                    } 
277                    while (keys[i] != null);
278                    --size;
279                    return value;
280                }
281            }
282            return nullValue;
283        }
284         
285        public Enumeration keys()
286        {
287            return new IntMapEnumeration();
288        }
289    
290        public Iterator iterator()
291        {
292            return new IntMapIterator();
293        }
294    
295        public String toString()
296        {
297            StringBuffer sbuf = new StringBuffer();
298            sbuf.append("IntMap[");
299            boolean isFirst = true;
300            for (int i = 0; i < keys.length; i++) 
301            {
302                if (keys[i] != null) 
303                {
304                    if (! isFirst)
305                        sbuf.append(", ");
306                    isFirst = false;
307                    sbuf.append(keys[i]);
308                    sbuf.append(":");
309                    sbuf.append(values[i]);
310                }
311            }
312            sbuf.append("]");
313            return sbuf.toString();
314        }
315    
316        private class IntMapEnumeration 
317            implements Enumeration 
318        {
319            protected int index = 0;
320    
321            public boolean hasMoreElements()
322            {
323                for (; index < keys.length; index++)
324                    if (keys[index] != null )
325                        return true;
326                return false;
327            }
328    
329            public Object nextElement()
330            {
331                for (; index < keys.length; index++)
332                    if (keys[index] != null )
333                        return keys[index++];
334                return null;
335            }
336        }
337    
338        private class IntMapIterator 
339            implements Iterator 
340        {
341            protected int index = 0;
342    
343            public boolean hasNext()
344            {
345                for (; index < keys.length; index++)
346                    if (keys[index] != null )
347                        return true;
348                return false;
349            }
350    
351            public Object next()
352            {
353                for (; index < keys.length; index++)
354                    if (keys[index] != null )
355                        return keys[index++];
356                return null;
357            }
358    
359            public void remove()
360            {
361                throw new java.lang.UnsupportedOperationException(
362                                                    "IntMapIterator.remove()");
363            }
364        }
365    
366    }
367