Merge remote-tracking branch 'dyncon/trimmed' into builder-2

This commit is contained in:
Leijurv
2023-03-20 23:55:09 -07:00
32 changed files with 6687 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
package com.github.btrekkie.arbitrary_order_collection;
import java.util.Comparator;
/**
* Provides objects ordered in an arbitrary, but consistent fashion, through the createValue() method. To determine the
* relative order of two values, use ArbitraryOrderValue.compareTo. We may only compare values on which we have not
* called "remove" that were created in the same ArbitraryOrderCollection instance. Note that despite the name,
* ArbitraryOrderCollection does not implement Collection.
*/
/* We implement an ArbitraryOrderCollection using a red-black tree. We order the nodes arbitrarily.
*/
public class ArbitraryOrderCollection {
/**
* The Comparator for ordering ArbitraryOrderNodes.
*/
private static final Comparator<ArbitraryOrderNode> NODE_COMPARATOR = new Comparator<ArbitraryOrderNode>() {
@Override
public int compare(ArbitraryOrderNode node1, ArbitraryOrderNode node2) {
return 0;
}
};
/**
* The root node of the tree.
*/
private ArbitraryOrderNode root = new ArbitraryOrderNode();
/**
* Adds and returns a new value for ordering.
*/
public ArbitraryOrderValue createValue() {
ArbitraryOrderNode node = new ArbitraryOrderNode();
root = root.insert(node, true, NODE_COMPARATOR);
return new ArbitraryOrderValue(node);
}
/**
* Removes the specified value from this collection. Assumes we obtained the value by calling createValue() on this
* instance of ArbitraryOrderCollection. After calling "remove" on a value, we may no longer compare it to other
* values.
*/
public void remove(ArbitraryOrderValue value) {
root = value.node.remove();
}
}

View File

@@ -0,0 +1,10 @@
package com.github.btrekkie.arbitrary_order_collection;
import com.github.btrekkie.red_black_node.RedBlackNode;
/**
* A node in an ArbitraryOrderCollection tree. See ArbitraryOrderCollection.
*/
class ArbitraryOrderNode extends RedBlackNode<ArbitraryOrderNode> {
}

View File

@@ -0,0 +1,21 @@
package com.github.btrekkie.arbitrary_order_collection;
/**
* A value in an ArbitraryOrderCollection. To determine the relative order of two values in the same collection, call
* compareTo.
*/
public class ArbitraryOrderValue implements Comparable<ArbitraryOrderValue> {
/**
* The node that establishes this value's relative position.
*/
final ArbitraryOrderNode node;
ArbitraryOrderValue(ArbitraryOrderNode node) {
this.node = node;
}
@Override
public int compareTo(ArbitraryOrderValue other) {
return node.compareTo(other.node);
}
}

View File

