package tc.oc.commons.random;
import java.util.NoSuchElementException;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import tc.oc.commons.core.random.Entropy;
import tc.oc.commons.core.random.MutableEntropy;
import tc.oc.commons.core.random.WeightedRandomChooser;
import tc.oc.commons.core.random.MutableWeightedRandomChooser;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import static org.junit.Assert.*;
import static tc.oc.test.Assert.*;
public class WeightedRandomChooserTest {
static final double Δ = 0.00001; // For floating-point comparisons
static final long SEED = 2905696505089501646L;
Entropy entropy;
@Before
public void setUp() throws Exception {
entropy = new MutableEntropy(SEED);
}
/**
* Run 10,000 trials and assert the given probability within 1/100
*
* This has a small chance of failing, but since we use a fixed random seed,
* it will do the same thing for every test run. If it starts failing, I guess
* we just change the SEED.
*/
<T, N extends Number> void assertProbability(WeightedRandomChooser<T, N> chooser, T choice, double probability) {
int n = 0;
for(int i = 0; i < 10000; i++) {
if(choice.equals(chooser.choose(entropy))) n++;
entropy.advance();
}
assertEquals(probability, n / 10000D, 0.01);
}
void assertTotalWeight(double totalWeight, WeightedRandomChooser<?, ?> chooser) {
assertEquals(totalWeight, chooser.totalWeight(), Δ);
}
@Test
public void empty() throws Exception {
ImmutableWeightedRandomChooser<?, ?> chooser = new ImmutableWeightedRandomChooser<>(ImmutableMap.of());
assertTrue(chooser.isEmpty());
assertTotalWeight(0, chooser);
assertThrows(NoSuchElementException.class, () -> chooser.choose(entropy));
}
@Test
public void immutable() throws Exception {
ImmutableWeightedRandomChooser<String, Integer> chooser = new ImmutableWeightedRandomChooser<>(ImmutableMap.of(
"One", 1,
"Two", 2,
"Three", 3
));
assertFalse(chooser.isEmpty());
assertTotalWeight(6, chooser);
assertProbability(chooser, "One", 1 / 6D);
assertProbability(chooser, "Two", 2 / 6D);
assertProbability(chooser, "Three", 3 / 6D);
}
@Test
public void mutable() throws Exception {
MutableWeightedRandomChooser<String, Integer> chooser = new MutableWeightedRandomChooser<>();
chooser.add("One", 1);
assertTotalWeight(1, chooser);
assertProbability(chooser, "One", 1 / 1D);
chooser.add("Two", 2);
assertTotalWeight(3, chooser);
assertProbability(chooser, "One", 1 / 3D);
assertProbability(chooser, "Two", 2 / 3D);
chooser.add("Three", 3);
assertTotalWeight(6, chooser);
assertProbability(chooser, "One", 1 / 6D);
assertProbability(chooser, "Two", 2 / 6D);
assertProbability(chooser, "Three", 3 / 6D);
chooser.remove("One");
assertTotalWeight(5, chooser);
assertProbability(chooser, "Two", 2 / 5D);
assertProbability(chooser, "Three", 3 / 5D);
chooser.remove("Two");
assertTotalWeight(3, chooser);
assertProbability(chooser, "Three", 3 / 3D);
chooser.remove("Three");
assertTotalWeight(0, chooser);
assertThrows(NoSuchElementException.class, () -> chooser.choose(entropy));
}
}