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 */
9package 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 */
22public
23final 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