diff --git a/src/fr/neatmonster/nocheatplus/utilities/ds/CoordMap.java b/src/fr/neatmonster/nocheatplus/utilities/ds/CoordMap.java index 79924ea6..76ccd675 100644 --- a/src/fr/neatmonster/nocheatplus/utilities/ds/CoordMap.java +++ b/src/fr/neatmonster/nocheatplus/utilities/ds/CoordMap.java @@ -1,162 +1,320 @@ package fr.neatmonster.nocheatplus.utilities.ds; import java.util.Arrays; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.NoSuchElementException; /** - * Map int coordinates to values (just for fun). Intended for Minecraft coordinates, probably not for too high values.
- * This implementation is not thread safe, though changing values and get/contains should work if the map stays unchanged. + * Map int coordinates to values (just for fun). Intended for Minecraft + * coordinates, probably not for too high values.
+ * This implementation is not thread safe, though changing values and + * get/contains should work if the map stays unchanged. + * * @author mc_dev - * + * * @param */ public class CoordMap { - - private static final int p1 = 73856093; - private static final int p2 = 19349663; - private static final int p3 = 83492791; - - private static final int getHash(final int x, final int y, final int z) { - return p1 * x ^ p2 * y ^ p3 * z; - } - - private static final class Entry{ - protected final int x; - protected final int y; - protected final int z; - protected V value; - protected final int hash; - public Entry(final int x, final int y, final int z, final V value, final int hash){ - this.x = x; - this.y = y; - this.z = z; - this.value = value; - this.hash = hash; - } - } - - // Core data. - private final float loadFactor; - private List>[] entries; - /** Current size. */ - private int size = 0; - - public CoordMap(){ - this(10, 0.75f); - } - - public CoordMap(final int initialCapacity){ - this(initialCapacity, 0.75f); - } - - /** - * - * @param initialCapacity Initial internal array size.
- * TODO: change to expected number of elements (len = cap/load). - * @param loadFactor - */ - @SuppressWarnings("unchecked") - public CoordMap(final int initialCapacity, float loadFactor){ - this.loadFactor = loadFactor; - entries = new List[initialCapacity]; - } - - /** - * Check if the map contains a value for the given coordinates.
- * NOTE: Delegates to get, use get for fastest checks. - * @param x - * @param y - * @param z - * @return - */ - public final boolean contains(final int x, final int y, final int z){ - return get(x,y,z) != null; - } - - /** - * Get the value if there is a mapping for the given coordinates. - * @param x - * @param y - * @param z - * @return - */ - public final V get(final int x, final int y, final int z){ - final int hash = getHash(x, y, z); - final int slot = Math.abs(hash) % entries.length; - final List> bucket = entries[slot]; - if (bucket == null) return null;; - for (final Entry entry : bucket){ - if (hash == entry.hash && x == entry.x && z == entry.z && y == entry.y) return entry.value; - } - return null; - } - - /** - * Add value with the coordinates + hash from the last contains call. - * @param value - * @return If a value was replaced. - */ - public final boolean put(final int x, final int y, final int z, final V value){ - final int hash = getHash(x, y, z); - final int absHash = Math.abs(hash); - int slot = absHash % entries.length; - List> bucket = entries[slot]; - if (bucket != null){ - for (final Entry entry : bucket){ - if (hash == entry.hash && x == entry.x && y == entry.z && z == entry.y){ - entry.value = value; - return true; - } - } - } - else if (size + 1 > entries.length * loadFactor){ - resize(size + 1); - slot = absHash % entries.length; - bucket = entries[slot]; - } - if (bucket == null) { - // TODO: use array list ? - bucket = new LinkedList>(); - entries[slot] = bucket; - } - entries[slot] = bucket; - bucket.add(new Entry(x, y, z, value, hash)); - size ++; - return false; - } - - private final void resize(final int size) { - // TODO: other capacity? - final int newCapacity = (int) ((size + 4) / loadFactor); - @SuppressWarnings("unchecked") - final List>[] newEntries = new List[newCapacity]; - for (int oldSlot = 0; oldSlot < entries.length; oldSlot++){ - final List> oldBucket = entries[oldSlot]; - if (oldBucket != null){ - for (final Entry entry : oldBucket){ - final int newSlot = Math.abs(entry.hash) % newCapacity; - List> newBucket = newEntries[oldSlot]; - if (newBucket == null){ - newBucket = new LinkedList>(); - newEntries[newSlot] = newBucket; - } - newBucket.add(entry); - } - oldBucket.clear(); - } - entries[oldSlot] = null; - } - entries = newEntries; - } - public final int size(){ - return size; - } - - public void clear(){ - size = 0; - Arrays.fill(entries, null); - // TODO: resize ? - } + private static final int p1 = 73856093; + private static final int p2 = 19349663; + private static final int p3 = 83492791; + + private static final int getHash(final int x, final int y, final int z) { + return p1 * x ^ p2 * y ^ p3 * z; + } + + public static final class Entry { + protected final int x; + protected final int y; + protected final int z; + protected V value; + protected final int hash; + + public Entry(final int x, final int y, final int z, final V value, final int hash) { + this.x = x; + this.y = y; + this.z = z; + this.value = value; + this.hash = hash; + } + + public final int getX(){ + return x; + } + + public final int getY(){ + return y; + } + + public final int getZ(){ + return z; + } + + public final V getValue(){ + return value; + } + } + + /** + * Does NOT throw anything, not the least thread safe. + * + * @author mc_dev + * + * @param + */ + public static final class CoordMapIterator implements Iterator> { + private final CoordMap map; + private final List>[] entries; + + /** Current search position. */ + private int slot = 0; + /** Current search position. */ + private int index = 0; + + /** Last found position. */ + private int slotLast = -1; + /** Last found position. */ + private int indexLast = -1; + + protected CoordMapIterator(final CoordMap map) { + this.map = map; + entries = map.entries; + } + + @Override + public final boolean hasNext() { + // Also set index and slot to next found element. + while (slot < entries.length) { + final List> bucket = entries[slot]; + if (bucket == null){ + slot ++; + index = 0; + } + else { + if (index < bucket.size()) return true; + else { + // Not found, reset. + slot ++; + index = 0; + } + } + } + return false; + } + + @Override + public final Entry next() { + while (slot < entries.length) { + final List> bucket = entries[slot]; + if (bucket == null){ + slot ++; + index = 0; + } + else { + final int size = bucket.size(); + if (index < size) { + final Entry res = bucket.get(index); + slotLast = slot; + indexLast = index; + index++; + if (index == size) { + index = 0; + slot++; + } + return res; + } + else{ + // TODO: inconsistent, could be empty though. + slot++; + index = 0; + } + } + } + throw new NoSuchElementException(); + } + + @Override + public final void remove() { + if (slotLast == -1){ + // Next had not been called at all, + // or remove has been called several times. + return; + } + final List> bucket = entries[slotLast]; + bucket.remove(indexLast); + if (bucket.isEmpty()) entries[slotLast] = null; + else if (slotLast == slot) index --; + map.size--; + slotLast = indexLast = -1; + } + } + + // Core data. + private final float loadFactor; + private List>[] entries; + /** Current size. */ + private int size = 0; + + public CoordMap() { + this(10, 0.75f); + } + + public CoordMap(final int initialCapacity) { + this(initialCapacity, 0.75f); + } + + /** + * + * @param initialCapacity + * Initial internal array size.
+ * TODO: change to expected number of elements (len = cap/load). + * @param loadFactor + */ + @SuppressWarnings("unchecked") + public CoordMap(final int initialCapacity, float loadFactor) { + this.loadFactor = loadFactor; + entries = new List[initialCapacity]; + } + + /** + * Check if the map contains a value for the given coordinates.
+ * NOTE: Delegates to get, use get for fastest checks. + * + * @param x + * @param y + * @param z + * @return + */ + public final boolean contains(final int x, final int y, final int z) { + return get(x, y, z) != null; + } + + /** + * Get the value if there is a mapping for the given coordinates. + * + * @param x + * @param y + * @param z + * @return + */ + public final V get(final int x, final int y, final int z) { + final int hash = getHash(x, y, z); + final int slot = Math.abs(hash) % entries.length; + final List> bucket = entries[slot]; + if (bucket == null) return null; + ; + for (final Entry entry : bucket) { + if (hash == entry.hash && x == entry.x && z == entry.z && y == entry.y) return entry.value; + } + return null; + } + + /** + * Add value with the coordinates + hash from the last contains call. + * + * @param value + * @return If a value was replaced. + */ + public final boolean put(final int x, final int y, final int z, final V value) + { + final int hash = getHash(x, y, z); + final int absHash = Math.abs(hash); + int slot = absHash % entries.length; + List> bucket = entries[slot]; + if (bucket != null) { + for (final Entry entry : bucket) { + if (hash == entry.hash && x == entry.x && z == entry.z && y == entry.y) { + entry.value = value; + return true; + } + } + } else if (size + 1 > entries.length * loadFactor) { + resize(size + 1); + slot = absHash % entries.length; + bucket = entries[slot]; + } + if (bucket == null) { + // TODO: use array list ? + bucket = new LinkedList>(); + entries[slot] = bucket; + } + bucket.add(new Entry(x, y, z, value, hash)); + size++; + return false; + } + + /** + * Remove an entry. + * + * @param x + * @param y + * @param z + * @return + */ + public final V remove(final int x, final int y, final int z) { + final int hash = getHash(x, y, z); + final int absHash = Math.abs(hash); + int slot = absHash % entries.length; + final List> bucket = entries[slot]; + if (bucket == null) return null; + else { + for (int i = 0; i < bucket.size(); i++) { + final Entry entry = bucket.get(i); + if (entry.hash == hash && x == entry.x && z == entry.z && y == entry.y) { + bucket.remove(entry); + if (bucket.isEmpty()) entries[slot] = null; + size--; + return entry.value; + } + } + return null; + } + } + + private final void resize(final int size) { + // TODO: other capacity / allow to set strategy [also for reducing for long time use] + final int newCapacity = Math.max((int) ((size + 4) / loadFactor), entries.length + entries.length / 4); + @SuppressWarnings("unchecked") + final List>[] newEntries = new List[newCapacity]; + int used = -1; // Fill old buckets to fornt of old array. + for (int oldSlot = 0; oldSlot < entries.length; oldSlot++) { + final List> oldBucket = entries[oldSlot]; + if (oldBucket == null) continue; + for (final Entry entry : oldBucket) { + final int newSlot = Math.abs(entry.hash) % newCapacity; + List> newBucket = newEntries[newSlot]; + if (newBucket == null) { + if (used < 0) newBucket = new LinkedList>(); + else{ + newBucket = entries[used]; + entries[used] = null; + used--; + } + newEntries[newSlot] = newBucket; + } + newBucket.add(entry); + } + oldBucket.clear(); + entries[oldSlot] = null; + entries[++used] = oldBucket; + } + entries = newEntries; + } + + public final int size() { + return size; + } + + public void clear() { + size = 0; + Arrays.fill(entries, null); + // TODO: resize ? + } + + public final Iterator> iterator(){ + return new CoordMapIterator(this); + } }