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: IntSet.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 IntSet
     24 {
     25     // public: ................................................................
     26 
     27     /**
     28      * Equivalent to <CODE>IntSet(11, 0.75F)</CODE>.
     29      */
     30     public IntSet ()
     31     {
     32         this (11, 0.75F);
     33     }
     34 
     35     /**
     36      * Equivalent to <CODE>IntSet(capacity, 0.75F)</CODE>.
     37      */
     38     public IntSet (final int initialCapacity)
     39     {
     40         this (initialCapacity, 0.75F);
     41     }
     42 
     43     /**
     44      * Constructs an IntSet with specified initial capacity and load factor.
     45      *
     46      * @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
     47      * @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
     48      */
     49     public IntSet (int initialCapacity, final float loadFactor)
     50     {
     51         if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
     52         if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
     53             throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
     54 
     55         if (initialCapacity == 0) initialCapacity = 1;
     56 
     57         m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;
     58         m_sizeThreshold = (int) (initialCapacity * loadFactor);
     59         m_buckets = new Entry [initialCapacity];
     60     }
     61 
     62 
     63     /**
     64      * Overrides Object.toString() for debug purposes.
     65      */
     66     public String toString ()
     67     {
     68         final StringBuffer s = new StringBuffer ();
     69         debugDump (s);
     70 
     71         return s.toString ();
     72     }
     73 
     74     /**
     75      * Returns the number of key-value mappings in this map.
     76      */
     77     public int size ()
     78     {
     79         return m_size;
     80     }
     81 
     82     public boolean contains (final int key)
     83     {
     84         // index into the corresponding hash bucket:
     85         final Entry [] buckets = m_buckets;
     86         final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
     87 
     88         // traverse the singly-linked list of entries in the bucket:
     89         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
     90         {
     91             if (key == entry.m_key)
     92                 return true;
     93         }
     94 
     95         return false;
     96     }
     97 
     98     public int [] values ()
     99     {
    100         if (m_size == 0)
    101             return IConstants.EMPTY_INT_ARRAY;
    102         else
    103         {
    104             final int [] result = new int [m_size];
    105             int scan = 0;
    106 
    107             for (int b = 0; b < m_buckets.length; ++ b)
    108             {
    109                 for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
    110                 {
    111                     result [scan ++] = entry.m_key;
    112                 }
    113             }
    114 
    115             return result;
    116         }
    117     }
    118 
    119     public void values (final int [] target, final int offset)
    120     {
    121         if (m_size != 0)
    122         {
    123             int scan = offset;
    124 
    125             for (int b = 0; b < m_buckets.length; ++ b)
    126             {
    127                 for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
    128                 {
    129                     target [scan ++] = entry.m_key;
    130                 }
    131             }
    132         }
    133     }
    134 
    135     public boolean add (final int key)
    136     {
    137         Entry currentKeyEntry = null;
    138 
    139         // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:
    140 
    141         // index into the corresponding hash bucket:
    142         int bucketIndex = (key & 0x7FFFFFFF) % m_buckets.length;
    143 
    144         // traverse the singly-linked list of entries in the bucket:
    145         Entry [] buckets = m_buckets;
    146         for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
    147         {
    148             if (key == entry.m_key)
    149             {
    150                 currentKeyEntry = entry;
    151                 break;
    152             }
    153         }
    154 
    155         if (currentKeyEntry == null)
    156         {
    157             // add a new entry:
    158 
    159             if (m_size >= m_sizeThreshold) rehash ();
    160 
    161             buckets = m_buckets;
    162             bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
    163             final Entry bucketListHead = buckets [bucketIndex];
    164             final Entry newEntry = new Entry (key, bucketListHead);
    165             buckets [bucketIndex] = newEntry;
    166 
    167             ++ m_size;
    168 
    169             return true;
    170         }
    171         else
    172             return false;
    173     }
    174 
    175     // protected: .............................................................
    176 
    177     // package: ...............................................................
    178 
    179 
    180     void debugDump (final StringBuffer out)
    181     {
    182         if (out != null)
    183         {
    184             out.append (super.toString ()); out.append (EOL);
    185             out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
    186             out.append ("size threshold = " + m_sizeThreshold + EOL);
    187         }
    188     }
    189 
    190     // private: ...............................................................
    191 
    192 
    193     /**
    194      * The structure used for chaining colliding keys.
    195      */
    196     private static final class Entry
    197     {
    198         Entry (final int key, final Entry next)
    199         {
    200             m_key = key;
    201             m_next = next;
    202         }
    203 
    204         final int m_key;
    205 
    206         Entry m_next; // singly-linked list link
    207 
    208     } // end of nested class
    209 
    210 
    211     /**
    212      * Re-hashes the table into a new array of buckets.
    213      */
    214     private void rehash ()
    215     {
    216         // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
    217         // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
    218         // only grow in size
    219 
    220         final Entry [] buckets = m_buckets;
    221 
    222         final int newBucketCount = (m_buckets.length << 1) + 1;
    223         final Entry [] newBuckets = new Entry [newBucketCount];
    224 
    225         // rehash all entry chains in every bucket:
    226         for (int b = 0; b < buckets.length; ++ b)
    227         {
    228             for (Entry entry = buckets [b]; entry != null; )
    229             {
    230                 final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
    231                 final int entryKey = entry.m_key;
    232 
    233                 // index into the corresponding new hash bucket:
    234                 final int newBucketIndex = (entryKey & 0x7FFFFFFF) % newBucketCount;
    235 
    236                 final Entry bucketListHead = newBuckets [newBucketIndex];
    237                 entry.m_next = bucketListHead;
    238                 newBuckets [newBucketIndex] = entry;
    239 
    240                 entry = next;
    241             }
    242         }
    243 
    244 
    245         m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
    246         m_buckets = newBuckets;
    247     }
    248 
    249 
    250     private final float m_loadFactor; // determines the setting of m_sizeThreshold
    251 
    252     private Entry [] m_buckets; // table of buckets
    253     private int m_size; // number of keys in the table, not cleared as of last check
    254     private int m_sizeThreshold; // size threshold for rehashing
    255 
    256     private static final String EOL = System.getProperty ("line.separator", "\n");
    257 
    258 } // end of class
    259 // ----------------------------------------------------------------------------
    260 
    261