Fixes and tests for LinkedCoordHashMap.

* Default order: order of first put (first put is first on iteration).
* Fix order being done right for all cases.
* Fix linking.
* Override clear.
* Add more tests.
This commit is contained in:
asofold 2015-11-30 09:16:41 +01:00
parent 1053c21e56
commit a216a940d6
2 changed files with 185 additions and 31 deletions

View File

@ -9,7 +9,7 @@ import java.util.NoSuchElementException;
* 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. This implementation does not
* order. By default entries are added to the end. 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.
*
@ -51,7 +51,7 @@ public class LinkedCoordHashMap<V> extends AbstractCoordHashMap<V, fr.neatmonste
private LinkedHashEntry<V> current = null;
private LinkedHashEntry<V> next;
private boolean reverse;
private final boolean reverse;
protected LinkedHashIterator(LinkedCoordHashMap<V> map, boolean reverse) {
this.map = map;
@ -177,7 +177,11 @@ public class LinkedCoordHashMap<V> extends AbstractCoordHashMap<V, fr.neatmonste
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) {
if (order == MoveOrder.FRONT) {
moveToFront(x, y, z);
}
else if (previousValue != null && order == MoveOrder.END) {
// Ensure the intended order.
moveToEnd(x, y, z);
}
return previousValue;
@ -198,7 +202,8 @@ public class LinkedCoordHashMap<V> extends AbstractCoordHashMap<V, fr.neatmonste
}
/**
* Control order of iteration. Actual order depends on the accessOrder flag.
* Control order of iteration.
*
* @param reversed
* @return
*/
@ -209,8 +214,8 @@ public class LinkedCoordHashMap<V> extends AbstractCoordHashMap<V, fr.neatmonste
@Override
protected LinkedHashEntry<V> newEntry(int x, int y, int z, V value, int hash) {
LinkedHashEntry<V> entry = new LinkedHashEntry<V>(x, y, z, value, hash);
// Always put in first.
setFirst(entry);
// Always put in last.
setLast(entry);
return entry;
}
@ -222,7 +227,8 @@ public class LinkedCoordHashMap<V> extends AbstractCoordHashMap<V, fr.neatmonste
private void setFirst(LinkedHashEntry<V> entry) {
if (this.firstEntry == null) {
this.firstEntry = this.lastEntry = entry;
} else {
}
else {
entry.next = this.firstEntry;
this.firstEntry.previous = entry;
this.firstEntry = entry;
@ -237,7 +243,8 @@ public class LinkedCoordHashMap<V> extends AbstractCoordHashMap<V, fr.neatmonste
private void setLast(LinkedHashEntry<V> entry) {
if (this.firstEntry == null) {
this.firstEntry = this.lastEntry = entry;
} else {
}
else {
entry.previous = this.lastEntry;
this.lastEntry.next = entry;
this.lastEntry = entry;
@ -247,18 +254,31 @@ public class LinkedCoordHashMap<V> extends AbstractCoordHashMap<V, fr.neatmonste
@Override
protected void removeEntry(LinkedHashEntry<V> entry) {
// Just unlink.
if (entry.previous == null) {
if (entry == this.firstEntry) {
this.firstEntry = entry.next;
} else {
if (this.firstEntry != null) {
this.firstEntry.previous = null;
}
}
else {
entry.previous.next = entry.next;
entry.previous = null;
}
if (entry.next == null) {
if (entry == this.lastEntry) {
this.lastEntry = entry.previous;
} else {
entry.next.previous = entry.previous;
entry.next = null;
if (this.lastEntry != null) {
this.lastEntry.next = null;
}
}
else {
entry.next.previous = entry.previous;
}
entry.previous = entry.next = null;
}
@Override
public void clear() {
super.clear();
firstEntry = lastEntry = null;
}
}

View File

@ -16,6 +16,7 @@ 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.MoveOrder;
import fr.neatmonster.nocheatplus.utilities.ds.map.LinkedCoordHashMap;
public class TestCoordMap {
@ -318,32 +319,165 @@ public class TestCoordMap {
final int n = e ? 40000 : 6000; // Number of coordinates.
final int max = 800; // Coordinate maximum.
// Preparecoordinates.
int [][] coords = getUniqueRandomCoords(n, max, random);
LinkedCoordHashMap<Integer> map = new LinkedCoordHashMap<Integer>(1, 0.75f);
// Use a map with these coordinates.
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++;
// Initial iteration order.
testIterationOrder(map, coords, 1);
// Re-put, moving to end.
for (int i = 0; i < coords.length; i++) {
map.put(coords[i][0], coords[i][1], coords[i][2], i, MoveOrder.END);
testLast(map, coords[i], i);
}
i = coords.length - 1;
it = map.iterator(false);
while (it.hasNext()) {
testNext(it, coords, i);
i--;
if (map.size() != coords.length) {
fail("Map different size than coords.");
}
testIterationOrder(map, coords, 1);
// Re-put, moving to front.
for (int i = coords.length - 1; i >= 0; i--) {
map.put(coords[i][0], coords[i][1], coords[i][2], i, MoveOrder.FRONT);
testFirst(map, coords[i], i);
}
if (map.size() != coords.length) {
fail("Map different size than coords.");
}
testIterationOrder(map, coords, 1);
// Map.clear
map.clear();
if (map.size() != 0) {
fail("Expect map size to be 0 after clear.");
}
if (map.iterator(false).hasNext()) {
fail("Expect no first element on iteration after clear.");
}
if (map.iterator(true).hasNext()) {
fail("Expect no last element on iteration after clear.");
}
// TODO: Specific tests with iterator.remove.
// New map with all coordinates.
fillMap(map, coords);
// Half the coordinates.
int[][] halfCoords = new int[n / 2][3];
for (int i = 0; i < n / 2; i++) {
for (int j = 0; j < 3; j++) {
halfCoords[i][j] = coords[i * 2][j];
}
}
// Test remove every second entry.
for (int i = 0; i < n / 2; i++) {
map.remove(coords[i * 2 + 1][0], coords[i * 2 + 1][1], coords[i * 2 + 1][2]);
if (map.contains(coords[i * 2 + 1][0], coords[i * 2 + 1][1], coords[i * 2 + 1][2])) {
fail("Expect removed entries not to be contained in the map.");
}
}
if (map.size() != n / 2) {
fail("Map size should be halfed after removing every second element (" + map.size() + " instead of " + n / 2 + ").");
}
testIterationOrder(map, halfCoords, 2);
// Test iterator.remove every second entry.
map.clear();
fillMap(map, coords);
int i = 0;
Iterator<Entry<Integer>> it = map.iterator(false);
while (it.hasNext()) {
Entry<Integer> entry = it.next();
if (i % 2 == 1) {
it.remove();
if (map.contains(entry.getX(), entry.getY(), entry.getZ())) {
fail("Expect entries removed by iterator not to be in the map.");
}
}
i ++;
}
if (map.size() != n / 2) {
fail("Map size should be halfed after removing every second element with an iterator (" + map.size() + " instead of " + n / 2 + ").");
}
testIterationOrder(map, halfCoords, 2);
// TODO: Some random mixtures.
}
private void testNext(Iterator<Entry<Integer>> it, int[][] coords, int matchIndex) {
private void testIterationOrder(LinkedCoordHashMap<Integer> map, int[][] coords, int multiplyId) {
// Test if the order of iteration is correct (!).
int i = 0;
Iterator<Entry<Integer>> it = map.iterator(false); // New entries are put to the end.
while (it.hasNext()) {
testNext(it, coords, i, multiplyId);
i++;
}
if (i != coords.length) {
fail("Iterator different size than coords.");
}
if (i != map.size()) {
fail("Iterator different size than map.");
}
if (map.size() != coords.length) {
fail("Map different size than coords.");
}
i = coords.length - 1;
it = map.iterator(true);
while (it.hasNext()) {
testNext(it, coords, i, multiplyId);
i--;
}
if (i != -1) {
fail("Iterator wrong size.");
}
if (map.size() != coords.length) {
fail("Map different size than coords.");
}
}
/**
* Test if the last element is the expected one.
* @param map
* @param is
*/
private void testLast(LinkedCoordHashMap<Integer> map, int[] coords, int value) {
if (map.get(coords[0], coords[1], coords[2]) != value) {
fail("Not even in the map: " + value);
}
Entry<Integer> entry = map.iterator(true).next();
if (entry.getValue() != value) {
fail("Wrong id: " + entry.getValue() + " instead of " + value);
}
if (entry.getX() != coords[0] || entry.getY() != coords[1] || entry.getZ() != coords[2]) {
fail("Coordinate mismatch on " + value);
}
}
/**
* Test if the first element is the expected one.
* @param map
* @param is
*/
private void testFirst(LinkedCoordHashMap<Integer> map, int[] coords, int value) {
if (map.get(coords[0], coords[1], coords[2]) != value) {
fail("Not even in the map: " + value);
}
Entry<Integer> entry = map.iterator().next();
if (entry.getValue() != value) {
fail("Wrong id: " + entry.getValue() + " instead of " + value);
}
if (entry.getX() != coords[0] || entry.getY() != coords[1] || entry.getZ() != coords[2]) {
fail("Coordinate mismatch on " + value);
}
}
private void testNext(Iterator<Entry<Integer>> it, int[][] coords, int matchIndex, int multiplyId) {
Entry<Integer> entry = it.next();
if (entry.getValue().intValue() != matchIndex) {
fail("Index mismatch, expect " + matchIndex + ", got instead: " + entry.getValue());
if (entry.getValue().intValue() != matchIndex * multiplyId) {
fail("Index vs. value mismatch, expect " + matchIndex * multiplyId + ", got instead: " + entry.getValue());
}
if (entry.getX() != coords[matchIndex][0] || entry.getY() != coords[matchIndex][1] || entry.getZ() != coords[matchIndex][2]) {
// Very unlikely.