001package squidpony.squidmath; 002 003import squidpony.ArrayTools; 004 005import java.util.Collection; 006import java.util.Iterator; 007import java.util.SortedSet; 008 009/** 010 * An ordered multi-directional "map" that keeps 1 or more keysets organized so you can look up one key given a key in 011 * another keyset, and do the same for the indices of keys. Does not have generic type parameters, which is needed 012 * because we handle arbitrary counts of keysets, and each keyset could have its own type. You can use most of the 013 * normal Map methods here, though they often need an int as their first argument, {@code which}, that specifies which 014 * keyset the method applies to. For example, {@link #contains(int, Object)} checks for the presence of the second 015 * parameter in the keyset specified by the first parameter. Adding items to a MultiKey uses {@link #put(Object...)}, 016 * and that does not take a {@code which} parameter because it needs to add an item to every keyset to succeed, and will 017 * do nothing if the array or varargs passed to it has a different length than the {@link #keyCount} of the MultiKey. 018 * Created by Tommy Ettinger on 10/23/2016. 019 */ 020@SuppressWarnings("unchecked") 021public class MultiKey { 022 public final int keyCount; 023 private Arrangement[] keys; 024 025 /** 026 * Constructs an empty MultiKey. 027 */ 028 public MultiKey() 029 { 030 this(3, 16, 0.5f); 031 } 032 033 /** 034 * Constructs a MultiKey with the expected number of indices to hold (the number of items in each keyset is always 035 * the same, and this will be more efficient if expected is greater than that number). 036 * @param expected how many items this should be ready to hold; can resize if needed 037 */ 038 public MultiKey(int keyCount, int expected) 039 { 040 this(keyCount, expected, 0.5f); 041 } 042 043 /** 044 * Constructs a MultiKey with the expected number of indices to hold (the number of items in each keyset is always 045 * the same, and this will be more efficient if expected is greater than that number) and the load factor to use, 046 * between 0.1f and 0.8f usually (using load factors higher than 0.8f can cause problems). 047 * @param expected the amount of indices (the number of items in each keyset is always the same) this should hold 048 * @param f the load factor, probably between 0.1f and 0.8f 049 */ 050 public MultiKey(int keyCount, int expected, float f) 051 { 052 this.keyCount = keyCount; 053 keys = new Arrangement[keyCount]; 054 for (int i = 0; i < keyCount; i++) { 055 keys[i] = new Arrangement(expected, f); 056 } 057 } 058 059 /** 060 * Constructs a MultiKey from a Collection of Iterables that will be processed in tandem, adding a unique item from 061 * each keyset in keysets if and only if it can also add a unique item from all other keysets, otherwise skipping 062 * that iteration in each Iterable. 063 * @param keysets a Collection of Iterable data structures, each containing items that should all be unique 064 */ 065 public MultiKey(Collection<Iterable> keysets) { 066 if (keysets == null) { 067 keyCount = 0; 068 keys = new Arrangement[0]; 069 } else { 070 keyCount = keysets.size(); 071 keys = new Arrangement[keyCount]; 072 for (int i = 0; i < keyCount; i++) { 073 keys[i] = new Arrangement(); 074 } 075 putAll(keysets); 076 } 077 } 078 079 public MultiKey(MultiKey other) 080 { 081 if(other == null) 082 { 083 keyCount = 0; 084 keys = new Arrangement[0]; 085 } 086 else 087 { 088 keyCount = other.keyCount; 089 keys = new Arrangement[keyCount]; 090 for (int i = 0; i < keyCount; i++) { 091 keys[i] = new Arrangement(other.keys[i]); 092 } 093 } 094 } 095 096 public MultiKey(Arrangement[] keysets) 097 { 098 if(keysets == null) 099 { 100 keyCount = 0; 101 keys = new Arrangement[0]; 102 } 103 else 104 { 105 keyCount = keysets.length; 106 keys = new Arrangement[keyCount]; 107 int minLength = Integer.MAX_VALUE; 108 for (int k = 0; k < keyCount; k++) { 109 if(keysets[k] == null) 110 return; 111 minLength = Math.min(minLength, keysets[k].size); 112 } 113 for (int k = 0; k < keyCount; k++) { 114 keys[k] = keysets[k].take(minLength); 115 } 116 } 117 } 118 /** 119 * Returns true if this contains key in the keyset specified by which. 120 * @param which which keyset to check in 121 * @param key the key to check the presence of 122 * @return true if key is present in the keyset at which; false otherwise 123 */ 124 public boolean contains(int which, Object key) { 125 if(which >= 0 && which < keyCount) 126 return keys[which].containsKey(key); 127 return false; 128 } 129 130 /** 131 * Returns true if index is between 0 (inclusive) and {@link #size()} (exclusive), or false otherwise. 132 * @param index the index to check 133 * @return true if index is a valid index in the ordering of this MultiKey 134 */ 135 public boolean containsIndex(int index) { 136 if(keyCount > 0) return keys[0].containsValue(index); 137 return false; 138 } 139 140 /** 141 * Given an index of a keyset (which) and an Object key, finds the position in the ordering that key has in the 142 * keyset at which, or -1 if key is not present. 143 * <br> 144 * Unlike {@link java.util.List#indexOf(Object)}, this runs in constant time. 145 * @param which which keyset to check in 146 * @param key the key to find the position of 147 * @return the int index of key in the ordering, or -1 if it is not present 148 */ 149 public int indexOf(int which, Object key) 150 { 151 if(which >= 0 && which < keyCount) 152 return keys[which].getInt(key); 153 return -1; 154 } 155 156 /** 157 * Given an index of a keyset (which) and an int index, finds the associated key in the keyset specified by which 158 * (using index as a point in the ordering). 159 * @param which which keyset to get from 160 * @param index an int index into this MultiKey 161 * @return the key (in the keyset found by which) with index for its position in the ordering, or null if index or which was invalid 162 */ 163 public Object getAt(int which, int index) 164 { 165 if(which >= 0 && which < keyCount) 166 return keys[which].keyAt(index); 167 return null; 168 } 169 170 /** 171 * Given an int index, finds the associated keys at all keysets (using index as a point in the ordering) and returns 172 * them as a newly-allocated Object array. 173 * @param index an int index into this MultiKey 174 * @return the array of keys with index for their position in the ordering, or an array of null if index was invalid 175 * @see #getAllAt(int, Object[]) getAllAt can avoid allocating a new array each time 176 */ 177 public Object[] getAllAt(int index) { 178 Object[] os = new Object[keyCount]; 179 for (int i = 0; i < keyCount; i++) { 180 os[i] = keys[i].keyAt(index); 181 } 182 return os; 183 } 184 185 /** 186 * Given an int index and an Object array to reuse, finds the associated keys at all keysets (using index as a point 187 * in the ordering) and fills into with those keys, up to keyCount items. 188 * @param index an int index into this MultiKey 189 * @param into an Object array to reuse and fill with items; will be returned as-is if smaller than keyCount 190 * @return the array of keys with index for their position in the ordering, or an array of null if index was invalid 191 */ 192 public Object[] getAllAt(int index, Object[] into) { 193 if (into != null && into.length >= keyCount) { 194 for (int i = 0; i < keyCount; i++) { 195 into[i] = keys[i].keyAt(index); 196 } 197 } 198 return into; 199 } 200 201 /** 202 * Given an index of the keyset to look up a key in (lookingUp), an index of the keyset to get from (getting), and 203 * an Object key to look up (key), finds the Object key in the keyset specified by getting that is associated with 204 * key in the keyset specified by lookingUp. key and the returned value will be at the same point in the ordering. 205 * @param lookingUp which keyset to look up the {@code key} parameter in 206 * @param getting which keyset to get a value from 207 * @param key an object to use as a key, which should be the right type for the keyset at lookingUp 208 * @return the object from getting's keyset that is associated with key, or null if key was not present 209 */ 210 public Object getFrom(int lookingUp, int getting, Object key) 211 { 212 if(lookingUp >= 0 && lookingUp < keyCount && getting >= 0 && getting < keyCount) 213 return keys[getting].keyAt(keys[lookingUp].getInt(key)); 214 return null; 215 } 216 217 /** 218 * Gets a random key from the keyset specified by which using the given IRNG. 219 * @param which which keyset to get a random key from 220 * @param random generates a random index to get a key with 221 * @return a randomly chosen key from the keyset specified, or null if this is empty 222 */ 223 public Object randomKey(int which, IRNG random) 224 { 225 if(which >= 0 && which < keyCount) 226 return keys[which].randomKey(random); 227 return null; 228 } 229 /** 230 * Gets a random key from a random keyset in this MultiKey using the given IRNG. 231 * @param random generates a random keyset index and random item index, to get a random key 232 * @return a randomly chosen Object key from possibly any keyset in this, or null if this is empty 233 */ 234 public Object randomKey(IRNG random) 235 { 236 if(keyCount > 0) 237 return keys[random.nextInt(keyCount)].randomKey(random); 238 return null; 239 } 240 241 /** 242 * In the keyset specified by {@code which}, changes an existing key, {@code past}, to another key, {@code future}, 243 * if past exists in that keyset and future does not yet exist in that keyset. This will retain past's point in the 244 * ordering for future, so the associated other key(s) will still be associated in the same way. 245 * @param which which keyset to alter the items in 246 * @param past a key, that must exist in the keyset specified by which, and will be changed 247 * @param future a key, that cannot currently exist in the keyset specified by which, but will if this succeeds 248 * @return this for chaining 249 */ 250 public MultiKey alter(int which, Object past, Object future) 251 { 252 if(which >= 0 && which < keyCount && keys[which].containsKey(past) && !keys[which].containsKey(future)) 253 keys[which].alter(past, future); 254 return this; 255 } 256 /** 257 * In the keyset specified by {@code which}, changes the key at {@code index} to another key, {@code future}, if 258 * index is valid and future does not yet exist in that keyset. The position in the ordering for future will be the 259 * same as index, and the same as the key this replaced, if this succeeds, so the other key(s) at that position will 260 * still be associated in the same way. 261 * @param which which keyset to alter the items in 262 * @param index a position in the ordering to change; must be at least 0 and less than {@link #size()} 263 * @param future a key, that cannot currently exist in the keyset specified by which, but will if this succeeds 264 * @return this for chaining 265 */ 266 public MultiKey alterAt(int which, int index, Object future) 267 { 268 if(which >= 0 && which < keyCount && !keys[which].containsKey(future) && index >= 0 && index < keys[which].size) 269 keys[which].alter(keys[which].keyAt(index), future); 270 return this; 271 } 272 273 /** 274 * Adds a key to each keyset at the same point in the ordering (the end) of this MultiKey. The length of k must 275 * match the keyCount of this MultiKey, and the nth item in k will go into the nth keyset. No item in k can be 276 * present in the matching keyset in this before this is called. If you want to change or update an existing key, 277 * use {@link #alter(int, Object, Object)} or {@link #alterAt(int, int, Object)}. 278 * @param k an array or varargs of keys to add after the last index of this MultiKey; length must equal keyCount 279 * @return true if this collection changed as a result of this call 280 */ 281 public boolean put(Object... k) 282 { 283 if(k != null && keyCount > 0 && k.length == keyCount) { 284 for (int i = 0; i < keyCount; i++) { 285 if(keys[i].containsKey(k[i])) 286 return false; 287 } 288 for (int i = 0; i < keyCount; i++) { 289 keys[i].add(k[i]); 290 } 291 return true; 292 } 293 return false; 294 } 295 296 /** 297 * Goes through all Iterable items in {@code k} and adds their unique items into their corresponding keyset at the 298 * end. If an item from the nth Iterable is already present in the nth keyset in this when this would add one, this 299 * will not put any keys at that point in the iteration order, and will place the next unique group of items it 300 * finds in the arguments at that position instead. 301 * @param k a Collection of Iterable s of keys to add to their respective keysets; should all be unique (like a Set) 302 * @return true if this collection changed as a result of this call 303 */ 304 public boolean putAll(Collection<Iterable> k) 305 { 306 if(k == null || k.size() != keyCount) return false; 307 boolean changed = false; 308 Iterator[] its = new Iterator[keyCount]; 309 int idx = 0; 310 for (Iterable it : k) { 311 its[idx++] = it.iterator(); 312 } 313 Object[] os = new Object[keyCount]; 314 while (true) 315 { 316 for (int i = 0; i < keyCount; i++) { 317 if(!its[i].hasNext()) 318 return changed; 319 os[i] = its[i].next(); 320 } 321 changed = put(os) || changed; 322 } 323 } 324 /** 325 * Puts all unique keys in {@code other} into this MultiKey, respecting other's ordering. If a key in other is 326 * already present when this would add one, this will not put the keys at that point in the iteration 327 * order, and will place the next unique items it finds in other at that position instead. 328 * @param other another MultiKey collection with the same keyCount 329 * @return true if this collection changed as a result of this call 330 */ 331 public boolean putAll(MultiKey other) 332 { 333 if(other == null || other.keyCount != keyCount) return false; 334 boolean changed = false; 335 int sz = other.size(); 336 Object[] os = new Object[keyCount]; 337 for (int i = 0; i < sz; i++) { 338 changed = put(other.getAllAt(i, os)) || changed; 339 } 340 return changed; 341 } 342 343 /** 344 * Adds a key to each keyset at the given index in the ordering of this MultiKey. The length of k must 345 * match the keyCount of this MultiKey, and the nth item in k will go into the nth keyset. No item in k can be 346 * present in the matching keyset in this before this is called. If you want to change or update an existing key, 347 * use {@link #alter(int, Object, Object)} or {@link #alterAt(int, int, Object)}. 348 * @param k an array or varargs of keys to add after the last index of this MultiKey; length must equal keyCount 349 * @return true if this collection changed as a result of this call 350 */ 351 public boolean putAt(int index, Object... k) 352 { 353 if(k != null && keyCount > 0 && k.length == keyCount) { 354 for (int i = 0; i < keyCount; i++) { 355 if(keys[i].containsKey(k[i])) 356 return false; 357 } 358 for (int i = 0; i < keyCount; i++) { 359 keys[i].addAt(index, k[i]); 360 } 361 return true; 362 } 363 return false; 364 } 365 366 /** 367 * Removes a given Object key from the keyset specified by which, if {@code removing} exists in that keyset, and 368 * also removes any keys associated with its point in the ordering. 369 * @param which which keyset to remove the item from; if {@code removing} isn't in that keyset, this does nothing 370 * @param removing the key to remove 371 * @return this for chaining 372 */ 373 public MultiKey remove(int which, Object removing) 374 { 375 if(which >= 0 && which < keyCount) { 376 int i = keys[which].removeInt(removing); 377 if(i >= 0) { 378 for (int j = 0; j < keyCount; j++) { 379 if(j != which) 380 keys[j].removeAt(i); 381 } 382 } 383 } 384 return this; 385 } 386 387 /** 388 * Removes a given point in the ordering, if {@code index} is at least 0 and less than {@link #size()}. 389 * @param index the position in the ordering to remove 390 * @return this for chaining 391 */ 392 public MultiKey removeAt(int index) 393 { 394 if(index >= 0 && keyCount > 0 && index < keys[0].size) { 395 for (int i = 0; i < keyCount; i++) { 396 keys[i].removeAt(index); 397 } 398 } 399 return this; 400 } 401 402 /** 403 * Reorders this MultiKey using {@code ordering}, which have the same length as this MultiKey's {@link #size()} 404 * and can be generated with {@link ArrayTools#range(int)} (which, if applied, would produce no 405 * change to the current ordering), {@link IRNG#randomOrdering(int)} (which gives a random ordering, and if 406 * applied immediately would be the same as calling {@link #shuffle(IRNG)}), or made in some other way. If you 407 * already have an ordering and want to make a different ordering that can undo the change, you can use 408 * {@link ArrayTools#invertOrdering(int[])} called on the original ordering. 409 * @param ordering an int array or vararg that should contain each int from 0 to {@link #size()} (or less) 410 * @return this for chaining 411 */ 412 public MultiKey reorder(int... ordering) 413 { 414 if(ordering != null) { 415 for (int i = 0; i < keyCount; i++) { 416 keys[i].reorder(ordering); 417 } 418 } 419 return this; 420 } 421 422 /** 423 * Generates a random ordering with rng and applies the same ordering to all kinds of keys this has; they will 424 * maintain their current association to other keys but their ordering/indices will change. 425 * @param rng an IRNG to produce the random ordering this will use 426 * @return this for chaining 427 */ 428 public MultiKey shuffle(IRNG rng) 429 { 430 if(keyCount > 0) { 431 int[] ordering = rng.randomOrdering(keys[0].size); 432 for (int i = 0; i < keyCount; i++) { 433 keys[i].reorder(ordering); 434 } 435 } 436 return this; 437 } 438 439 /** 440 * Creates a new iterator over the keys this holds in the keyset specified by which. This can be problematic for 441 * garbage collection if called very frequently; it may be better to access items by index (which also lets you 442 * access other keys associated with that index) using {@link #getAt(int, int)} in a for(int i=0...) loop. 443 * @return a newly-created iterator over this MultiKey's keys in the specified keyset 444 */ 445 public Iterator iterator(int which) 446 { 447 if(which >= 0 && which < keyCount) 448 return keys[which].iterator(); 449 return null; 450 } 451 452 /** 453 * Gets and caches the keys in the keyset specified by which as a Collection that implements SortedSet (and so also 454 * implements Set). 455 * @param which which keyset to get as a separate value 456 * @return the keys from the keyset specified by which, as a SortedSet 457 */ 458 public SortedSet getSet(int which) { 459 if(which >= 0 && which < keyCount) 460 return keys[which].keySet(); 461 return null; 462 } 463 464 /** 465 * To be called sparingly, since this allocates a new OrderedSet instead of reusing one. 466 * @param which which keyset to get as a separate value 467 * @return the keys from the keyset specified by which, as an OrderedSet 468 */ 469 public OrderedSet getOrderedSet(int which) { 470 if(which >= 0 && which < keyCount) 471 return keys[which].keysAsOrderedSet(); 472 return null; 473 474 } 475 476 public int keyCount() { 477 return keyCount; 478 } 479 public int valueCount() { 480 return 0; 481 } 482 483 public int size() { 484 return (keyCount > 0) ? keys[0].size : 0; 485 } 486 487 public boolean isEmpty() 488 { 489 return keyCount > 0 && keys[0].size > 0; 490 } 491 492}