import static org.junit.Assert.*;
import org.junit.Test;
import java.util.Random;
import java.util.NoSuchElementException;
public class AddMaxTest {
// comparing doubles for exact equality is bad, so we have this helper function
static void assertClose(double expected, double actual) {
assertEquals(Math.round(actual*1000), Math.round(expected*1000));
}
// helper method in case I want to test a different class name
private AddMax create() {
return new DoubleTree();
}
@Test
public void add1Remove1() {
AddMax am = create();
am.add(3.7);
assertClose(3.7, am.removeMax());
}
@Test(expected = NoSuchElementException.class)
public void add1Remove2() {
AddMax am = create();
am.add(-1.1);
am.removeMax();
am.removeMax();
}
@Test
public void add3remove3() {
AddMax am = create();
am.add(1.1);
am.add(3.3);
am.add(2.2);
assertClose(3.3, am.removeMax());
assertClose(2.2, am.removeMax());
assertClose(1.1, am.removeMax());
}
@Test
public void addAfterEmpty() {
AddMax am = create();
am.add(9.8);
am.add(9.5);
assertClose(9.8, am.removeMax());
assertClose(9.5, am.removeMax());
am.add(9.7);
am.add(9.9);
assertClose(9.9, am.removeMax());
assertClose(9.7, am.removeMax());
}
@Test
public void twoAtOnce() {
AddMax am1 = create();
AddMax am2 = create();
am1.add(3.4);
am2.add(4.5);
am1.add(1.2);
am2.add(6.7);
am1.add(9.8);
assertClose(9.8, am1.removeMax());
assertClose(6.7, am2.removeMax());
assertClose(4.5, am2.removeMax());
assertClose(3.4, am1.removeMax());
assertClose(1.2, am1.removeMax());
}
@Test
public void add20Remove10() {
AddMax am = create();
double[] items = new double[20];
for (int i = 0; i < 20; ++i) {
items[i] = Math.sin(i+1);
am.add(items[i]);
}
java.util.Arrays.sort(items);
for (int i = 0; i < 10; ++i) {
assertClose(items[19-i], am.removeMax());
}
}
@Test
public void add2remove1repeat() {
int n = 15;
double[] items = new double[n+1];
AddMax am = create();
for (int i = 0; i < n; ++i) {
items[i] = Math.cos(i*.7 + 1);
items[i+1] = Math.cos(i*.7 + 1.35);
am.add(items[i]);
am.add(items[i+1]);
java.util.Arrays.sort(items, 0, i+2);
assertClose(items[i+1], am.removeMax());
}
}
@Test(timeout=1000)
public void speedRandom() {
int n = 400000;
Random rng = new Random(2020);
AddMax am = create();
for (int i = 0; i < n; ++i) {
am.add(rng.nextDouble());
am.add(rng.nextDouble());
am.removeMax();
}
}
@Test(timeout=1000)
public void speedOrdered() {
int n = 200000;
AddMax am = create();
for (int i = 0; i < n; ++i) {
am.add(i*.02 + .01);
am.add(i*.02);
assertClose(i*.02 + .01, am.removeMax());
}
for (int i = 1; i < n; ++i) {
am.add(i*-.03);
am.add(i*-.03 + .01);
assertClose((n-i)*.02, am.removeMax());
}
}
// helper function to remove max from an array and decrease size by 1
private double removeMaxFromArray(double[] arr, int size) {
assert size >= 1;
int maxInd = 0;
for (int i = 1; i < size; ++i) {
if (arr[i] > arr[maxInd]) maxInd = i;
}
double max = arr[maxInd];
arr[maxInd] = arr[size-1];
return max;
}
@Test
public void randomActions() {
Random rng = new Random(2000);
int rounds = 30;
int steps = 200;
int initialSize = 20;
for (int i = 0; i < rounds; ++i) {
AddMax am = create();
double[] items = new double[initialSize + steps];
for (int j = 0; j < initialSize; ++j) {
items[j] = rng.nextDouble();
am.add(items[j]);
}
int size = initialSize;
for (int j = 0; j < steps; ++j) {
if (size == 0 || rng.nextBoolean()) {
items[size++] = rng.nextDouble();
am.add(items[size-1]);
}
else {
double expected = removeMaxFromArray(items, size--);
double actual = am.removeMax();
assertClose(expected, actual);
}
}
}
}
}