Skip to content

Instantly share code, notes, and snippets.

@nbness2
Last active September 28, 2018 03:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nbness2/6178f6415189d903e3c97cae9620cfc6 to your computer and use it in GitHub Desktop.
Save nbness2/6178f6415189d903e3c97cae9620cfc6 to your computer and use it in GitHub Desktop.
Java RandomUtil
package JavaSrc;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Random;
import java.lang.reflect.Method;
public abstract class BaseRandom {
private int toInt(boolean bool) { return bool ? 1 : 0; }
protected Byte nextByte(Random random, byte lowerBound, byte upperBound, boolean inclusive) {
if (random == null) random = this._random;
short low = (short) (lowerBound - 1); //This is only necessary if you are using lowest possible value as first value
return (byte) (low + (random.nextDouble() * (upperBound - low + toInt(inclusive))));
}
protected Short nextShort(Random random, short lowerBound, short upperBound, boolean inclusive) {
if (random == null) random = this._random;
int low = lowerBound-1;
return (short) (low + (random.nextDouble() * (upperBound - low + toInt(inclusive))));
}
protected Integer nextInt(Random random, int lowerBound, int upperBound, boolean inclusive) {
if (random == null) random = this._random;
return (int) (lowerBound + (int) (random.nextDouble() * (upperBound - lowerBound + toInt(inclusive))));
}
protected Long nextLong(Random random, long lowerBound, long upperBound, boolean inclusive) {
if (random == null) random = this._random;
return lowerBound + (long) (random.nextDouble() * (upperBound - lowerBound + toInt(inclusive)));
}
protected Float nextFloat(Random random, float lowerBound, float upperBound, boolean inclusive) {
if (random == null) random = this._random;
return lowerBound + (float) (random.nextDouble() * (upperBound - lowerBound + (inclusive ? Float.MIN_VALUE : 0)));
}
protected Double nextDouble(Random random, double lowerBound, double upperBound, boolean inclusive) {
if (random == null) random = this._random;
return lowerBound + ((random.nextDouble() + Double.MIN_VALUE) * (upperBound - lowerBound + (inclusive ? Double.MIN_VALUE : 0)));
}
private HashMap<Class, Method> getNextByType = new HashMap<Class, Method>();
private Random _random;
BaseRandom() {
this(new Random());
}
BaseRandom(Random random) {
this._random = random == null ? new Random() : random;
for (Method m: BaseRandom.class.getDeclaredMethods()) {
if (m.getName().contains("next")) {
this.getNextByType.put(m.getReturnType(), m);
}
}
}
public <T extends Number & Comparable<T>> T random(T lowerBound, T upperBound, boolean inclusive, Random ... random) throws NullPointerException {
if (lowerBound == null || upperBound == null) { throw new NullPointerException(this.getClass().getName()+" cannot set first bound to null"); }
Random selectedRandom = random.length == 0 || random[0] == null ? this._random : random[0];
int compared = lowerBound.compareTo(upperBound);
T low = compared < 0 ? lowerBound : upperBound;
T high = compared < 0 ? upperBound : lowerBound;
Method selectedMethod = this.getNextByType.get(low.getClass());
T result;
try {
result = (T) selectedMethod.invoke(this, selectedRandom, low, high, inclusive);
} catch (IllegalAccessException | InvocationTargetException e) {
return low;
}
return result;
}
}
package JavaSrc.util;
public class Pair<A, B> {
public final A first;
public final B second;
public Pair(A first, B second) {
this.first = first;
this.second = second;
}
public String toString() {
return "Pair("+this.first.toString()+", "+this.second.toString()+")";
}
}
package JavaSrc;
public class RandomRange <T extends Number & Comparable<T>> extends BaseRandom {
private boolean inclusive;
private final T minimum;
private final T maximum;
RandomRange(T minimum, T maximum, boolean inclusive) throws NullPointerException {
super();
if (minimum == null || maximum == null) { throw new NullPointerException(this.getClass().getName()+" cannot set min or max to null"); }
int compare = minimum.compareTo(maximum);
this.minimum = compare < 0 ? minimum : maximum;
this.maximum = compare < 0 ? maximum : minimum;
this.inclusive = inclusive;
}
RandomRange(T minimum, T maximum) {
this(minimum, maximum, false);
}
public T pick(boolean inclusive) { return this.random(this.minimum, this.maximum, inclusive); }
public T pick() { return this.pick(this.inclusive); }
@Override public String toString() { return this.getClass().getName()+"("+this.minimum+" -> "+this.maximum+(this.inclusive ? ")" : "]"); }
}
package JavaSrc;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import JavaSrc.util.Pair;
public class RandomTable <T> extends BaseRandom {
private final List<T> items;
private final HashMap<T, Integer> resultsMap = new HashMap<>();
private final ArrayList<T> resultsList = new ArrayList<>();
RandomTable(T[] items){ this(Arrays.asList(items)); }
public RandomTable(List<T> items) {
for (T item: items) {
if (item == null)
throw new NullPointerException("Nulls not allowed in " + this.getClass().getName());
}
this.items = new ArrayList<T>(items);
}
protected void sortItemsBy(List<Integer> other) {
List<T> sorted = IntStream.range(0, other.size())
.mapToObj(i -> new Pair<>(this.items.get(i), other.get(i)))
.sorted(Comparator.comparingInt(p -> p.second))
.map(o -> o.first)
.collect(Collectors.toList());
for (int idx = 0; idx < this.items.size(); idx++)
this.items.set(idx, sorted.get(idx));
Collections.sort(other);
}
protected T get(int index) { // pythony indexing
int finalIndex = index;
if (finalIndex > this.items.size() || finalIndex < -this.items.size())
throw new ArrayIndexOutOfBoundsException("Index "+finalIndex+" out of range for length: "+this.items.size());
finalIndex += finalIndex < 0 ? this.items.size() : 0;
return this.items.get(finalIndex);
}
protected int indexOf(T item) {
if (!items.contains(item))
throw new ArrayStoreException("Item "+item.toString()+" not contained.");
return this.items.indexOf(item);
}
protected T pickInternal(int modifier) { return this.items.get(this.random(0, this.items.size(), false)); }
public T pick() { return this.pick(0); }
public T pick(int modifier) { return this.pickInternal(modifier); }
public HashMap<T, Integer> pickMap(int pickAmount) { return this.pickMap(pickAmount, 0); }
public HashMap<T, Integer> pickMap(int pickAmount, int modifier) {
this.resultsMap.clear();
T result;
Integer previousCount;
for (int run = 0; run < pickAmount; run++) {
result = this.pickInternal(modifier);
this.resultsMap.putIfAbsent(result, 0);
previousCount = this.resultsMap.get(result);
this.resultsMap.put(result, previousCount + 1);
}
return (HashMap<T, Integer>) this.resultsMap.clone();
}
public List<T> pickOrdered(int pickAmount) { return this.pickOrdered(pickAmount, 0); }
public List<T> pickOrdered(int pickAmount, int modifier) {
this.resultsList.clear();
for (int run = 0; run < pickAmount; run++)
this.resultsList.add(this.pickInternal(modifier));
return (ArrayList<T>) this.resultsList.clone();
}
public RandomTable<T> copyOf() { return new RandomTable<T>(this.items); }
}
package JavaSrc;
import java.util.*;
public class Tests {
public static class TestBR extends BaseRandom {}
public static void main(String[] args) {
final int min = -250_000;
final int max = 250_000;
final boolean inclusive = false;
final int pickAmount = 100_000;
List<String> items = Arrays.asList(
"String1", "String2", "String3", "String4", "String5", "String6", "String7", "String8"
);
List<Integer> weights = Arrays.asList(
5, 6, 1, 4, 25, 8, 10, 2
);
BRTest(min, max, inclusive);
System.out.println();
RRTest(min, max, !inclusive);
System.out.println();
RTTest(items, pickAmount);
System.out.println();
WTTest(items, weights, pickAmount);
}
static void BRTest(int min, int max, boolean inclusive) {
TestBR br = new TestBR();
int result;
int counter = 0;
boolean isMin = false;
boolean isMax = false;
int inclusiveMax = max - (inclusive ? 0 : 1);
System.out.println("Starting BaseRandom test from "+min+" to "+max+" "+ (inclusive ? "" : "not ")+"inclusive");
while (!isMin || !isMax) {
result = br.random(min, max, inclusive);
counter++;
if (!isMin && result == min) {
System.out.println("BaseRandom min ("+min+") picked at "+counter+" picks");
isMin = true;
} else if (!isMax && result == inclusiveMax) {
System.out.println("BaseRandom max ("+inclusiveMax+") picked at "+counter+" picks");
isMax = true;
} else if (result > inclusiveMax || result < min) {
System.out.println("Result: "+result+" out of range");
}
}
System.out.println("BaseRandom finished at "+counter+" picks");
}
static void RRTest(int min, int max, boolean inclusive) {
RandomRange<Integer> rr = new RandomRange<>(min, max, true);
int result;
int counter = 0;
boolean isMin = false;
boolean isMax = false;
int inclusiveMax = max - (inclusive ? 0 : 1);
System.out.println("Starting RandomRange test from "+min+" to "+max+" "+(inclusive ? "" : "not ") + "inclusive");
while (!isMin || !isMax) {
result = rr.pick();
counter++;
if (!isMin && result == min) {
System.out.println("RandomRange min (" + min + ") picked at " + counter + " picks");
isMin = true;
} else if (!isMax && result == inclusiveMax) {
System.out.println("RandomRange max (" + inclusiveMax + ") picked at " + counter + " picks");
isMax = true;
} else if (result > inclusiveMax || result < min) {
System.out.println("Result: " + result + " out of range");
}
}
System.out.println("RandomRange finished at "+counter+" picks");
}
static <T> void RTTest(List<T> items, int pickAmount) {
System.out.println("Starting RandomTable test with items: "+items.toString());
System.out.println("Each item has a "+(100.0 / items.size()) + "% chance to be picked");
RandomTable<T> rt = new RandomTable<>(items);
Map<T, Integer> results = rt.pickMap(pickAmount);
System.out.println("RandomTable finished, results over "+pickAmount+" picks\n\t"+results);
}
static <T> void WTTest(List<T> items, List<Integer> weights, int pickAmount) {
WeightedTable<T> wt = new WeightedTable<>(items, weights);
System.out.println("Starting WrightedTable test wit items:\n\t" +
items.toString()+"\n\tweights: " +
weights.toString()+"\n\ttotal weight:" +
wt.totalWeight
);
Map<T, Integer> results = wt.pickMap(pickAmount);
Map<T, Double> expected = new HashMap<>();
for (T key: results.keySet()) {
expected.putIfAbsent(key, wt.chanceOf(key));
}
System.out.println("Expected chances:\n\t"+expected);
System.out.println("Actual result out of "+pickAmount+" picks:\n\t"+results);
}
}
package JavaSrc;
import java.util.List;
public class WeightedTable<T> extends RandomTable<T> {
private final List<Integer> weights;
public final long totalWeight;
private final RandomRange<Long> weightRange;
private static <T> int amountOf(List<T> items, T item) {
int count = 0;
for (T containedItem : items)
if (containedItem.equals(item)) count++;
return count;
}
WeightedTable(List<T> items, List<Integer> weights) {
super(items);
for (Integer weight: weights) {
if (amountOf(weights, weight) > 1)
throw new ArrayStoreException("Cannot have more than 1 item per weight in "+this.getClass().getSimpleName());
else if (weight < 1)
throw new ArrayStoreException("Weight cannot be less than 1 in "+this.getClass().getSimpleName());
}
this.weights = weights;
this.sortItemsBy(this.weights);
long finalWeight = 0L;
for (Integer weight: this.weights)
finalWeight += weight;
this.totalWeight = finalWeight;
this.weightRange = new RandomRange<>(1L, this.totalWeight, true);
}
@Override public T pickInternal(int modifier) {
double pickedWeight = this.weightRange.pick() * (1.0 / (1 + ((double) modifier / 100.0)));
for (int weightIndex = 0; weightIndex < this.weights.size(); weightIndex++) {
pickedWeight -= this.weights.get(weightIndex);
if (pickedWeight <= 0) return this.get(weightIndex);
}
return this.get(-1);
}
public double chanceOf(T item) {
int selectedWeight = this.weights.get(this.indexOf(item));
return ((double) selectedWeight / this.totalWeight) * 100;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment