Home | History | Annotate | Download | only in util
      1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
      2  *
      3  * This program and the accompanying materials are made available under
      4  * the terms of the Common Public License v1.0 which accompanies this distribution,
      5  * and is available at http://www.eclipse.org/legal/cpl-v10.html
      6  *
      7  * $Id: IntIntMap.java,v 1.1.1.1 2004/05/09 16:57:53 vlad_r Exp $
      8  */
      9 package com.vladium.util;
     10 
     11 // ----------------------------------------------------------------------------
     12 /**
     13  *
     14  * MT-safety: an instance of this class is <I>not</I> safe for access from
     15  * multiple concurrent threads [even if access is done by a single thread at a
     16  * time]. The caller is expected to synchronize externally on an instance [the
     17  * implementation does not do internal synchronization for the sake of efficiency].
     18  * java.util.ConcurrentModificationException is not supported either.
     19  *
     20  * @author Vlad Roubtsov, (C) 2001
     21  */
     22 public
     23 final class IntIntMap
     24 {
     25     // public: ................................................................
     26 
     27     // TODO: optimize key comparisons using key.hash == entry.key.hash condition
     28 
     29     /**
     30      * Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.
     31      */
     32     public IntIntMap ()
     33     {
     34         this (11, 0.75F);
     35     }
     36 
     37     /**
     38      * Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
     39      */
     40     public IntIntMap (final int initialCapacity)
     41     {
     42         this (initialCapacity, 0.75F);
     43     }
     44 
     45     /**
     46      * Constructs an IntObjectMap with specified initial capacity and load factor.
     47      *
     48      * @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
     49      * @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
     50      */
     51     public IntIntMap (int initialCapacity, final float loadFactor)
     52     {
     53         if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
     54         if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
     55             throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
     56 
     57         if (initialCapacity == 0) initialCapacity = 1;
     58 
     59         m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;
     60         m_sizeThreshold = (int) (initialCapacity * loadFactor);
     61         m_buckets = new Entry [initialCapacity];
     62     }
     63 
     64 
     65     /**
     66      * Overrides Object.toString() for debug purposes.
     67      */
     68     public String toString ()
     69     {
     70         final StringBuffer s = new StringBuffer ();
     71         debugDump (s);
     72 
     73         return s.toString ();
     74     }
     75 
     76     /**
     77      * Returns the number of key-value mappings in this map.
     78      */
     79     public int size ()
     80     {
     81         return m_size;
     82     }
     83 
     84     public boolean contains (final int key)
     85     {
     86         // index into the corresponding hash bucket:
     87         final Entry [] buckets = m_buckets;
     88         final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
     89 
     90         // traverse the singly-linked list of entries in the bucket:
     91         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
     92         {
     93             if (key == entry.m_key) return true;
     94         }
     95 
     96         return false;
     97     }
     98 
     99     /**
    100      * Returns the value that is mapped to a given 'key'. Returns
    101      * false if this key has never been mapped.
    102      *
    103      * @param key mapping key
    104      * @param out holder for the found value [must be at least of size 1]
    105      *
    106      * @return 'true' if this key was mapped to an existing value
    107      */
    108     public boolean get (final int key, final int [] out)
    109     {
    110         // index into the corresponding hash bucket:
    111         final Entry [] buckets = m_buckets;
    112         final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
    113 
    114         // traverse the singly-linked list of entries in the bucket:
    115         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
    116         {
    117             if (key == entry.m_key)
    118             {
    119                 out [0] = entry.m_value;
    120                 return true;
    121             }
    122         }
    123 
    124         return false;
    125     }
    126 
    127     public boolean get (final int key, final int [] out, final int index)
    128     {
    129         // index into the corresponding hash bucket:
    130         final Entry [] buckets = m_buckets;
    131         final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
    132 
    133         // traverse the singly-linked list of entries in the bucket:
    134         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
    135         {
    136             if (key == entry.m_key)
    137             {
    138                 out [index] = entry.m_value;
    139                 return true;
    140             }
    141         }
    142 
    143         return false;
    144     }
    145 
    146     public int [] keys ()
    147     {
    148         final int [] result = new int [m_size];
    149         int scan = 0;
    150 
    151         for (int b = 0; b < m_buckets.length; ++ b)
    152         {
    153             for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
    154             {
    155                 result [scan ++] = entry.m_key;
    156             }
    157         }
    158 
    159         return result;
    160     }
    161 
    162     /**
    163      * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
    164      *
    165      * @param key mapping key
    166      * @param value mapping value
    167      */
    168     public void put (final int key, final int value)
    169     {
    170         Entry currentKeyEntry = null;
    171 
    172         // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:
    173 
    174         // index into the corresponding hash bucket:
    175         int bucketIndex = (key & 0x7FFFFFFF) % m_buckets.length;
    176 
    177         // traverse the singly-linked list of entries in the bucket:
    178         Entry [] buckets = m_buckets;
    179         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
    180         {
    181             if (key == entry.m_key)
    182             {
    183                 currentKeyEntry = entry;
    184                 break;
    185             }
    186         }
    187 
    188         if (currentKeyEntry != null)
    189         {
    190             // replace the current value:
    191 
    192             currentKeyEntry.m_value = value;
    193         }
    194         else
    195         {
    196             // add a new entry:
    197 
    198             if (m_size >= m_sizeThreshold) rehash ();
    199 
    200             buckets = m_buckets;
    201             bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
    202             final Entry bucketListHead = buckets [bucketIndex];
    203             final Entry newEntry = new Entry (key, value, bucketListHead);
    204             buckets [bucketIndex] = newEntry;
    205 
    206             ++ m_size;
    207         }
    208     }
    209 
    210     /**
    211      * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
    212      *
    213      * @param key mapping key
    214      */
    215     public void remove (final int key)
    216     {
    217         // index into the corresponding hash bucket:
    218         final int bucketIndex = (key  & 0x7FFFFFFF) % m_buckets.length;
    219 
    220         // traverse the singly-linked list of entries in the bucket:
    221         Entry [] buckets = m_buckets;
    222         for (Entry entry = buckets [bucketIndex], prev = entry; entry != null; )
    223         {
    224             final Entry next = entry.m_next;
    225 
    226             if (key == entry.m_key)
    227             {
    228                 if (prev == entry)
    229                     buckets [bucketIndex] = next;
    230                 else
    231                     prev.m_next = next;
    232 
    233                 -- m_size;
    234                 break;
    235             }
    236 
    237             prev = entry;
    238             entry = next;
    239         }
    240     }
    241 
    242 
    243     // protected: .............................................................
    244 
    245     // package: ...............................................................
    246 
    247 
    248     void debugDump (final StringBuffer out)
    249     {
    250         if (out != null)
    251         {
    252             out.append (super.toString ()); out.append (EOL);
    253             out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
    254             out.append ("size threshold = " + m_sizeThreshold + EOL);
    255         }
    256     }
    257 
    258     // private: ...............................................................
    259 
    260 
    261     /**
    262      * The structure used for chaining colliding keys.
    263      */
    264     private static final class Entry
    265     {
    266         Entry (final int key, final int value, final Entry next)
    267         {
    268             m_key = key;
    269             m_value = value;
    270             m_next = next;
    271         }
    272 
    273         int m_key;
    274         int m_value;
    275 
    276         Entry m_next; // singly-linked list link
    277 
    278     } // end of nested class
    279 
    280 
    281     /**
    282      * Re-hashes the table into a new array of buckets.
    283      */
    284     private void rehash ()
    285     {
    286         // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
    287         // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
    288         // only grow in size
    289 
    290         final Entry [] buckets = m_buckets;
    291 
    292         final int newBucketCount = (m_buckets.length << 1) + 1;
    293         final Entry [] newBuckets = new Entry [newBucketCount];
    294 
    295         // rehash all entry chains in every bucket:
    296         for (int b = 0; b < buckets.length; ++ b)
    297         {
    298             for (Entry entry = buckets [b]; entry != null; )
    299             {
    300                 final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
    301                 final int entryKeyHash = entry.m_key & 0x7FFFFFFF;
    302 
    303                 // index into the corresponding new hash bucket:
    304                 final int newBucketIndex = entryKeyHash % newBucketCount;
    305 
    306                 final Entry bucketListHead = newBuckets [newBucketIndex];
    307                 entry.m_next = bucketListHead;
    308                 newBuckets [newBucketIndex] = entry;
    309 
    310                 entry = next;
    311             }
    312         }
    313 
    314 
    315         m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
    316         m_buckets = newBuckets;
    317     }
    318 
    319 
    320     private final float m_loadFactor; // determines the setting of m_sizeThreshold
    321 
    322     private Entry [] m_buckets; // table of buckets
    323     private int m_size; // number of keys in the table, not cleared as of last check
    324     private int m_sizeThreshold; // size threshold for rehashing
    325 
    326     private static final String EOL = System.getProperty ("line.separator", "\n");
    327 
    328 } // end of class
    329 // ----------------------------------------------------------------------------
    330 
    331