@@ -0,0 +1,73 @@
package com.github.btrekkie.arbitrary_order_collection.test;
import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderCollection;
import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderValue;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class ArbitraryOrderCollectionTest {
/**
* Tests ArbitraryOrderCollection.
*/
@Test
public void test() {
ArbitraryOrderCollection collection = new ArbitraryOrderCollection();
List<ArbitraryOrderValue> values1 = new ArrayList<ArbitraryOrderValue>(5);
ArbitraryOrderValue value = collection.createValue();
assertEquals(0, value.compareTo(value));
values1.add(value);
for (int i = 0; i < 4; i++) {
values1.add(collection.createValue());
}
Collections.sort(values1);
List<ArbitraryOrderValue> values2 = new ArrayList<ArbitraryOrderValue>(10);
for (int i = 0; i < 10; i++) {
value = collection.createValue();
values2.add(value);
}
for (int i = 0; i < 5; i++) {
collection.remove(values2.get(2 * i));
}
assertEquals(0, values1.get(0).compareTo(values1.get(0)));
assertTrue(values1.get(0).compareTo(values1.get(1)) < 0);
assertTrue(values1.get(1).compareTo(values1.get(0)) > 0);
assertTrue(values1.get(4).compareTo(values1.get(2)) > 0);
assertTrue(values1.get(0).compareTo(values1.get(4)) < 0);
collection = new ArbitraryOrderCollection();
values1 = new ArrayList<ArbitraryOrderValue>(1000);
for (int i = 0; i < 1000; i++) {
value = collection.createValue();
values1.add(value);
}
for (int i = 0; i < 500; i++) {
collection.remove(values1.get(2 * i));
}
values2 = new ArrayList<ArbitraryOrderValue>(500);
for (int i = 0; i < 500; i++) {
values2.add(values1.get(2 * i + 1));
}
for (int i = 0; i < 500; i++) {
values2.get(0).compareTo(values2.get(i));
}
Collections.sort(values2);
for (int i = 0; i < 500; i++) {
collection.createValue();
}
for (int i = 0; i < 499; i++) {
assertTrue(values2.get(i).compareTo(values2.get(i + 1)) < 0);
assertTrue(values2.get(i + 1).compareTo(values2.get(i)) > 0);
}
for (int i = 1; i < 500; i++) {
assertEquals(0, values2.get(i).compareTo(values2.get(i)));
assertTrue(values2.get(0).compareTo(values2.get(i)) < 0);
assertTrue(values2.get(i).compareTo(values2.get(0)) > 0);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
package com.github.btrekkie.connectivity.test;
import com.github.btrekkie.connectivity.Augmentation;
/**
* Stores two values: a sum and a maximum. Used for testing augmentation in ConnGraph.
*/
class SumAndMax {
/**
* An Augmentation that combines two SumAndMaxes into one.
*/
public static final Augmentation AUGMENTATION = new Augmentation() {
@Override
public Object combine(Object value1, Object value2) {
SumAndMax sumAndMax1 = (SumAndMax) value1;
SumAndMax sumAndMax2 = (SumAndMax) value2;
return new SumAndMax(sumAndMax1.sum + sumAndMax2.sum, Math.max(sumAndMax1.max, sumAndMax2.max));
}
};
public final int sum;
public final int max;
public SumAndMax(int sum, int max) {
this.sum = sum;
this.max = max;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof SumAndMax)) {
return false;
}
SumAndMax sumAndMax = (SumAndMax) obj;
return sum == sumAndMax.sum && max == sumAndMax.max;
}
@Override
public int hashCode() {
return 31 * sum + max;
}
}

View File

@@ -0,0 +1,65 @@
package com.github.btrekkie.interval_tree;
/**
* An interval tree data structure, which supports adding or removing an interval and finding an arbitrary interval that
* contains a specified value.
*/
/* The interval tree is ordered in ascending order of the start an interval, with ties broken by the end of the
* interval. Each node is augmented with the maximum ending value of an interval in the subtree rooted at the node.
*/
public class IntervalTree {
/**
* The root node of the tree.
*/
private IntervalTreeNode root = IntervalTreeNode.LEAF;
/**
* Adds the specified interval to this.
*/
public void addInterval(IntervalTreeInterval interval) {
root = root.insert(new IntervalTreeNode(interval), true, null);
}
/**
* Removes the specified interval from this, if it is present.
*
* @param interval The interval.
* @return Whether the interval was present.
*/
public boolean removeInterval(IntervalTreeInterval interval) {
IntervalTreeNode node = root;
while (!node.isLeaf()) {
if (interval.start < node.interval.start) {
node = node.left;
} else if (interval.start > node.interval.start) {
node = node.right;
} else if (interval.end < node.interval.end) {
node = node.left;
} else if (interval.end > node.interval.end) {
node = node.right;
} else {
root = node.remove();
return true;
}
}
return false;
}
/**
* Returns an aribtrary IntervalTreeInterval in this that contains the specified value. Returns null if there is no
* such interval.
*/
public IntervalTreeInterval findInterval(double value) {
IntervalTreeNode node = root;
while (!node.isLeaf()) {
if (value >= node.interval.start && value <= node.interval.end) {
return node.interval;
} else if (value <= node.left.maxEnd) {
node = node.left;
} else {
node = node.right;
}
}
return null;
}
}

View File

@@ -0,0 +1,32 @@
package com.github.btrekkie.interval_tree;
/**
* An inclusive range of values [start, end]. Two intervals are equal if they have the same starting and ending values.
*/
public class IntervalTreeInterval {
/**
* The smallest value in the range.
*/
public final double start;
/**
* The largest value in the range.
*/
public final double end;
public IntervalTreeInterval(double start, double end) {
if (start > end) {
throw new IllegalArgumentException("The end of the range must be at most the start");
}
this.start = start;
this.end = end;
}
public boolean equals(Object obj) {
if (!(obj instanceof IntervalTreeInterval)) {
return false;
}
IntervalTreeInterval interval = (IntervalTreeInterval) obj;
return start == interval.start && end == interval.end;
}
}

View File

@@ -0,0 +1,76 @@
package com.github.btrekkie.interval_tree;
import com.github.btrekkie.red_black_node.RedBlackNode;
/**
* A node in an IntervalTree. See the comments for the implementation of IntervalTree. Its compareTo method orders
* nodes as suggested in the comments for the implementation of IntervalTree.
*/
class IntervalTreeNode extends RedBlackNode<IntervalTreeNode> {
/**
* The dummy leaf node.
*/
public static final IntervalTreeNode LEAF = new IntervalTreeNode();
/**
* The interval stored in this node.
*/
public IntervalTreeInterval interval;
/**
* The maximum ending value of an interval in the subtree rooted at this node.
*/
public double maxEnd;
public IntervalTreeNode(IntervalTreeInterval interval) {
this.interval = interval;
maxEnd = interval.end;
}
/**
* Constructs a new dummy leaf node.
*/
private IntervalTreeNode() {
interval = null;
maxEnd = Double.NEGATIVE_INFINITY;
}
@Override
public boolean augment() {
double newMaxEnd = Math.max(interval.end, Math.max(left.maxEnd, right.maxEnd));
if (newMaxEnd == maxEnd) {
return false;
} else {
maxEnd = newMaxEnd;
return true;
}
}
@Override
public void assertNodeIsValid() {
double expectedMaxEnd;
if (isLeaf()) {
expectedMaxEnd = Double.NEGATIVE_INFINITY;
} else {
expectedMaxEnd = Math.max(interval.end, Math.max(left.maxEnd, right.maxEnd));
}
if (maxEnd != expectedMaxEnd) {
throw new RuntimeException("The node's maxEnd does not match that of the children");
}
}
@Override
public int compareTo(IntervalTreeNode other) {
if (interval.start != interval.end) {
return Double.compare(interval.start, other.interval.start);
} else {
return Double.compare(interval.end, other.interval.end);
}
}
@Override
public void assertSubtreeIsValid() {
super.assertSubtreeIsValid();
assertOrderIsValid(null);
}
}

View File

@@ -0,0 +1,46 @@
package com.github.btrekkie.interval_tree.test;
import com.github.btrekkie.interval_tree.IntervalTree;
import com.github.btrekkie.interval_tree.IntervalTreeInterval;
import org.junit.Test;
import static org.junit.Assert.*;
public class IntervalTreeTest {
/**
* Tests IntervalTree.
*/
@Test
public void test() {
IntervalTree tree = new IntervalTree();
assertNull(tree.findInterval(0.5));
assertNull(tree.findInterval(-1));
tree.addInterval(new IntervalTreeInterval(5, 7));
tree.addInterval(new IntervalTreeInterval(42, 48));
tree.addInterval(new IntervalTreeInterval(-1, 2));
tree.addInterval(new IntervalTreeInterval(6, 12));
tree.addInterval(new IntervalTreeInterval(21, 23));
assertTrue(tree.removeInterval(new IntervalTreeInterval(-1, 2)));
assertFalse(tree.removeInterval(new IntervalTreeInterval(-1, 2)));
tree.addInterval(new IntervalTreeInterval(-6, -2));
assertEquals(new IntervalTreeInterval(6, 12), tree.findInterval(8));
assertNull(tree.findInterval(0));
assertEquals(new IntervalTreeInterval(21, 23), tree.findInterval(21));
assertEquals(new IntervalTreeInterval(42, 48), tree.findInterval(48));
IntervalTreeInterval interval = tree.findInterval(6.5);
assertTrue(new IntervalTreeInterval(5, 7).equals(interval) || new IntervalTreeInterval(6, 12).equals(interval));
tree = new IntervalTree();
for (int i = 0; i < 500; i++) {
tree.addInterval(new IntervalTreeInterval(2 * i, 2 * i + 1));
}
for (int i = 0; i < 250; i++) {
tree.removeInterval(new IntervalTreeInterval(4 * i + 2, 4 * i + 3));
}
assertNull(tree.findInterval(123.5));
assertEquals(new IntervalTreeInterval(124, 125), tree.findInterval(124.5));
assertEquals(new IntervalTreeInterval(776, 777), tree.findInterval(776));
assertEquals(new IntervalTreeInterval(0, 1), tree.findInterval(0.5));
assertEquals(new IntervalTreeInterval(996, 997), tree.findInterval(997));
}
}

View File

@@ -0,0 +1,174 @@
package com.github.btrekkie.red_black_node.test;
import org.junit.Test;
import java.util.Comparator;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests RedBlackNode. Most of the testing for RedBlackNode takes place in TreeListTest, IntervalTreeTest,
* SubArrayMinTest, and ArbitraryOrderCollectionTest, which test realistic use cases of RedBlackNode. TreeListTest
* tests most of the RedBlackNode methods, while IntervalTreeTest tests the "insert" method, SubArrayMinTest tests
* "lca", ArbitraryOrderCollectionTest tests compareTo, and RedBlackNodeTest tests assertSubtreeIsValid() and
* assertOrderIsValid.
*/
public class RedBlackNodeTest {
/**
* Returns whether the subtree rooted at the specified node is valid, as in TestRedBlackNode.assertSubtreeIsValid().
*/
private boolean isSubtreeValid(TestRedBlackNode node) {
try {
node.assertSubtreeIsValid();
return true;
} catch (RuntimeException exception) {
return false;
}
}
/**
* Returns whether the nodes in the subtree rooted at the specified node are ordered correctly, as in
* TestRedBlackNode.assertOrderIsValid.
*
* @param comparator A comparator indicating how the nodes should be ordered. If this is null, we use the nodes'
* natural ordering, as in TestRedBlackNode.compare.
*/
private boolean isOrderValid(TestRedBlackNode node, Comparator<TestRedBlackNode> comparator) {
try {
node.assertOrderIsValid(null);
return true;
} catch (RuntimeException exception) {
return false;
}
}
/**
* Tests RedBlackNode.assertSubtreeIsValid() and RedBlackNode.assertOrderIsValid.
*/
@Test
public void testAssertIsValid() {
// Create a perfectly balanced tree of height 3
TestRedBlackNode node0 = new TestRedBlackNode(0);
TestRedBlackNode node1 = new TestRedBlackNode(1);
TestRedBlackNode node2 = new TestRedBlackNode(2);
TestRedBlackNode node3 = new TestRedBlackNode(3);
TestRedBlackNode node4 = new TestRedBlackNode(4);
TestRedBlackNode node5 = new TestRedBlackNode(5);
TestRedBlackNode node6 = new TestRedBlackNode(6);
node0.parent = node1;
node0.left = TestRedBlackNode.LEAF;
node0.right = TestRedBlackNode.LEAF;
node1.parent = node3;
node1.left = node0;
node1.right = node2;
node1.isRed = true;
node2.parent = node1;
node2.left = TestRedBlackNode.LEAF;
node2.right = TestRedBlackNode.LEAF;
node3.left = node1;
node3.right = node5;
node4.parent = node5;
node4.left = TestRedBlackNode.LEAF;
node4.right = TestRedBlackNode.LEAF;
node5.parent = node3;
node5.left = node4;
node5.right = node6;
node5.isRed = true;
node6.parent = node5;
node6.left = TestRedBlackNode.LEAF;
node6.right = TestRedBlackNode.LEAF;
node3.left = node3;
node3.right = node3;
node3.parent = node3;
assertFalse(isSubtreeValid(node3));
node3.left = node1;
node3.right = node5;
node3.parent = null;
node0.parent = node3;
assertFalse(isSubtreeValid(node3));
node0.parent = node1;
node1.right = node0;
assertFalse(isSubtreeValid(node3));
node1.right = node2;
node5.isRed = false;
assertFalse(isSubtreeValid(node3));
assertTrue(isSubtreeValid(node5));
node5.isRed = true;
node3.isRed = true;
assertFalse(isSubtreeValid(node3));
assertTrue(isSubtreeValid(node5));
node3.isRed = false;
node0.isRed = true;
node2.isRed = true;
node4.isRed = true;
node6.isRed = true;
assertFalse(isSubtreeValid(node3));
node0.isRed = false;
node2.isRed = false;
node4.isRed = false;
node6.isRed = false;
TestRedBlackNode.LEAF.isRed = true;
assertFalse(isSubtreeValid(node3));
TestRedBlackNode.LEAF.isRed = false;
TestRedBlackNode.LEAF.isValid = false;
assertFalse(isSubtreeValid(node3));
assertFalse(isSubtreeValid(TestRedBlackNode.LEAF));
TestRedBlackNode.LEAF.isValid = true;
node1.isValid = false;
assertFalse(isSubtreeValid(node3));
node1.isValid = true;
node3.value = 2;
node2.value = 3;
assertFalse(isOrderValid(node3, null));
assertFalse(
isOrderValid(node3, new Comparator<TestRedBlackNode>() {
@Override
public int compare(TestRedBlackNode node1, TestRedBlackNode node2) {
return node1.value - node2.value;
}
}));
node3.value = 3;
node2.value = 2;
node2.value = 4;
node4.value = 2;
assertFalse(isOrderValid(node3, null));
node2.value = 2;
node4.value = 4;
node0.value = 1;
node1.value = 0;
assertFalse(isOrderValid(node3, null));
node0.value = 0;
node1.value = 1;
// Do all of the assertions for which the tree is supposed to be valid at the end, to make sure we didn't make a
// mistake undoing any of the modifications
assertTrue(isSubtreeValid(node3));
assertTrue(isSubtreeValid(node1));
assertTrue(isSubtreeValid(node0));
assertTrue(isSubtreeValid(TestRedBlackNode.LEAF));
assertTrue(isOrderValid(node3, null));
assertTrue(isOrderValid(node1, null));
assertTrue(isOrderValid(node0, null));
assertTrue(isOrderValid(TestRedBlackNode.LEAF, null));
assertTrue(
isOrderValid(node3, new Comparator<TestRedBlackNode>() {
@Override
public int compare(TestRedBlackNode node1, TestRedBlackNode node2) {
return node1.value - node2.value;
}
}));
}
}

View File

@@ -0,0 +1,46 @@
package com.github.btrekkie.red_black_node.test;
import com.github.btrekkie.red_black_node.RedBlackNode;
/**
* A RedBlackNode for RedBlackNodeTest.
*/
class TestRedBlackNode extends RedBlackNode<TestRedBlackNode> {
/**
* The dummy leaf node.
*/
public static final TestRedBlackNode LEAF = new TestRedBlackNode();
/**
* The value stored in this node. "value" is unspecified if this is a leaf node.
*/
public int value;
/**
* Whether this node is considered valid, as in assertNodeIsValid().
*/
public boolean isValid = true;
public TestRedBlackNode(int value) {
this.value = value;
}
/**
* Constructs a new dummy leaf node.
*/
private TestRedBlackNode() {
}
@Override
public void assertNodeIsValid() {
if (!isValid) {
throw new RuntimeException("isValid is false");
}
}
@Override
public int compareTo(TestRedBlackNode other) {
return value - other.value;
}
}

View File

@@ -0,0 +1,99 @@
package com.github.btrekkie.sub_array_min;
/**
* A list of integers. SubArrayMin provides the ability to quickly determine the minimum value in a given sublist.
*/
/* We implement SubArrayMin using a red-black tree augmented by subtree size and minimum value. Using the subtree size
* augmentation, we can find the node at a given index.
*/
public class SubArrayMin {
/**
* The root node.
*/
private SubArrayMinNode root = SubArrayMinNode.LEAF;
/**
* Appends the specified value to the end of the list.
*/
public void add(int value) {
SubArrayMinNode newNode = new SubArrayMinNode(value);
newNode.left = SubArrayMinNode.LEAF;
newNode.right = SubArrayMinNode.LEAF;
if (root.isLeaf()) {
root = newNode;
newNode.augment();
} else {
SubArrayMinNode node = root.max();
node.right = newNode;
newNode.parent = node;
newNode.isRed = true;
root = newNode.fixInsertion();
}
}
/**
* Returns the node for the element with the specified index. Assumes "index" is in the range [0, root.size).
*/
private SubArrayMinNode getNode(int index) {
if (index < 0 || index >= root.size) {
throw new IndexOutOfBoundsException("Index " + index + " is not in the range [0, " + root.size + ")");
}
int rank = index;
SubArrayMinNode node = root;
while (rank != node.left.size) {
if (rank < node.left.size) {
node = node.left;
} else {
rank -= node.left.size + 1;
node = node.right;
}
}
return node;
}
/**
* Returns the minimum value in the subarray starting at index startIndex and ending at index endIndex - 1,
* inclusive. Assumes startIndex < endIndex, and assumes this contains indices startIndex and endIndex - 1.
*/
public int min(int startIndex, int endIndex) {
if (startIndex >= endIndex) {
throw new IllegalArgumentException("The start index must be less than the end index");
}
SubArrayMinNode start = getNode(startIndex);
SubArrayMinNode end = getNode(endIndex - 1);
SubArrayMinNode lca = start.lca(end);
int min = Math.min(lca.value, Math.min(start.value, end.value));
if (start != lca) {
if (start.right.min < min) {
min = start.right.min;
}
for (SubArrayMinNode node = start; node.parent != lca; node = node.parent) {
if (node.parent.left == node) {
if (node.parent.value < min) {
min = node.parent.value;
}
if (node.parent.right.min < min) {
min = node.parent.right.min;
}
}
}
}
if (end != lca) {
if (end.left.min < min) {
min = end.left.min;
}
for (SubArrayMinNode node = end; node.parent != lca; node = node.parent) {
if (node.parent.right == node) {
if (node.parent.value < min) {
min = node.parent.value;
}
if (node.parent.left.min < min) {
min = node.parent.left.min;
}
}
}
}
return min;
}
}

View File

@@ -0,0 +1,66 @@
package com.github.btrekkie.sub_array_min;
import com.github.btrekkie.red_black_node.RedBlackNode;
/**
* A node in a SubArrayMin object. See the comments for the implementation of that class.
*/
class SubArrayMinNode extends RedBlackNode<SubArrayMinNode> {
/**
* The dummy leaf node.
*/
public static final SubArrayMinNode LEAF = new SubArrayMinNode();
/**
* The element stored in the node. The value is unspecified if this is a leaf node.
*/
public final int value;
/**
* The number of elements in the subtree rooted at this node.
*/
public int size;
/**
* The minimum element in the subtree rooted at this node. This is Integer.MAX_VALUE if this is a leaf node.
*/
public int min;
public SubArrayMinNode(int value) {
this.value = value;
}
private SubArrayMinNode() {
value = 0;
min = Integer.MAX_VALUE;
}
@Override
public boolean augment() {
int newSize = left.size + right.size + 1;
int newMin = Math.min(value, Math.min(left.min, right.min));
if (newSize == size && newMin == min) {
return false;
} else {
size = newSize;
min = newMin;
return true;
}
}
@Override
public void assertNodeIsValid() {
int expectedSize;
int expectedMin;
if (isLeaf()) {
expectedSize = 0;
expectedMin = Integer.MAX_VALUE;
} else {
expectedSize = left.size + right.size + 1;
expectedMin = Math.min(value, Math.min(left.min, right.min));
}
if (size != expectedSize || min != expectedMin) {
throw new RuntimeException("The node's size or minimum value does not match that of the children");
}
}
}

View File

@@ -0,0 +1,41 @@
package com.github.btrekkie.sub_array_min.test;
import com.github.btrekkie.sub_array_min.SubArrayMin;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class SubArrayMinTest {
/**
* Tests SubArrayMin.
*/
@Test
public void test() {
SubArrayMin sam = new SubArrayMin();
sam.add(12);
sam.add(42);
sam.add(-3);
sam.add(16);
sam.add(5);
sam.add(8);
sam.add(4);
assertEquals(-3, sam.min(0, 7));
assertEquals(12, sam.min(0, 2));
assertEquals(-3, sam.min(2, 4));
assertEquals(12, sam.min(0, 1));
assertEquals(5, sam.min(3, 6));
assertEquals(4, sam.min(4, 7));
sam = new SubArrayMin();
for (int i = 0; i < 1000; i++) {
sam.add(-Integer.bitCount(i));
}
assertEquals(0, sam.min(0, 1));
assertEquals(-4, sam.min(0, 30));
assertEquals(-9, sam.min(0, 1000));
assertEquals(-9, sam.min(123, 777));
assertEquals(-8, sam.min(777, 888));
assertEquals(-6, sam.min(777, 788));
assertEquals(-9, sam.min(900, 1000));
}
}

View File

@@ -0,0 +1,276 @@
package com.github.leijurv;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalInt;
import static org.junit.Assert.assertEquals;
public class NavigableSurfaceTest {
@Test
public void testBasic() {
NavigableSurface surface = new NavigableSurface(10, 10, 10);
surface.placeBlock(0, 0, 0);
assertEquals(OptionalInt.empty(), surface.surfaceSize(new BetterBlockPos(0, 0, 0)));
assertEquals(1, surface.requireSurfaceSize(0, 1, 0));
surface.placeBlock(1, 0, 0);
assertEquals(2, surface.requireSurfaceSize(0, 1, 0));
surface.placeBlock(1, 0, 0);
surface.placeBlock(2, 0, 0);
surface.placeBlock(3, 0, 0);
surface.placeBlock(4, 0, 0);
surface.placeBlock(5, 0, 0);
// XXXXXX
assertEquals(6, surface.requireSurfaceSize(2, 1, 0));
surface.placeBlock(2, 1, 0);
assertEquals(OptionalInt.empty(), surface.surfaceSize(new BetterBlockPos(2, 1, 0)));
assertEquals(6, surface.requireSurfaceSize(2, 2, 0));
surface.placeBlock(2, 2, 0);
// X
// X
// XXXXXX
assertEquals(2, surface.requireSurfaceSize(0, 1, 0));
assertEquals(1, surface.requireSurfaceSize(2, 3, 0));
assertEquals(3, surface.requireSurfaceSize(3, 1, 0));
surface.placeBlock(1, 1, 0);
// X
// XX
// XXXXXX
assertEquals(3, surface.requireSurfaceSize(0, 1, 0));
assertEquals(3, surface.requireSurfaceSize(3, 1, 0));
surface.placeBlock(3, 2, 0);
// XX
// XX
// XXXXXX
assertEquals(4, surface.requireSurfaceSize(0, 1, 0));
assertEquals(2, surface.requireSurfaceSize(4, 1, 0));
surface.placeBlock(4, 1, 0);
// XX
// XX X
// XXXXXX
assertEquals(6, surface.requireSurfaceSize(0, 1, 0));
assertEquals(OptionalInt.empty(), surface.surfaceSize(new BetterBlockPos(3, 1, 0)));
surface.removeBlock(2, 2, 0);
// X
// XX X
// XXXXXX
assertEquals(6, surface.requireSurfaceSize(2, 2, 0));
surface.removeBlock(2, 1, 0);
// X
// X X
// XXXXXX
assertEquals(3, surface.requireSurfaceSize(1, 2, 0));
assertEquals(3, surface.requireSurfaceSize(3, 3, 0));
surface.removeBlock(3, 2, 0);
// X X
// XXXXXX
assertEquals(6, surface.requireSurfaceSize(0, 1, 0));
surface.removeBlock(1, 0, 0);
// X X
// X XXXX
assertEquals(6, surface.requireSurfaceSize(0, 1, 0));
surface.removeBlock(1, 1, 0);
// X
// X XXXX
assertEquals(1, surface.requireSurfaceSize(0, 1, 0));
assertEquals(4, surface.requireSurfaceSize(2, 1, 0));
}
private NavigableSurface makeFlatSurface(int SZ) {
NavigableSurface surface = new NavigableSurface(SZ, SZ, SZ);
for (int x = 0; x < SZ; x++) {
for (int z = 0; z < SZ; z++) {
surface.placeBlock(new BetterBlockPos(x, 0, z));
}
}
assertEquals(SZ * SZ, surface.requireSurfaceSize(0, 1, 0));
return surface;
}
@Test
public void testSurfaceSmall() {
makeFlatSurface(10);
}
@Test
public void testSurfaceMed() {
makeFlatSurface(100);
}
/*@Test
public void testSurfaceBig() { // 10x more on each side, so 100x more nodes total. youd expect this to be about 100x slower than testSurfaceMed, but it's actually 200x slower. this is ideally just because each graph operation is O(log^2 n) so n operations is O(n log^2 n), and hopefully not because of something that otherwise scales superlinearly
makeFlatSurface(1000);
}*/
// okay but its slow so we dont care
@Test
public void testStep() {
int SZ = 100;
int lineAt = SZ / 2;
NavigableSurface surface = makeFlatSurface(SZ);
for (int x = 0; x < SZ; x++) {
surface.placeBlock(x, 1, lineAt); // doesn't block the player since you can step over 1 block
}
assertEquals(SZ * SZ, surface.requireSurfaceSize(0, 1, 0));
}
@Test
public void testBlocked() {
int SZ = 100;
int lineAt = SZ / 2;
NavigableSurface surface = makeFlatSurface(SZ);
for (int x = 0; x < SZ; x++) {
surface.placeBlock(x, 2, lineAt); // does block the player since you can't step over 2 blocks
}
assertEquals(SZ * lineAt, surface.requireSurfaceSize(0, 1, 0));
assertEquals(SZ * (SZ - lineAt - 1), surface.requireSurfaceSize(0, 1, SZ - 1));
assertEquals(SZ, surface.requireSurfaceSize(0, 3, lineAt));
}
private void fillSurfaceInOrderMaintainingConnection(NavigableSurface surface, BetterBlockPos maintainConnectionTo, List<BetterBlockPos> iterationOrder) {
outer:
while (true) {
for (BetterBlockPos candidate : iterationOrder) {
if (surface.getBlock(candidate)) {
continue; // already placed
}
// let's try placing
surface.placeBlock(candidate);
if (surface.connected(candidate.upPlusY(), maintainConnectionTo)) {
// success, placed a block while retaining the path down to the ground
continue outer;
}
// fail :(
surface.removeBlock(candidate);
}
return;
}
}
@Test
public void testCastleWall() {
// build a single wall, but, never place a block that disconnects the surface
// (we expect to see a triangle)
int SZ = 20;
NavigableSurface surface = makeFlatSurface(SZ);
BetterBlockPos someOtherBlock = new BetterBlockPos(0, 1, 1); // won't be involved in the wall (since z=1)
List<BetterBlockPos> order = new ArrayList<>();
for (int y = 0; y < SZ; y++) {
for (int x = 0; x < SZ; x++) {
order.add(new BetterBlockPos(x, y, 0));
}
}
fillSurfaceInOrderMaintainingConnection(surface, someOtherBlock, order);
String shouldBe = "" +
"XX | | | \n" +
"XXX | | | \n" +
"XXXX | | | \n" +
"XXXXX | | | \n" +
"XXXXXX | | | \n" +
"XXXXXXX | | | \n" +
"XXXXXXXX | | | \n" +
"XXXXXXXXX | | | \n" +
"XXXXXXXXXX | | | \n" +
"XXXXXXXXXXX | | | \n" +
"XXXXXXXXXXXX | | | \n" +
"XXXXXXXXXXXXX | | | \n" +
"XXXXXXXXXXXXXX | | | \n" +
"XXXXXXXXXXXXXXX | | | \n" +
"XXXXXXXXXXXXXXXX | | | \n" +
"XXXXXXXXXXXXXXXXX | | | \n" +
"XXXXXXXXXXXXXXXXXX | | | \n" +
"XXXXXXXXXXXXXXXXXXX | | | \n" +
"XXXXXXXXXXXXXXXXXXXX| | | \n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n"; // double row is because we started with a flat surface, so there is another flat row behind this one to step back into
assertEquals(shouldBe, reportAllFourWalls(surface));
}
@Test
public void testCastleFourWalls() {
// build four walls, but, never place a block that disconnects the surface
// (we expect to see a zigzag cut out)
int SZ = 20;
NavigableSurface surface = makeFlatSurface(SZ);
BetterBlockPos someOtherBlock = new BetterBlockPos(SZ / 2, 1, SZ / 2); // center of the courtyard
List<BetterBlockPos> order = new ArrayList<>();
for (int y = 0; y < SZ; y++) {
for (int x = 0; x < SZ; x++) {
for (int z = 0; z < SZ; z++) {
boolean xOnEdge = x == 0 || x == SZ - 1;
boolean zOnEdge = z == 0 || z == SZ - 1;
if (!xOnEdge && !zOnEdge) {
continue; // in the courtyard
}
order.add(new BetterBlockPos(x, y, z));
}
}
}
fillSurfaceInOrderMaintainingConnection(surface, someOtherBlock, order);
String shouldBe = "" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXX XXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXX XXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXX XXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXX XXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXX XXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXX XXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXX XXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXX XXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXX XXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXX XXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXX XXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XX XXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|X XXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX| XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXX |XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXX X|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" +
"XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n";
assertEquals(shouldBe, reportAllFourWalls(surface));
}
private String reportAllFourWalls(NavigableSurface surface) {
StringBuilder report = new StringBuilder();
for (int y = surface.sizeY - 1; y >= 0; y--) {
// make a report of what all four walls look like
for (int x = 0; x < surface.sizeX; x++) {
report.append(surface.getBlock(new BetterBlockPos(x, y, 0)) ? 'X' : ' ');
}
report.append('|');
// start at 1 not 0 so that we don't repeat the last iteration of the previous loop (that would make the report look bad because the staircase would repeat one column for no reason)
for (int z = 1; z < surface.sizeZ; z++) {
report.append(surface.getBlock(new BetterBlockPos(surface.sizeX - 1, y, z)) ? 'X' : ' ');
}
report.append('|');
// same deal for starting at -2 rather than -1
for (int x = surface.sizeX - 2; x >= 0; x--) {
report.append(surface.getBlock(new BetterBlockPos(x, y, surface.sizeZ - 1)) ? 'X' : ' ');
}
report.append('|');
// and same again
for (int z = surface.sizeZ - 2; z > 0; z--) {
report.append(surface.getBlock(new BetterBlockPos(0, y, z)) ? 'X' : ' ');
}
report.append('\n');
}
return report.toString();
}
}