Merge remote-tracking branch 'dyncon/trimmed' into builder-2
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
276
src/test/java/com/github/leijurv/NavigableSurfaceTest.java
Normal file
276
src/test/java/com/github/leijurv/NavigableSurfaceTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user