diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/map/AbstractCoordHashMap.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/map/AbstractCoordHashMap.java index 8892f63d..f04c797e 100644 --- a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/map/AbstractCoordHashMap.java +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/map/AbstractCoordHashMap.java @@ -92,6 +92,18 @@ public abstract class AbstractCoordHashMap bucket = entries[slot]; @@ -100,14 +112,14 @@ public abstract class AbstractCoordHashMap entries.length * loadFactor) { @@ -131,7 +144,7 @@ public abstract class AbstractCoordHashMap { * Add value with the coordinates + hash from the last contains call. * * @param value - * @return If a value was replaced. + * @return The replaced value. If no value is present, null is returned. */ - public boolean put(final int x, final int y, final int z, final V value); + public V put(final int x, final int y, final int z, final V value); /** * Remove an entry. @@ -80,7 +80,10 @@ public interface CoordMap { * Iterator over all elements (default order to be specified). *
* There is no guarantee that any checks for concurrent modification are - * performed. + * performed. The iterator might not follow standard behavior (current + * implementations would throw NoSuchElementException on next(), but + * remove() might remove the first element if next() has not yet been + * called, or do nothing in other cases. * * @return */ diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/map/LinkedCoordHashMap.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/map/LinkedCoordHashMap.java index 877f6a9b..cdf4638b 100644 --- a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/map/LinkedCoordHashMap.java +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/map/LinkedCoordHashMap.java @@ -1,6 +1,7 @@ package fr.neatmonster.nocheatplus.utilities.ds.map; import java.util.Iterator; +import java.util.NoSuchElementException; /** * Intended for Minecraft coordinates, probably not for too high values.
@@ -8,7 +9,9 @@ import java.util.Iterator; * get/contains should work if the map stays unchanged. *
* Linked hash map implementation of CoordMap, allowing for insertion/access - * order. Default order is the order of insertion. + * order. Default order is the order of insertion. This implementation does not + * imitate the LinkedHashMap behavior [may be adapted to be similar, if + * desired], instead methods are provided for manipulating the order at will. * * @author asofold * @@ -16,29 +19,182 @@ import java.util.Iterator; */ public class LinkedCoordHashMap extends AbstractCoordHashMap> { - // TODO: Implement linked structure + iterator (+reversed iteration). + // TODO: Add default order for get/put? + // TODO: Tests. + + /** + * Where to move an entry. + * @author asofold + * + */ + public static enum MoveOrder { + FRONT, + NOT, + END + } public static class LinkedHashEntry extends fr.neatmonster.nocheatplus.utilities.ds.map.AbstractCoordHashMap.HashEntry { + protected LinkedHashEntry previous = null; + protected LinkedHashEntry next = null; + public LinkedHashEntry(int x, int y, int z, V value, int hash) { - // TODO: linked ... super(x, y, z, value, hash); - throw new IllegalStateException("Not yet implemented."); // TODO: Implement. } } - public boolean isAccessOrder() { - throw new IllegalStateException("Not yet implemented."); // TODO: Implement. + public static class LinkedHashIterator implements Iterator> { + + private final LinkedCoordHashMap map; + + private LinkedHashEntry current = null; + private LinkedHashEntry next; + + private boolean reverse; + + protected LinkedHashIterator(LinkedCoordHashMap map, boolean reverse) { + this.map = map; + this.reverse = reverse; + next = reverse ? map.lastEntry : map.firstEntry; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public LinkedHashEntry next() { + if (next == null) { + throw new NoSuchElementException(); + } + current = next; + next = reverse ? next.previous : next.next; + return current; + } + + @Override + public void remove() { + if (current != null) { + // TODO: more efficient version ? + map.remove(current.x, current.y, current.z); + current = null; + } + } + } - public boolean isInsertionOrder() { - throw new IllegalStateException("Not yet implemented."); // TODO: Implement. + protected LinkedHashEntry firstEntry = null; + protected LinkedHashEntry lastEntry = null; + + public LinkedCoordHashMap() { + super(); + } + + public LinkedCoordHashMap(int initialCapacity) { + super(initialCapacity); + } + + public LinkedCoordHashMap(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + } + + /** + * Move an entry to the start of the linked structure. + * + * @param x + * @param y + * @param z + * @return The value that was stored for the given coordinates. If no entry + * is present, null is returned. + */ + public V moveToFront(final int x, final int y, final int z) { + final LinkedHashEntry entry = getEntry(x, y, z); + if (entry == null) { + return null; + } + removeEntry(entry); + setFirst(entry); + return entry.value; + } + + /** + * Move an entry to the end of the linked structure. + * + * @param x + * @param y + * @param z + * @return The value that was stored for the given coordinates. If no entry + * is present, null is returned. + */ + public V moveToEnd(final int x, final int y, final int z) { + final LinkedHashEntry entry = getEntry(x, y, z); + if (entry == null) { + return null; + } + removeEntry(entry); + setLast(entry); + return entry.value; + } + + /** + * Convenience method to specify moving based on MoveOrder. Note that + * MoveOrder.NOT leads to super.get being called, in order to be bale to + * return a present value. + * + * @param x + * @param y + * @param z + * @param order + * @return The value that was stored for the given coordinates. If no entry + * is present, null is returned. + */ + public V move(final int x, final int y, final int z, final MoveOrder order) { + switch (order) { + case END: + return moveToEnd(x, y, z); + case FRONT: + return moveToFront(x, y, z); + default: + break; + } + // Ensure no changes. + return super.get(x, y, z); + } + + /** + * Convenience method to control where the resulting entry is put to (front + * or end). + * + * @param x + * @param y + * @param z + * @param value + * @param moveToEnd + * @return + */ + public V put(final int x, final int y, final int z, final V value, final MoveOrder order) { + // TODO: Optimized. + final V previousValue = super.put(x, y, z, value); + if (order == MoveOrder.END) { + moveToEnd(x, y, z); + } + return previousValue; + } + + public V get(final int x, final int y, final int z, final MoveOrder order) { + // TODO: Optimized. + final V value = super.get(x, y ,z); + if (order != MoveOrder.NOT) { + move(x, y, z, order); + } + return value; } @Override - public Iterator> iterator() { - throw new IllegalStateException("Not yet implemented."); // TODO: Implement. + public LinkedHashIterator iterator() { + return new LinkedHashIterator(this, false); } /** @@ -46,15 +202,63 @@ public class LinkedCoordHashMap extends AbstractCoordHashMap> iterator(boolean reversed) { - throw new IllegalStateException("Not yet implemented."); // TODO: Implement + public LinkedHashIterator iterator(boolean reversed) { + return new LinkedHashIterator(this, reversed); } @Override protected LinkedHashEntry newEntry(int x, int y, int z, V value, int hash) { - throw new IllegalStateException("Not yet implemented."); // TODO: Implement + LinkedHashEntry entry = new LinkedHashEntry(x, y, z, value, hash); + // Always put in first. + setFirst(entry); + return entry; } - + /** + * Insert entry as the first element. Assumes the entry not to be linked. + * + * @param entry + */ + private void setFirst(LinkedHashEntry entry) { + if (this.firstEntry == null) { + this.firstEntry = this.lastEntry = entry; + } else { + entry.next = this.firstEntry; + this.firstEntry.previous = entry; + this.firstEntry = entry; + } + } + + /** + * Insert entry as the last element. Assumes the entry not to be linked. + * + * @param entry + */ + private void setLast(LinkedHashEntry entry) { + if (this.firstEntry == null) { + this.firstEntry = this.lastEntry = entry; + } else { + entry.previous = this.lastEntry; + this.lastEntry.next = entry; + this.lastEntry = entry; + } + } + + @Override + protected void removeEntry(LinkedHashEntry entry) { + // Just unlink. + if (entry.previous == null) { + this.firstEntry = entry.next; + } else { + entry.previous.next = entry.next; + entry.previous = null; + } + if (entry.next == null) { + this.lastEntry = entry.previous; + } else { + entry.next.previous = entry.previous; + entry.next = null; + } + } } diff --git a/NCPCommons/src/test/java/fr/neatmonster/nocheatplus/test/TestCoordMap.java b/NCPCommons/src/test/java/fr/neatmonster/nocheatplus/test/TestCoordMap.java index 1a4bc03a..bcae3582 100644 --- a/NCPCommons/src/test/java/fr/neatmonster/nocheatplus/test/TestCoordMap.java +++ b/NCPCommons/src/test/java/fr/neatmonster/nocheatplus/test/TestCoordMap.java @@ -2,6 +2,7 @@ package fr.neatmonster.nocheatplus.test; import static org.junit.Assert.fail; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -15,272 +16,336 @@ import fr.neatmonster.nocheatplus.utilities.build.BuildParameters; import fr.neatmonster.nocheatplus.utilities.ds.map.CoordHashMap; import fr.neatmonster.nocheatplus.utilities.ds.map.CoordMap; import fr.neatmonster.nocheatplus.utilities.ds.map.CoordMap.Entry; +import fr.neatmonster.nocheatplus.utilities.ds.map.LinkedCoordHashMap; public class TestCoordMap { - - public static class Pos{ - 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 final int x; - public final int y; - public final int z; - private final int hash; - public Pos(int x, int y, int z){ - this.x = x; - this.y = y; - this.z = z; - this.hash = getHash(x, y, z); - } - @Override - public boolean equals(Object obj) { - if (obj instanceof Pos){ - Pos other = (Pos) obj; - return other.hash == hash && other.x == x && other.y == y && other.z == z; - } - else return false; - } - @Override - public int hashCode() { - return hash; - } - } - - public int[][] getRandomCoords(int n, int max, Random random) { - final int [][] coords = new int[n][3]; - for (int i = 0; i < n; i++){ - for (int j = 0; j < 3 ; j++){ - coords[i][j] = random.nextInt(2*max) - max; - } - } - return coords; - } - - public int[][] getUniqueRandomCoords(int n, int max, Random random) { - Set present = new HashSet(); - int failures = 0; - final int [][] coords = new int[n][3]; - for (int i = 0; i < n; i++){ - boolean unique = false; - Pos pos = null; - while (!unique){ - pos = new Pos(random.nextInt(2*max) - max, random.nextInt(2*max) - max, random.nextInt(2*max) - max); - if (!present.contains(pos)) break; - failures ++; - if (failures >= 2 * n){ - throw new RuntimeException("Too many failed attempts to create a unique coordinate."); - } - } - coords[i][0] = pos.x; - coords[i][1] = pos.y; - coords[i][2] = pos.z; - present.add(pos); - } - present.clear(); - return coords; - } - - public Map getIndexMap(int[][] coords) { - final Map indexMap = new HashMap(coords.length); - for (int i = 0; i < coords.length; i ++){ - indexMap.put(i, coords[i]); - } - return indexMap; - } - - /** - * Fill map and check if filled in elements are inside (no size check). - * @param map - * @param coords - */ - public void fillMap(CoordMap map, int[][] coords) { - for (int i = 0; i < coords.length ; i++){ - map.put(coords[i][0], coords[i][1], coords[i][2], i); - Integer value = map.get(coords[i][0], coords[i][1], coords[i][2]); - if (value == null) fail("Value is null, get after put: " + i); - else if (value.intValue() != i) fail("get right after put"); - if (!map.contains(coords[i][0], coords[i][1], coords[i][2])) fail("Contains returns false: " + i); - } - } - - /** - * Match map contents (must match exactly). - * @param map - * @param coords - */ - public void matchAll(CoordMap map, int[][] coords) { - for (int i = 0; i < coords.length ; i++){ - Integer value = map.get(coords[i][0], coords[i][1], coords[i][2]).intValue(); - if (value == null) fail("Value is null instead of " + i); - if (value.intValue() != i) fail("Wrong value: " + value + " vs. " + i); - if (!map.contains(coords[i][0], coords[i][1], coords[i][2])) fail("Contains returns false."); - } - if (map.size() != coords.length) fail("Iterator wrong number of elements: " + map.size() + "/" + coords.length); - } - - /** - * Match map contents with an (must match exactly). - * @param map - * @param indexMap - */ - public void matchAllIterator(CoordMap map, Map indexMap){ - Iterator> it = map.iterator(); - Set found = new HashSet(); - while (it.hasNext()){ - Entry entry = it.next(); - Integer value = entry.getValue(); - if (value == null) fail("Null value."); - int[] pos = indexMap.get(value); -// if (pos == null) fail - if (pos[0] != entry.getX() || pos[1] != entry.getY() || pos[2] != entry.getZ()) fail("Wrong coordinates."); - if (map.get(pos[0], pos[1], pos[2]).intValue() != value.intValue()) fail("Wrong value."); - if (found.contains(value)) fail("Already found: " + value); - if (!map.contains(pos[0], pos[1], pos[2])) fail("Contains returns false"); - found.add(value); - } - if (found.size() != indexMap.size()) fail("Iterator wrong number of elements: " + found.size() + "/" + indexMap.size()); + public static class Pos{ + 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 final int x; + public final int y; + public final int z; + private final int hash; + public Pos(int x, int y, int z){ + this.x = x; + this.y = y; + this.z = z; + this.hash = getHash(x, y, z); + } + @Override + public boolean equals(Object obj) { + if (obj instanceof Pos){ + Pos other = (Pos) obj; + return other.hash == hash && other.x == x && other.y == y && other.z == z; + } + else return false; + } + @Override + public int hashCode() { + return hash; + } + } - } - - /** - * Remove all coords (expect map to be only filled with those). - * @param map - * @param coords - */ - public void removeAll(CoordMap map, int[][] coords) { - for (int i = 0; i < coords.length ; i++){ - if (map.remove(coords[i][0], coords[i][1], coords[i][2]).intValue() != i) fail("removed should be " + i ); - int expectedSize = coords.length - (i + 1); - if (map.size() != expectedSize) fail("Bad size (" + map.size() + "), expect " + expectedSize); - if (map.get(coords[i][0], coords[i][1], coords[i][2]) != null) fail("get right after remove not null"); - if (map.contains(coords[i][0], coords[i][1], coords[i][2])) fail ("Still contains"); - } - if (map.size() != 0) fail("Map not emptied, left: " + map.size()); - } - - /** - * Remove all coords using an iterator (expect map to be only filled with those). - * @param map - * @param indexMap - */ - public void removeAllIterator(CoordMap map, Map indexMap) - { - Iterator> it = map.iterator(); - Set removed = new HashSet(); - while (it.hasNext()){ - Entry entry = it.next(); - Integer value = entry.getValue(); - if (value == null) fail("Null value."); - int[] pos = indexMap.get(value); -// if (pos == null) fail - if (pos[0] != entry.getX() || pos[1] != entry.getY() || pos[2] != entry.getZ()) fail("Wrong coordinates."); - if (removed.contains(value)) fail("Already removed: " + value); - removed.add(value); - it.remove(); - if (map.get(pos[0], pos[1], pos[2]) != null) fail("get right after remove not null"); - if (map.contains(pos[0], pos[1], pos[2])) fail("Still contains."); - } - if (map.size() != 0) fail("Map not emptied, left: " + map.size()); - if (removed.size() != indexMap.size()) fail("Iterator wrong number of elements: " + removed.size() + "/" + indexMap.size()); - } - - public void assertSize(CoordMap map, int size){ - if (map.size() != size) fail("Map returns wrong size: " + map.size() + " instead of " + size); - int found = 0; - final Iterator it = map.iterator(); - while (it.hasNext()){ - found ++; - it.next(); - } - if (found != size) fail("Iterator has wrong number of elements: " + found + " instead of " + size); - } + public int[][] getRandomCoords(int n, int max, Random random) { + final int [][] coords = new int[n][3]; + for (int i = 0; i < n; i++){ + for (int j = 0; j < 3 ; j++){ + coords[i][j] = random.nextInt(2*max) - max; + } + } + return coords; + } - /** - * One integrity test series with a map with given initial size. - * @param coords - * @param indexMap - * @param initialSize - */ - public void series(int[][] coords, Map indexMap, int initialSize, float loadFactor) { - CoordMap map; - - // Fill and check - map = new CoordHashMap(initialSize, loadFactor); - fillMap(map, coords); - assertSize(map, indexMap.size()); - matchAll(map, coords); - - // Fill and check with iterator. - map = new CoordHashMap(initialSize, loadFactor); - fillMap(map, coords); - assertSize(map, indexMap.size()); - matchAllIterator(map, indexMap); - - // Normal removing - map = new CoordHashMap(initialSize, loadFactor); - fillMap(map, coords); - assertSize(map, indexMap.size()); - removeAll(map, coords); - assertSize(map, 0); - - // Removing with iterator. - map = new CoordHashMap(initialSize, loadFactor); - fillMap(map, coords); - assertSize(map, indexMap.size()); - removeAllIterator(map, indexMap); - assertSize(map, 0); - - // Fill twice. - map = new CoordHashMap(initialSize, loadFactor); - fillMap(map, coords); - assertSize(map, indexMap.size()); - fillMap(map, coords); - assertSize(map, indexMap.size()); - matchAll(map, coords); - removeAll(map, coords); - assertSize(map, 0); - - // Fill twice iterator. - map = new CoordHashMap(initialSize, loadFactor); - fillMap(map, coords); - assertSize(map, indexMap.size()); - fillMap(map, coords); - assertSize(map, indexMap.size()); - matchAllIterator(map, indexMap); - removeAllIterator(map, indexMap); - assertSize(map, 0); - - // TODO: test / account for identical keys. - - // ? random sequence of actions ? - } - - @Test - public void testIntegrity() { + public int[][] getUniqueRandomCoords(int n, int max, Random random) { + Set present = new HashSet(); + int failures = 0; + final int [][] coords = new int[n][3]; + for (int i = 0; i < n; i++){ + boolean unique = false; + Pos pos = null; + while (!unique){ + pos = new Pos(random.nextInt(2*max) - max, random.nextInt(2*max) - max, random.nextInt(2*max) - max); + if (!present.contains(pos)) break; + failures ++; + if (failures >= 2 * n){ + throw new RuntimeException("Too many failed attempts to create a unique coordinate."); + } + } + coords[i][0] = pos.x; + coords[i][1] = pos.y; + coords[i][2] = pos.z; + present.add(pos); + } + present.clear(); + return coords; + } - final Random random = new Random(System.nanoTime() - (System.currentTimeMillis() % 2 == 1 ? 37 : 137)); - - final boolean e = BuildParameters.testLevel > 0; - - final int n = e ? 40000 : 6000; // Number of coordinates. - final int max = 800; // Coordinate maximum. - - int [][] coords = getUniqueRandomCoords(n, max, random); - - Map indexMap = getIndexMap(coords); - - // No resize - series(coords, indexMap, 3 * n, 0.75f); + public Map getIndexMap(int[][] coords) { + final Map indexMap = new HashMap(coords.length); + for (int i = 0; i < coords.length; i ++){ + indexMap.put(i, coords[i]); + } + return indexMap; + } + + /** + * Fill map and check if filled in elements are inside (no size check). + * @param map + * @param coords + */ + public void fillMap(CoordMap map, int[][] coords) { + for (int i = 0; i < coords.length ; i++){ + map.put(coords[i][0], coords[i][1], coords[i][2], i); + Integer value = map.get(coords[i][0], coords[i][1], coords[i][2]); + if (value == null) fail("Value is null, get after put: " + i); + else if (value.intValue() != i) fail("get right after put"); + if (!map.contains(coords[i][0], coords[i][1], coords[i][2])) fail("Contains returns false: " + i); + } + } + + /** + * Match map contents (must match exactly). + * @param map + * @param coords + */ + public void matchAll(CoordMap map, int[][] coords) { + for (int i = 0; i < coords.length ; i++){ + Integer value = map.get(coords[i][0], coords[i][1], coords[i][2]).intValue(); + if (value == null) fail("Value is null instead of " + i); + if (value.intValue() != i) fail("Wrong value: " + value + " vs. " + i); + if (!map.contains(coords[i][0], coords[i][1], coords[i][2])) fail("Contains returns false."); + } + if (map.size() != coords.length) fail("Iterator wrong number of elements: " + map.size() + "/" + coords.length); + + } + + /** + * Match map contents with an (must match exactly). + * @param map + * @param indexMap + */ + public void matchAllIterator(CoordMap map, Map indexMap){ + Iterator> it = map.iterator(); + Set found = new HashSet(); + while (it.hasNext()){ + Entry entry = it.next(); + Integer value = entry.getValue(); + if (value == null) fail("Null value."); + int[] pos = indexMap.get(value); + // if (pos == null) fail + if (pos[0] != entry.getX() || pos[1] != entry.getY() || pos[2] != entry.getZ()) fail("Wrong coordinates."); + if (map.get(pos[0], pos[1], pos[2]).intValue() != value.intValue()) fail("Wrong value."); + if (found.contains(value)) fail("Already found: " + value); + if (!map.contains(pos[0], pos[1], pos[2])) fail("Contains returns false"); + found.add(value); + } + if (found.size() != indexMap.size()) fail("Iterator wrong number of elements: " + found.size() + "/" + indexMap.size()); + + } + + /** + * Remove all coords (expect map to be only filled with those). + * @param map + * @param coords + */ + public void removeAll(CoordMap map, int[][] coords) { + for (int i = 0; i < coords.length ; i++){ + if (map.remove(coords[i][0], coords[i][1], coords[i][2]).intValue() != i) fail("removed should be " + i ); + int expectedSize = coords.length - (i + 1); + if (map.size() != expectedSize) fail("Bad size (" + map.size() + "), expect " + expectedSize); + if (map.get(coords[i][0], coords[i][1], coords[i][2]) != null) fail("get right after remove not null"); + if (map.contains(coords[i][0], coords[i][1], coords[i][2])) fail ("Still contains"); + } + if (map.size() != 0) fail("Map not emptied, left: " + map.size()); + } + + /** + * Remove all coords using an iterator (expect map to be only filled with those). + * @param map + * @param indexMap + */ + public void removeAllIterator(CoordMap map, Map indexMap) + { + Iterator> it = map.iterator(); + Set removed = new HashSet(); + while (it.hasNext()){ + Entry entry = it.next(); + Integer value = entry.getValue(); + if (value == null) fail("Null value."); + int[] pos = indexMap.get(value); + // if (pos == null) fail + if (pos[0] != entry.getX() || pos[1] != entry.getY() || pos[2] != entry.getZ()) fail("Wrong coordinates."); + if (removed.contains(value)) fail("Already removed: " + value); + removed.add(value); + it.remove(); + if (map.get(pos[0], pos[1], pos[2]) != null) fail("get right after remove not null"); + if (map.contains(pos[0], pos[1], pos[2])) fail("Still contains."); + } + if (map.size() != 0) fail("Map not emptied, left: " + map.size()); + if (removed.size() != indexMap.size()) fail("Iterator wrong number of elements: " + removed.size() + "/" + indexMap.size()); + } + + public void assertSize(CoordMap map, int size){ + if (map.size() != size) fail("Map returns wrong size: " + map.size() + " instead of " + size); + int found = 0; + final Iterator it = map.iterator(); + while (it.hasNext()){ + found ++; + it.next(); + } + if (found != size) fail("Iterator has wrong number of elements: " + found + " instead of " + size); + } + + /** + * One integrity test series with a map with given initial size. + * @param coords + * @param indexMap + * @param initialSize + */ + public void series(int[][] coords, Map indexMap, int initialSize, float loadFactor) { + + // Fill and check + for (CoordMap map : Arrays.asList( + new CoordHashMap(initialSize, loadFactor), + new LinkedCoordHashMap(initialSize, loadFactor) + )) { + fillMap(map, coords); + assertSize(map, indexMap.size()); + matchAll(map, coords); + } + + // Fill and check with iterator. + for (CoordMap map : Arrays.asList( + new CoordHashMap(initialSize, loadFactor), + new LinkedCoordHashMap(initialSize, loadFactor) + )) { + fillMap(map, coords); + assertSize(map, indexMap.size()); + matchAllIterator(map, indexMap); + } + + // Normal removing + for (CoordMap map : Arrays.asList( + new CoordHashMap(initialSize, loadFactor), + new LinkedCoordHashMap(initialSize, loadFactor) + )) { + fillMap(map, coords); + assertSize(map, indexMap.size()); + removeAll(map, coords); + assertSize(map, 0); + } + + // Removing with iterator. + for (CoordMap map : Arrays.asList( + new CoordHashMap(initialSize, loadFactor), + new LinkedCoordHashMap(initialSize, loadFactor) + )) { + fillMap(map, coords); + assertSize(map, indexMap.size()); + removeAllIterator(map, indexMap); + assertSize(map, 0); + } + + // Fill twice. + for (CoordMap map : Arrays.asList( + new CoordHashMap(initialSize, loadFactor), + new LinkedCoordHashMap(initialSize, loadFactor) + )) { + fillMap(map, coords); + assertSize(map, indexMap.size()); + fillMap(map, coords); + assertSize(map, indexMap.size()); + matchAll(map, coords); + removeAll(map, coords); + assertSize(map, 0); + } + + // Fill twice iterator. + for (CoordMap map : Arrays.asList( + new CoordHashMap(initialSize, loadFactor), + new LinkedCoordHashMap(initialSize, loadFactor) + )) { + fillMap(map, coords); + assertSize(map, indexMap.size()); + fillMap(map, coords); + assertSize(map, indexMap.size()); + matchAllIterator(map, indexMap); + removeAllIterator(map, indexMap); + assertSize(map, 0); + } + + // TODO: test / account for identical keys. + + // ? random sequence of actions ? + } + + @Test + public void testIntegrity() { + + final Random random = new Random(System.nanoTime() - (System.currentTimeMillis() % 2 == 1 ? 37 : 137)); + + final boolean e = BuildParameters.testLevel > 0; + + final int n = e ? 40000 : 6000; // Number of coordinates. + final int max = 800; // Coordinate maximum. + + int [][] coords = getUniqueRandomCoords(n, max, random); + + Map indexMap = getIndexMap(coords); + + // No resize + series(coords, indexMap, 3 * n, 0.75f); + + // With some times resize. + series(coords, indexMap, 1, 0.75f); + + // TODO: Also test with certain sets of coords that always are the same. + // TODO: fill in multiple times + size, fill in new ones (careful random) + size + } + + @Test + public void testLinkedCoordHashMap() { + + final Random random = new Random(System.nanoTime() - (System.currentTimeMillis() % 2 == 1 ? 37 : 137)); + + final boolean e = BuildParameters.testLevel > 0; + + final int n = e ? 40000 : 6000; // Number of coordinates. + final int max = 800; // Coordinate maximum. + + int [][] coords = getUniqueRandomCoords(n, max, random); + + LinkedCoordHashMap map = new LinkedCoordHashMap(1, 0.75f); + fillMap(map, coords); + // Test if the order of iteration is correct (!). + int i = 0; + Iterator> it = map.iterator(true); // New entries are put to front. + while (it.hasNext()) { + testNext(it, coords, i); + i++; + } + i = coords.length - 1; + it = map.iterator(false); + while (it.hasNext()) { + testNext(it, coords, i); + i--; + } + } + + private void testNext(Iterator> it, int[][] coords, int matchIndex) { + Entry entry = it.next(); + if (entry.getValue().intValue() != matchIndex) { + fail("Index mismatch, expect " + matchIndex + ", got instead: " + entry.getValue()); + } + if (entry.getX() != coords[matchIndex][0] || entry.getY() != coords[matchIndex][1] || entry.getZ() != coords[matchIndex][2]) { + // Very unlikely. + fail("Coordinate mismatch at index: " + matchIndex); + } + } - // With some times resize. - series(coords, indexMap, 1, 0.75f); - - // TODO: Also test with certain sets of coords that always are the same. - // TODO: fill in multiple times + size, fill in new ones (careful random) + size - } - }