Last active
September 28, 2018 03:09
-
-
Save nbness2/6178f6415189d903e3c97cae9620cfc6 to your computer and use it in GitHub Desktop.
Java RandomUtil
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()+")"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ? ")" : "]"); } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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