mirror of
https://github.com/NoCheatPlus/NoCheatPlus.git
synced 2025-02-16 03:31:37 +01:00
Implement a linked coord hash map, custom order, tests.
Can't implement "dumb" access order, because we will have to move entries to the front with setting and altering data, but we don't want the data to be changed every time get is called, e.g. with piston tracking. Efficiency could be increased, using the entry as reference instead of duplicate calls with coordinates.
This commit is contained in:
parent
f70151d8c7
commit
08c5aa2d7c
@ -92,6 +92,18 @@ public abstract class AbstractCoordHashMap<V, E extends fr.neatmonster.nocheatpl
|
||||
|
||||
@Override
|
||||
public V get(final int x, final int y, final int z) {
|
||||
final E entry = getEntry(x, y ,z);
|
||||
return entry == null ? null : entry.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just get an entry.
|
||||
* @param x
|
||||
* @param y
|
||||
* @param z
|
||||
* @return
|
||||
*/
|
||||
protected E getEntry(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<E> bucket = entries[slot];
|
||||
@ -100,14 +112,14 @@ public abstract class AbstractCoordHashMap<V, E extends fr.neatmonster.nocheatpl
|
||||
}
|
||||
for (final E entry : bucket) {
|
||||
if (hash == entry.hash && x == entry.x && z == entry.z && y == entry.y) {
|
||||
return entry.value;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
final int hash = getHash(x, y, z);
|
||||
final int absHash = Math.abs(hash);
|
||||
int slot = absHash % entries.length;
|
||||
@ -115,8 +127,9 @@ public abstract class AbstractCoordHashMap<V, E extends fr.neatmonster.nocheatpl
|
||||
if (bucket != null) {
|
||||
for (final E entry : bucket) {
|
||||
if (hash == entry.hash && x == entry.x && z == entry.z && y == entry.y) {
|
||||
final V previousValue = entry.value;
|
||||
entry.value = value;
|
||||
return true;
|
||||
return previousValue;
|
||||
}
|
||||
}
|
||||
} else if (size + 1 > entries.length * loadFactor) {
|
||||
@ -131,7 +144,7 @@ public abstract class AbstractCoordHashMap<V, E extends fr.neatmonster.nocheatpl
|
||||
}
|
||||
bucket.add(newEntry(x, y, z, value, hash));
|
||||
size++;
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -153,6 +166,7 @@ public abstract class AbstractCoordHashMap<V, E extends fr.neatmonster.nocheatpl
|
||||
if (bucket.isEmpty()) {
|
||||
entries[slot] = null;
|
||||
}
|
||||
removeEntry(entry);
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
@ -220,4 +234,13 @@ public abstract class AbstractCoordHashMap<V, E extends fr.neatmonster.nocheatpl
|
||||
*/
|
||||
protected abstract E newEntry(int x, int y, int z, V value, int hash);
|
||||
|
||||
/**
|
||||
* Called after removing an entry from the internal storage.
|
||||
*
|
||||
* @param entry
|
||||
*/
|
||||
protected void removeEntry(E entry) {
|
||||
// Override if needed.
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -51,9 +51,9 @@ public interface CoordMap<V> {
|
||||
* 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<V> {
|
||||
* Iterator over all elements (default order to be specified).
|
||||
* <hr>
|
||||
* 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
|
||||
*/
|
||||
|
@ -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.<br>
|
||||
@ -8,7 +9,9 @@ import java.util.Iterator;
|
||||
* get/contains should work if the map stays unchanged.
|
||||
* <hr>
|
||||
* Linked hash map implementation of CoordMap<V>, 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<V> extends AbstractCoordHashMap<V, fr.neatmonster.nocheatplus.utilities.ds.map.LinkedCoordHashMap.LinkedHashEntry<V>> {
|
||||
|
||||
// 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<V> extends fr.neatmonster.nocheatplus.utilities.ds.map.AbstractCoordHashMap.HashEntry<V> {
|
||||
|
||||
protected LinkedHashEntry<V> previous = null;
|
||||
protected LinkedHashEntry<V> 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<V> implements Iterator<Entry<V>> {
|
||||
|
||||
private final LinkedCoordHashMap<V> map;
|
||||
|
||||
private LinkedHashEntry<V> current = null;
|
||||
private LinkedHashEntry<V> next;
|
||||
|
||||
private boolean reverse;
|
||||
|
||||
protected LinkedHashIterator(LinkedCoordHashMap<V> 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<V> 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<V> firstEntry = null;
|
||||
protected LinkedHashEntry<V> 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<V> 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<V> 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<Entry<V>> iterator() {
|
||||
throw new IllegalStateException("Not yet implemented."); // TODO: Implement.
|
||||
public LinkedHashIterator<V> iterator() {
|
||||
return new LinkedHashIterator<V>(this, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,15 +202,63 @@ public class LinkedCoordHashMap<V> extends AbstractCoordHashMap<V, fr.neatmonste
|
||||
* @param reversed
|
||||
* @return
|
||||
*/
|
||||
public Iterator<Entry<V>> iterator(boolean reversed) {
|
||||
throw new IllegalStateException("Not yet implemented."); // TODO: Implement
|
||||
public LinkedHashIterator<V> iterator(boolean reversed) {
|
||||
return new LinkedHashIterator<V>(this, reversed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinkedHashEntry<V> newEntry(int x, int y, int z, V value, int hash) {
|
||||
throw new IllegalStateException("Not yet implemented."); // TODO: Implement
|
||||
LinkedHashEntry<V> entry = new LinkedHashEntry<V>(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<V> 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<V> 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<V> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Pos> present = new HashSet<Pos>();
|
||||
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<Integer, int[]> getIndexMap(int[][] coords) {
|
||||
final Map<Integer, int[]> indexMap = new HashMap<Integer, int[]>(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<Integer> 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<Integer> 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<Integer> map, Map<Integer, int[]> indexMap){
|
||||
Iterator<Entry<Integer>> it = map.iterator();
|
||||
Set<Integer> found = new HashSet<Integer>();
|
||||
while (it.hasNext()){
|
||||
Entry<Integer> 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<Integer> 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<Integer> map, Map<Integer, int[]> indexMap)
|
||||
{
|
||||
Iterator<Entry<Integer>> it = map.iterator();
|
||||
Set<Integer> removed = new HashSet<Integer>();
|
||||
while (it.hasNext()){
|
||||
Entry<Integer> 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<Integer, int[]> indexMap, int initialSize, float loadFactor) {
|
||||
CoordMap<Integer> map;
|
||||
|
||||
// Fill and check
|
||||
map = new CoordHashMap<Integer>(initialSize, loadFactor);
|
||||
fillMap(map, coords);
|
||||
assertSize(map, indexMap.size());
|
||||
matchAll(map, coords);
|
||||
|
||||
// Fill and check with iterator.
|
||||
map = new CoordHashMap<Integer>(initialSize, loadFactor);
|
||||
fillMap(map, coords);
|
||||
assertSize(map, indexMap.size());
|
||||
matchAllIterator(map, indexMap);
|
||||
|
||||
// Normal removing
|
||||
map = new CoordHashMap<Integer>(initialSize, loadFactor);
|
||||
fillMap(map, coords);
|
||||
assertSize(map, indexMap.size());
|
||||
removeAll(map, coords);
|
||||
assertSize(map, 0);
|
||||
|
||||
// Removing with iterator.
|
||||
map = new CoordHashMap<Integer>(initialSize, loadFactor);
|
||||
fillMap(map, coords);
|
||||
assertSize(map, indexMap.size());
|
||||
removeAllIterator(map, indexMap);
|
||||
assertSize(map, 0);
|
||||
|
||||
// Fill twice.
|
||||
map = new CoordHashMap<Integer>(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<Integer>(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<Pos> present = new HashSet<Pos>();
|
||||
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<Integer, int[]> indexMap = getIndexMap(coords);
|
||||
|
||||
// No resize
|
||||
series(coords, indexMap, 3 * n, 0.75f);
|
||||
public Map<Integer, int[]> getIndexMap(int[][] coords) {
|
||||
final Map<Integer, int[]> indexMap = new HashMap<Integer, int[]>(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<Integer> 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<Integer> 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<Integer> map, Map<Integer, int[]> indexMap){
|
||||
Iterator<Entry<Integer>> it = map.iterator();
|
||||
Set<Integer> found = new HashSet<Integer>();
|
||||
while (it.hasNext()){
|
||||
Entry<Integer> 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<Integer> 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<Integer> map, Map<Integer, int[]> indexMap)
|
||||
{
|
||||
Iterator<Entry<Integer>> it = map.iterator();
|
||||
Set<Integer> removed = new HashSet<Integer>();
|
||||
while (it.hasNext()){
|
||||
Entry<Integer> 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<Integer, int[]> indexMap, int initialSize, float loadFactor) {
|
||||
|
||||
// Fill and check
|
||||
for (CoordMap<Integer> map : Arrays.asList(
|
||||
new CoordHashMap<Integer>(initialSize, loadFactor),
|
||||
new LinkedCoordHashMap<Integer>(initialSize, loadFactor)
|
||||
)) {
|
||||
fillMap(map, coords);
|
||||
assertSize(map, indexMap.size());
|
||||
matchAll(map, coords);
|
||||
}
|
||||
|
||||
// Fill and check with iterator.
|
||||
for (CoordMap<Integer> map : Arrays.asList(
|
||||
new CoordHashMap<Integer>(initialSize, loadFactor),
|
||||
new LinkedCoordHashMap<Integer>(initialSize, loadFactor)
|
||||
)) {
|
||||
fillMap(map, coords);
|
||||
assertSize(map, indexMap.size());
|
||||
matchAllIterator(map, indexMap);
|
||||
}
|
||||
|
||||
// Normal removing
|
||||
for (CoordMap<Integer> map : Arrays.asList(
|
||||
new CoordHashMap<Integer>(initialSize, loadFactor),
|
||||
new LinkedCoordHashMap<Integer>(initialSize, loadFactor)
|
||||
)) {
|
||||
fillMap(map, coords);
|
||||
assertSize(map, indexMap.size());
|
||||
removeAll(map, coords);
|
||||
assertSize(map, 0);
|
||||
}
|
||||
|
||||
// Removing with iterator.
|
||||
for (CoordMap<Integer> map : Arrays.asList(
|
||||
new CoordHashMap<Integer>(initialSize, loadFactor),
|
||||
new LinkedCoordHashMap<Integer>(initialSize, loadFactor)
|
||||
)) {
|
||||
fillMap(map, coords);
|
||||
assertSize(map, indexMap.size());
|
||||
removeAllIterator(map, indexMap);
|
||||
assertSize(map, 0);
|
||||
}
|
||||
|
||||
// Fill twice.
|
||||
for (CoordMap<Integer> map : Arrays.asList(
|
||||
new CoordHashMap<Integer>(initialSize, loadFactor),
|
||||
new LinkedCoordHashMap<Integer>(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<Integer> map : Arrays.asList(
|
||||
new CoordHashMap<Integer>(initialSize, loadFactor),
|
||||
new LinkedCoordHashMap<Integer>(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<Integer, int[]> 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<Integer> map = new LinkedCoordHashMap<Integer>(1, 0.75f);
|
||||
fillMap(map, coords);
|
||||
// Test if the order of iteration is correct (!).
|
||||
int i = 0;
|
||||
Iterator<Entry<Integer>> 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<Entry<Integer>> it, int[][] coords, int matchIndex) {
|
||||
Entry<Integer> 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
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user