|
4 | 4 | import java.util.Map; |
5 | 5 |
|
6 | 6 | /** |
7 | | - * A generic class representing a collection of elements identified |
8 | | - * by a series of indices which length can vary. |
9 | | - * The indices are hashed into a single long key indexing the collection. |
| 7 | + * A generic class representing a collection of elements identified by a series |
| 8 | + * of indices which length can vary. The indices are hashed into a single long |
| 9 | + * key indexing the collection. |
10 | 10 | * |
11 | 11 | * @param <T> the type of elements stored in the list |
12 | 12 | * |
13 | 13 | * @author gavalian |
14 | 14 | */ |
15 | 15 | public class IndexedList<T> { |
| 16 | + |
16 | 17 | //Collection of elements |
17 | | - private final Map<Long,T> collection = new LinkedHashMap<>(); |
| 18 | + private final Map<Long, T> collection = new LinkedHashMap<>(); |
18 | 19 | //Number of indices |
19 | 20 | private int indexSize; |
20 | 21 | //index generator used for hashing the multiple |
21 | 22 | //indices into a single long key |
22 | 23 | private IndexGenerator indexGenerator; |
23 | | - |
| 24 | + |
24 | 25 | /** |
25 | 26 | * Constructs an empty IndexedList with the default index size (3). |
26 | 27 | */ |
27 | 28 | public IndexedList() { |
28 | | - this(3); |
| 29 | + this.indexSize = 3; |
| 30 | + //Initialization with the default index |
| 31 | + this.indexGenerator = new IndexGenerator(); |
29 | 32 | } |
30 | | - |
| 33 | + |
31 | 34 | /** |
32 | 35 | * Constructs an IndexedList with the specified number of indices. |
33 | 36 | * |
34 | 37 | * @param indsize the number of indices |
35 | 38 | */ |
36 | | - public IndexedList(int indsize){ |
| 39 | + public IndexedList(int indsize) { |
37 | 40 | this.indexSize = indsize; |
| 41 | + //Initialization with the default index |
| 42 | + this.indexGenerator = new IndexGenerator(); |
| 43 | + } |
| 44 | + |
| 45 | + /** |
| 46 | + * Constructs an IndexedList with the specified number of indices. |
| 47 | + * |
| 48 | + * @param byteShifts the byte shifts to consider to build the index |
| 49 | + */ |
| 50 | + public IndexedList(int[] byteShifts) { |
| 51 | + this.indexSize = byteShifts.length; |
38 | 52 | //Initialization with the index size |
39 | | - this.indexGenerator = new IndexGenerator(indsize); |
| 53 | + this.indexGenerator = new IndexGenerator(byteShifts); |
40 | 54 | } |
41 | | - |
42 | | - |
| 55 | + |
43 | 56 | /** |
44 | | - * Helper method to check if an index can be looked up. |
45 | | - * |
46 | | - * @param index array to check |
47 | | - * @return true or false if the index is valid or not |
48 | | - */ |
| 57 | + * Helper method to check if an index can be looked up. |
| 58 | + * |
| 59 | + * @param index array to check |
| 60 | + * @return true or false if the index is valid or not |
| 61 | + */ |
49 | 62 | private boolean isValidIndex(int... index) { |
50 | | - return index != null && index.length == this.indexSize; |
| 63 | + return index != null && index.length == this.indexSize; |
51 | 64 | } |
52 | | - |
| 65 | + |
53 | 66 | /** |
54 | | - * Helper method to handle errors for wrong indices |
55 | | - * |
56 | | - * @param index array to check |
57 | | - */ |
| 67 | + * Helper method to handle errors for wrong indices |
| 68 | + * |
| 69 | + * @param index array to check |
| 70 | + * @throws IllegalArgumentException if the number of indices or their values |
| 71 | + * exceeds supported parameters |
| 72 | + */ |
58 | 73 | private void validateIndex(int... index) { |
59 | | - if (!isValidIndex(index)) { |
60 | | - throw new IllegalArgumentException("Index length mismatch: expected " + this.indexSize); |
61 | | - } |
| 74 | + if (!isValidIndex(index)) { |
| 75 | + throw new IllegalArgumentException("Index length mismatch: expected " + this.indexSize); |
| 76 | + } |
| 77 | + for (int i = 0; i < index.length-1; i++) { |
| 78 | + int bits = (i != 0) |
| 79 | + ? indexGenerator.getByteShifts()[i] - indexGenerator.getByteShifts()[i + 1] |
| 80 | + : 64 - indexGenerator.getByteShifts()[i]; // First field: number of bits from shift to 64 |
62 | 81 |
|
63 | | - int maxValue = (indexGenerator.shiftsToConsider == IndexGenerator.BYTE_SHIFTS) ? 0xFFFF : 0x7F; |
| 82 | + int maxValue = (1 << bits) - 1; |
64 | 83 |
|
65 | | - for (int i : index) { |
66 | | - if (i < 0 || i > maxValue) { |
67 | | - throw new IllegalArgumentException( |
68 | | - String.format("Index value out of range (0–%d): %d", maxValue, i) |
69 | | - ); |
| 84 | + // Check if the index value is within the allowed range |
| 85 | + if (index[i] < 0 || index[i] > maxValue) { |
| 86 | + throw new IllegalArgumentException( |
| 87 | + String.format("Index value out of range (0–%d) for byte shift %d: %d", maxValue, bits, index[i]) |
| 88 | + ); |
| 89 | + } |
70 | 90 | } |
71 | 91 | } |
72 | | -} |
73 | | - |
| 92 | + |
74 | 93 | /** |
75 | 94 | * Adds an item to the collection with its index. |
76 | 95 | * |
77 | 96 | * @param item the item to be added |
78 | 97 | * @param index the index array used to identify the item |
79 | 98 | */ |
80 | | - public void add(T item, int... index){ |
| 99 | + public void add(T item, int... index) { |
81 | 100 | validateIndex(index); |
82 | 101 | long code = this.indexGenerator.hashCode(index); |
83 | 102 | this.collection.put(code, item); |
84 | 103 | } |
85 | | - |
| 104 | + |
86 | 105 | /** |
87 | 106 | * Checks whether an item exists for the specified index. |
88 | 107 | * |
89 | 108 | * @param index the index to look up |
90 | 109 | * @return true if an item exists at the index; false otherwise |
91 | 110 | */ |
92 | | - public boolean hasItem(int... index){ |
93 | | - if(!isValidIndex(index)) return false; |
| 111 | + public boolean hasItem(int... index) { |
| 112 | + if (!isValidIndex(index)) { |
| 113 | + return false; |
| 114 | + } |
94 | 115 | long code = indexGenerator.hashCode(index); |
95 | 116 | return this.collection.containsKey(code); |
96 | 117 | } |
97 | | - |
| 118 | + |
98 | 119 | /** |
99 | 120 | * Retrieves an item by its index. |
100 | 121 | * |
101 | 122 | * @param index the index to find |
102 | 123 | * @return the item at the index, null if not found |
103 | 124 | */ |
104 | | - public T getItem(int... index){ |
105 | | - if (!isValidIndex(index)) return null; |
| 125 | + public T getItem(int... index) { |
| 126 | + if (!isValidIndex(index)) { |
| 127 | + return null; |
| 128 | + } |
106 | 129 | long code = indexGenerator.hashCode(index); |
107 | 130 | return this.collection.get(code); |
108 | 131 | } |
109 | | - |
| 132 | + |
110 | 133 | /** |
111 | 134 | * Clears items from the collection. |
112 | 135 | */ |
113 | | - public void clear(){this.collection.clear();} |
| 136 | + public void clear() { |
| 137 | + this.collection.clear(); |
| 138 | + } |
| 139 | + |
114 | 140 | /** |
115 | 141 | * Gets the number of indices used to identify elements. |
116 | 142 | * |
117 | 143 | * @return the index size |
118 | 144 | */ |
119 | | - public int getIndexSize(){ return this.indexSize;} |
| 145 | + public int getIndexSize() { |
| 146 | + return this.indexSize; |
| 147 | + } |
| 148 | + |
120 | 149 | /** |
121 | 150 | * Returns the collection of items. |
122 | 151 | * |
123 | 152 | * @return the map of hashed keys to items |
124 | 153 | */ |
125 | | - public Map<Long,T> getMap(){ return this.collection;} |
| 154 | + public Map<Long, T> getMap() { |
| 155 | + return this.collection; |
| 156 | + } |
| 157 | + |
126 | 158 | /** |
127 | 159 | * Returns the index generator for this collection. |
128 | 160 | * |
129 | 161 | * @return the index generator |
130 | 162 | */ |
131 | | - public IndexGenerator getIndexGenerator(){ return this.indexGenerator;} |
132 | | - |
| 163 | + public IndexGenerator getIndexGenerator() { |
| 164 | + return this.indexGenerator; |
| 165 | + } |
| 166 | + |
| 167 | + /** |
| 168 | + * Sets the index generator for this collection. |
| 169 | + * |
| 170 | + * @param indexGenerator, the { |
| 171 | + * @IndexGenerator} to be set |
| 172 | + */ |
| 173 | + public void setIndexGenerator(IndexGenerator indexGenerator) { |
| 174 | + this.indexGenerator = indexGenerator; |
| 175 | + } |
| 176 | + |
133 | 177 | /** |
134 | 178 | * Displays the collection |
135 | 179 | */ |
136 | | - public void show(){ |
137 | | - for(Map.Entry<Long,T> entry : this.collection.entrySet()){ |
| 180 | + public void show() { |
| 181 | + for (Map.Entry<Long, T> entry : this.collection.entrySet()) { |
138 | 182 | String indexString = indexGenerator.getString(entry.getKey(), this.indexSize); |
139 | | - System.out.println(String.format("[%s] : ", |
| 183 | + System.out.println(String.format("[%s] : ", |
140 | 184 | indexString) + entry.getValue()); |
141 | 185 | } |
142 | 186 | } |
143 | 187 |
|
144 | 188 | /** |
145 | | - * Utility class for generating and decoding a long key from a multi-dimensional index. |
146 | | - * either up to 4 indices with 16 bits, or up to 9 indices with 7 bits (int up to 128) are handled |
| 189 | + * Utility class for generating and decoding a long key from a |
| 190 | + * multi-dimensional index. Default is up to 4 indices with 16 bits, but any |
| 191 | + * byte shifts can be set up to a max of 64 |
147 | 192 | */ |
148 | 193 | public static class IndexGenerator { |
149 | | - |
150 | | - //Nominal case, only works up to four indices. 4 times 16 bits = 64 bits for a long |
151 | | - static int[] BYTE_SHIFTS = new int[]{48,32,16,0}; |
152 | | - //Case where you can use up to 9 indices that are int up to 128 |
153 | | - //2^7 = 128 and 9*7 = 63 < 64 |
154 | | - static int[] COMPRESSED_SHIFTS = new int[]{56, 49, 42, 35, 28, 21, 14, 7, 0}; |
155 | | - //This is the shifts that will be considered for all computations |
156 | | - private int[] shiftsToConsider; |
157 | | - |
| 194 | + |
| 195 | + private int[] byteShifts = new int[]{48, 32, 16, 0}; |
| 196 | + |
158 | 197 | /** |
159 | 198 | * Constructs an IndexGenerator with generic index size. |
160 | | - * |
161 | 199 | */ |
162 | | - public IndexGenerator(){ |
163 | | - this(3); |
| 200 | + public IndexGenerator() { |
| 201 | + //Nominal case, only works up to four indices. 4 times 16 bits = 64 bits for a long |
| 202 | + this.byteShifts = new int[]{48, 32, 16, 0}; |
164 | 203 | } |
165 | | - |
| 204 | + |
166 | 205 | /** |
167 | | - * Constructs an IndexGenerator for the specified index size. |
| 206 | + * Constructs an IndexGenerator from a given byte shifts array. |
168 | 207 | * |
169 | | - * @param indsize the number of indices to encode |
170 | | - * @throws IllegalArgumentException if the number of indices exceeds the supported limit |
| 208 | + * @param byteShifts the array of byte shifts to consider |
171 | 209 | */ |
172 | | - public IndexGenerator(int indsize){ |
173 | | - if (indsize > this.COMPRESSED_SHIFTS.length) { |
174 | | - throw new IllegalArgumentException("# indices is larger than "+ this.COMPRESSED_SHIFTS.length); |
175 | | - } |
176 | | - else if (indsize > this.BYTE_SHIFTS.length) { |
177 | | - this.shiftsToConsider = this.COMPRESSED_SHIFTS; |
| 210 | + public IndexGenerator(int[] byteShifts) { |
| 211 | + // Check that no byte shift exceeds 64 |
| 212 | + for (int shift : byteShifts) { |
| 213 | + if (shift < 0 || shift >= 64) { |
| 214 | + throw new IllegalArgumentException("Byte shift must be between 0 and 63."); |
| 215 | + } |
178 | 216 | } |
179 | | - else this.shiftsToConsider = this.BYTE_SHIFTS; |
| 217 | + this.byteShifts = byteShifts; |
| 218 | + } |
| 219 | + |
| 220 | + /** |
| 221 | + * Get the byte shifts |
| 222 | + * |
| 223 | + * @return the array of byte shifts |
| 224 | + */ |
| 225 | + public int[] getByteShifts() { |
| 226 | + return this.byteShifts; |
180 | 227 | } |
181 | 228 |
|
182 | 229 | /** |
183 | 230 | * Generates a long key from the given array of indices. |
184 | 231 | * |
185 | 232 | * @param indices the index array |
186 | 233 | * @return a long key representing the hashed index |
187 | | - * @throws IllegalArgumentException if the number of indices exceeds supported length |
| 234 | + * @throws IllegalArgumentException if the number of indices exceeds |
| 235 | + * supported length |
188 | 236 | */ |
189 | | - public long hashCode(int... indices){ |
| 237 | + public long hashCode(int... indices) { |
190 | 238 | long result = (long) 0; |
191 | | - |
192 | | - if (indices.length > this.shiftsToConsider.length) { |
193 | | - throw new IllegalArgumentException("# indices is larger than "+ this.shiftsToConsider.length); |
| 239 | + |
| 240 | + if (indices.length > this.byteShifts.length) { |
| 241 | + throw new IllegalArgumentException("# indices is larger than " + this.byteShifts.length); |
194 | 242 | } |
195 | | - |
196 | | - for(int loop = 0; loop < indices.length; loop++){ |
197 | | - long patern = (((long) indices[loop])&0x000000000000FFFF)<<this.shiftsToConsider[loop]; |
| 243 | + |
| 244 | + for (int loop = 0; loop < indices.length; loop++) { |
| 245 | + long patern = (((long) indices[loop]) & 0x000000000000FFFF) << this.byteShifts[loop]; |
198 | 246 | result = (result | patern); |
199 | 247 | } |
200 | 248 | return result; |
201 | 249 | } |
202 | | - |
| 250 | + |
203 | 251 | /** |
204 | 252 | * Retrieves a specific index from the encoded long key. |
205 | 253 | * |
206 | 254 | * @param hashcode the encoded long key |
207 | 255 | * @param order the position of the index to retrieve |
208 | 256 | * @return the decoded index |
209 | 257 | */ |
210 | | - public int getIndex(long hashcode, int order){ |
211 | | - int result = (int) (hashcode>>this.shiftsToConsider[order])&0x000000000000FFFF; |
| 258 | + public int getIndex(long hashcode, int order) { |
| 259 | + int result = (int) (hashcode >> this.byteShifts[order]) & 0x000000000000FFFF; |
212 | 260 | return result; |
213 | 261 | } |
214 | | - |
| 262 | + |
215 | 263 | /** |
216 | 264 | * Returns a formatted string representing all indices in the hash key. |
217 | 265 | * |
218 | 266 | * @param hashcode the encoded long key |
219 | 267 | * @param length the number of indices to extract |
220 | 268 | * @return a string representation of the indices |
221 | 269 | */ |
222 | | - public String getString(long hashcode, int length){ |
| 270 | + public String getString(long hashcode, int length) { |
223 | 271 | StringBuilder str = new StringBuilder(); |
224 | | - for(int loop = 0; loop <length; loop++){ |
| 272 | + for (int loop = 0; loop < length; loop++) { |
225 | 273 | str.append(String.format("%5d", this.getIndex(hashcode, loop))); |
226 | 274 | } |
227 | 275 | return str.toString(); |
|
0 commit comments