Skip to content

Instantly share code, notes, and snippets.

@Silthus
Created January 9, 2021 18:06
Show Gist options
  • Save Silthus/683f0b7d11dd5af0c2f2b094f0cb8f73 to your computer and use it in GitHub Desktop.
Save Silthus/683f0b7d11dd5af0c2f2b094f0cb8f73 to your computer and use it in GitHub Desktop.
Pseudo Random Generator Comparison
public interface IPseudoRandomGenerator {
/**
* The iteration count is increased on every failure and reset on a success.
* <p>You can reset it manually by calling {@link #reset()}.
*
* @return the current iteration count
*/
int iteration();
float chance();
float baseChance();
/**
* Hits the generator with a random number and checks if it is below the next chance.
* <p>Returns true if the check was successful and resets the iteration count.
* <p>Returns false if the check was a failure and increases the iteration count by one.
*
* @return the result of the random chance check against the iteration and base chance
*/
boolean hit();
}
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class Main {
private static final long SEED = 1610212414097L;
private static final int ITERATIONS = Integer.MAX_VALUE;
private static final float CHANCE = 0.25f;
public static void main(String[] args) {
IPseudoRandomGenerator silthus = PseudoRandomGenerator.create(CHANCE, 1.0f, new Random(SEED));
System.out.println("----- Silthus's Generator -----");
System.out.println("resets iteration to 0 on success");
System.out.println("nextChance: chance(" + CHANCE + ") * (iteration + 1)");
System.out.println();
calculate(silthus);
System.out.println();
IPseudoRandomGenerator fs = PseudoRandomGeneratorFS.create(CHANCE, 1.0f, new Random(SEED));
System.out.println("----- ForbiddenSoul's Generator -----");
System.out.println("resets iteration to 0 on success");
System.out.println("nextChance: chance(" + CHANCE + ") + (iteration * baseChance(" + fs.baseChance() + ")");
System.out.println();
calculate(fs);
System.out.println();
IPseudoRandomGenerator fs2 = PseudoRandomGeneratorFS2.create(CHANCE, 1.0f, new Random(SEED));
System.out.println("----- ForbiddenSoul's v2 Generator -----");
System.out.println("nextChance: chance(" + CHANCE + ") + (failures * baseChance(" + fs.baseChance() + ") - (successes * baseChance(" + fs.baseChance() + ")");
System.out.println();
calculate(fs2);
}
private static void calculate(IPseudoRandomGenerator generator) {
Map<Integer, Integer> hitCount = new HashMap<>();
int successes = 0;
for (int i = 0; i < ITERATIONS; i++) {
int iteration = generator.iteration() + 1;
if (generator.hit()) {
successes++;
hitCount.put(iteration, hitCount.getOrDefault(iteration, 0) + 1);
}
}
for (Map.Entry<Integer, Integer> entry : hitCount.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue() + " (" + String.format("%.2f", (entry.getValue() * 1.0d / successes) * 100.0d) + "%)");
}
}
}
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* The PseudoRandomGenerator is used to test an increasing chance based of the initial chance.
* <p>
* Use it to align the preceived chance of randomness for users with the actual randomness.
* <p>
* The generator will keep track of an iteration counter that will increase with every
* failed check and increase the chance for the next check. It resets after the chance was hit.
* <p>
* By default the base chance is multiplied with the iteration count. You can modify this
* by adjusting the {@link #multiplier()}. It defaults to 1.0f.
* <p>
* The base {@link #chance()} is calculated when creating the pseudo random generator based off
* the following formula: <pre>chance / ((1.0f / chance) - 1.0f)</pre>
* The chance cannot not be greater than 1 or less than 0.
* Any value above or below will be set to the min or max value.
* <br><p>
* You can {@link #reset()} the current iteration at any time and calculate the {@link #nextChance()}
* for the current iteration.
* <p>However when using this generator your practically only need to create it with your
* chance and optional multiplier and then call the {@link #hit()} method everytime the chance
* should be checked. The iteration counter will increase and reset automatically every time {@code hit()} is called.
* <br><pre>{@code
* // a chance of 25% will result in an initial base chance of 8.3%
* // and increase with every failed hit multiplied by the failed iterations
* // e.g.: after three failed iterations the nextChance() will be 33% and 41,5% after four failed attempts
* PseudoRandomGenerator random = PseudoRandomGenerator.create(0.25f);
* if (random.hit()) {
* // success
* }
* }</pre>
*
* @author Michael Reichenbach<michael@reichenbach.in>
* @since 08-01-2020
* @version 2
*/
public final class PseudoRandomGenerator implements IPseudoRandomGenerator {
/**
* Creates a new PseudoRandomGenerator with the given chance.
* <p>The generator will use a default interation multiplier of 1.0f.
*
* @param chance the chance of the pseudo random generator as percentage.
* A chance greater than {@code 1.0} will be set to {@code 1.0} and less then {@code 0} will become {@code 0}.
* e.g.: {@code 0.25f} represents a chance of 25%.
* @return the created {@link PseudoRandomGenerator} with a fresh {@link Random} seed.
*/
public static IPseudoRandomGenerator create(float chance) {
return new PseudoRandomGenerator(chance);
}
/**
* Creates a new PseudoRandomGenerator with the given chance using the supplied iteration multiplier.
*
* @param chance the chance of the pseudo random generator as percentage.
* A chance greater than {@code 1.0} will be set to {@code 1.0} and less then {@code 0} will become {@code 0}.
* e.g.: {@code 0.25f} represents a chance of 25%.
* @param multiplier the multiplier that is multiplied with the iteration count and base chance.
* This defaults to {@code 1.0f} when using the {@link #create(float)} method.
* @return the created {@link PseudoRandomGenerator} with a fresh {@link Random} seed.
*/
public static IPseudoRandomGenerator create(float chance, float multiplier) {
return new PseudoRandomGenerator(chance, multiplier);
}
public static IPseudoRandomGenerator create(float chance, float multiplier, Random random) {
return new PseudoRandomGenerator(chance, multiplier, random);
}
private final Random random;
private final float chance;
private final float multiplier;
private int iteration = 0;
private PseudoRandomGenerator(float chance, float multiplier, Random random) {
this.random = random;
this.chance = calculateBaseChance(chance);
this.multiplier = multiplier;
}
private PseudoRandomGenerator(float chance, float multiplier) {
this(chance, multiplier, ThreadLocalRandom.current());
}
private PseudoRandomGenerator(float chance) {
this(chance, 1.0f);
}
/**
* Gets the base chance that was intially calculated when creating the generator.
* <p>Use the {@link #nextChance()} method the retrieve the relative chance based off the current iteration.
*
* @return the base chance of this pseudo random generator
*/
public float chance() {
return this.chance;
}
public float baseChance() {
return this.chance;
}
/**
* The multiplier is multiplied with the iteration count
* and base chance to calculate the {@link #nextChance()}.
*
* @return the multiplier that calculates the next chance
*/
public float multiplier() {
return this.multiplier;
}
@Override
public int iteration() {
return this.iteration;
}
/**
* Sets the iteration of this generator to the given value.
* <p>Any value below zero will be set to zero.
*
* @param iteration the iteration that this generator is set to
* @return this generator
*/
public IPseudoRandomGenerator iteration(int iteration) {
if (iteration < 0) iteration = 0;
this.iteration = iteration;
return this;
}
/**
* Calculates the chance for the next iteration.
* <p>The next chance is calculated like this:
* <pre>{@code
* chance() * ((iteration() + 1) * multiplier())
* }</pre>
*
* @return the chance of the next iteration
*/
public float nextChance() {
return chance() * ((iteration() + 1) * multiplier());
}
/**
* Resets the iteration count of this generator back to zero.
*
* @return this generator
*/
public IPseudoRandomGenerator reset() {
return iteration(0);
}
@Override
public boolean hit() {
if (random.nextFloat() < nextChance()) {
reset();
return true;
}
iteration++;
return false;
}
private float calculateBaseChance(float chance) {
if (chance > 1.0) chance = 1.0f;
if (chance <= 0) chance = 1.0f;
// lets calculate the base chance by taking the number of times the action should hit
// e.g. with a chance of 0.25 -> 25% 1/4 should be a hit
return chance / ((1.0f / chance) - 1.0f);
}
}
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* The PseudoRandomGenerator is used to test an increasing chance based of the initial chance.
* <p>
* Use it to align the preceived chance of randomness for users with the actual randomness.
* <p>
* The generator will keep track of an iteration counter that will increase with every
* failed check and increase the chance for the next check. It resets after the chance was hit.
* <p>
* By default the base chance is multiplied with the iteration count. You can modify this
* by adjusting the {@link #multiplier()}. It defaults to 1.0f.
* <p>
* The base {@link #chance()} is calculated when creating the pseudo random generator based off
* the following formula: <pre>chance / ((1.0f / chance) - 1.0f)</pre>
* The chance cannot not be greater than 1 or less than 0.
* Any value above or below will be set to the min or max value.
* <br><p>
* You can {@link #reset()} the current iteration at any time and calculate the {@link #nextChance()}
* for the current iteration.
* <p>However when using this generator your practically only need to create it with your
* chance and optional multiplier and then call the {@link #hit()} method everytime the chance
* should be checked. The iteration counter will increase and reset automatically every time {@code hit()} is called.
* <br><pre>{@code
* // a chance of 25% will result in an initial base chance of 8.3%
* // and increase with every failed hit multiplied by the failed iterations
* // e.g.: after three failed iterations the nextChance() will be 33% and 41,5% after four failed attempts
* PseudoRandomGenerator random = PseudoRandomGenerator.create(0.25f);
* if (random.hit()) {
* // success
* }
* }</pre>
*
* @author Michael Reichenbach<michael@reichenbach.in>
* @since 08-01-2020
* @version 2
*/
public final class PseudoRandomGeneratorFS implements IPseudoRandomGenerator {
/**
* Creates a new PseudoRandomGenerator with the given chance.
* <p>The generator will use a default interation multiplier of 1.0f.
*
* @param chance the chance of the pseudo random generator as percentage.
* A chance greater than {@code 1.0} will be set to {@code 1.0} and less then {@code 0} will become {@code 0}.
* e.g.: {@code 0.25f} represents a chance of 25%.
* @return the created {@link PseudoRandomGeneratorFS} with a fresh {@link Random} seed.
*/
public static IPseudoRandomGenerator create(float chance) {
return new PseudoRandomGeneratorFS(chance);
}
/**
* Creates a new PseudoRandomGenerator with the given chance using the supplied iteration multiplier.
*
* @param chance the chance of the pseudo random generator as percentage.
* A chance greater than {@code 1.0} will be set to {@code 1.0} and less then {@code 0} will become {@code 0}.
* e.g.: {@code 0.25f} represents a chance of 25%.
* @param multiplier the multiplier that is multiplied with the iteration count and base chance.
* This defaults to {@code 1.0f} when using the {@link #create(float)} method.
* @return the created {@link PseudoRandomGeneratorFS} with a fresh {@link Random} seed.
*/
public static IPseudoRandomGenerator create(float chance, float multiplier) {
return new PseudoRandomGeneratorFS(chance, multiplier);
}
public static IPseudoRandomGenerator create(float chance, float multiplier, Random random) {
return new PseudoRandomGeneratorFS(chance, multiplier, random);
}
private final Random random;
private final float chance;
private final float baseChance;
private final float multiplier;
private int iteration = 0;
private PseudoRandomGeneratorFS(float chance, float multiplier, Random random) {
this.random = random;
this.chance = chance;
this.baseChance = calculateBaseChance(chance);
this.multiplier = multiplier;
}
private PseudoRandomGeneratorFS(float chance, float multiplier) {
this(chance, multiplier, ThreadLocalRandom.current());
}
private PseudoRandomGeneratorFS(float chance) {
this(chance, 1.0f);
}
/**
* Gets the base chance that was intially calculated when creating the generator.
* <p>Use the {@link #nextChance()} method the retrieve the relative chance based off the current iteration.
*
* @return the base chance of this pseudo random generator
*/
public float chance() {
return this.chance;
}
public float baseChance() {
return this.baseChance;
}
/**
* The multiplier is multiplied with the iteration count
* and base chance to calculate the {@link #nextChance()}.
*
* @return the multiplier that calculates the next chance
*/
public float multiplier() {
return this.multiplier;
}
/**
* The iteration count is increased on every failure and reset on a success.
* <p>You can reset it manually by calling {@link #reset()}.
*
* @return the current iteration count
*/
public int iteration() {
return this.iteration;
}
/**
* Sets the iteration of this generator to the given value.
* <p>Any value below zero will be set to zero.
*
* @param iteration the iteration that this generator is set to
* @return this generator
*/
public PseudoRandomGeneratorFS iteration(int iteration) {
if (iteration < 0) iteration = 0;
this.iteration = iteration;
return this;
}
/**
* Calculates the chance for the next iteration.
* <p>The next chance is calculated like this:
* <pre>{@code
* chance() * ((iteration() + 1) * multiplier())
* }</pre>
*
* @return the chance of the next iteration
*/
public float nextChance() {
return chance() + (iteration() * baseChance);
}
/**
* Resets the iteration count of this generator back to zero.
*
* @return this generator
*/
public PseudoRandomGeneratorFS reset() {
return iteration(0);
}
/**
* Hits the generator with a random number and checks if it is below the next chance.
* <p>Returns true if the check was successful and resets the iteration count.
* <p>Returns false if the check was a failure and increases the iteration count by one.
*
* @return the result of the random chance check against the iteration and base chance
*/
public boolean hit() {
if (random.nextFloat() < nextChance()) {
reset();
return true;
}
iteration++;
return false;
}
private float calculateBaseChance(float chance) {
if (chance > 1.0) chance = 1.0f;
if (chance <= 0) chance = 1.0f;
// lets calculate the base chance by taking the number of times the action should hit
// e.g. with a chance of 0.25 -> 25% 1/4 should be a hit
return chance / ((1.0f / chance) - 1.0f);
}
}
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* The PseudoRandomGenerator is used to test an increasing chance based of the initial chance.
* <p>
* Use it to align the preceived chance of randomness for users with the actual randomness.
* <p>
* The generator will keep track of an iteration counter that will increase with every
* failed check and increase the chance for the next check. It resets after the chance was hit.
* <p>
* By default the base chance is multiplied with the iteration count. You can modify this
* by adjusting the {@link #multiplier()}. It defaults to 1.0f.
* <p>
* The base {@link #chance()} is calculated when creating the pseudo random generator based off
* the following formula: <pre>chance / ((1.0f / chance) - 1.0f)</pre>
* The chance cannot not be greater than 1 or less than 0.
* Any value above or below will be set to the min or max value.
* <br><p>
* You can {@link #reset()} the current iteration at any time and calculate the {@link #nextChance()}
* for the current iteration.
* <p>However when using this generator your practically only need to create it with your
* chance and optional multiplier and then call the {@link #hit()} method everytime the chance
* should be checked. The iteration counter will increase and reset automatically every time {@code hit()} is called.
* <br><pre>{@code
* // a chance of 25% will result in an initial base chance of 8.3%
* // and increase with every failed hit multiplied by the failed iterations
* // e.g.: after three failed iterations the nextChance() will be 33% and 41,5% after four failed attempts
* PseudoRandomGenerator random = PseudoRandomGenerator.create(0.25f);
* if (random.hit()) {
* // success
* }
* }</pre>
*
* @author Michael Reichenbach<michael@reichenbach.in>
* @since 08-01-2020
* @version 2
*/
public final class PseudoRandomGeneratorFS2 implements IPseudoRandomGenerator {
/**
* Creates a new PseudoRandomGenerator with the given chance.
* <p>The generator will use a default interation multiplier of 1.0f.
*
* @param chance the chance of the pseudo random generator as percentage.
* A chance greater than {@code 1.0} will be set to {@code 1.0} and less then {@code 0} will become {@code 0}.
* e.g.: {@code 0.25f} represents a chance of 25%.
* @return the created {@link PseudoRandomGeneratorFS2} with a fresh {@link Random} seed.
*/
public static IPseudoRandomGenerator create(float chance) {
return new PseudoRandomGeneratorFS2(chance);
}
/**
* Creates a new PseudoRandomGenerator with the given chance using the supplied iteration multiplier.
*
* @param chance the chance of the pseudo random generator as percentage.
* A chance greater than {@code 1.0} will be set to {@code 1.0} and less then {@code 0} will become {@code 0}.
* e.g.: {@code 0.25f} represents a chance of 25%.
* @param multiplier the multiplier that is multiplied with the iteration count and base chance.
* This defaults to {@code 1.0f} when using the {@link #create(float)} method.
* @return the created {@link PseudoRandomGeneratorFS2} with a fresh {@link Random} seed.
*/
public static IPseudoRandomGenerator create(float chance, float multiplier) {
return new PseudoRandomGeneratorFS2(chance, multiplier);
}
public static IPseudoRandomGenerator create(float chance, float multiplier, Random random) {
return new PseudoRandomGeneratorFS2(chance, multiplier, random);
}
private final Random random;
private final float chance;
private final float baseChance;
private final float multiplier;
private int iteration = 0;
private int successes = 0;
private int failures = 0;
private PseudoRandomGeneratorFS2(float chance, float multiplier, Random random) {
this.random = random;
this.chance = chance;
this.baseChance = calculateBaseChance(chance);
this.multiplier = multiplier;
}
private PseudoRandomGeneratorFS2(float chance, float multiplier) {
this(chance, multiplier, ThreadLocalRandom.current());
}
private PseudoRandomGeneratorFS2(float chance) {
this(chance, 1.0f);
}
/**
* Gets the base chance that was intially calculated when creating the generator.
* <p>Use the {@link #nextChance()} method the retrieve the relative chance based off the current iteration.
*
* @return the base chance of this pseudo random generator
*/
public float chance() {
return this.chance;
}
public float baseChance() {
return this.baseChance;
}
/**
* The multiplier is multiplied with the iteration count
* and base chance to calculate the {@link #nextChance()}.
*
* @return the multiplier that calculates the next chance
*/
public float multiplier() {
return this.multiplier;
}
/**
* The iteration count is increased on every failure and reset on a success.
* <p>You can reset it manually by calling {@link #reset()}.
*
* @return the current iteration count
*/
public int iteration() {
return this.iteration;
}
/**
* Sets the iteration of this generator to the given value.
* <p>Any value below zero will be set to zero.
*
* @param iteration the iteration that this generator is set to
* @return this generator
*/
public PseudoRandomGeneratorFS2 iteration(int iteration) {
if (iteration < 0) iteration = 0;
this.iteration = iteration;
return this;
}
/**
* Calculates the chance for the next iteration.
* <p>The next chance is calculated like this:
* <pre>{@code
* chance() * ((iteration() + 1) * multiplier())
* }</pre>
*
* @return the chance of the next iteration
*/
public float nextChance() {
return chance() + (failures * baseChance) - (successes * baseChance);
}
/**
* Resets the iteration count of this generator back to zero.
*
* @return this generator
*/
public PseudoRandomGeneratorFS2 reset() {
return iteration(0);
}
/**
* Hits the generator with a random number and checks if it is below the next chance.
* <p>Returns true if the check was successful and resets the iteration count.
* <p>Returns false if the check was a failure and increases the iteration count by one.
*
* @return the result of the random chance check against the iteration and base chance
*/
public boolean hit() {
if (random.nextFloat() < nextChance()) {
reset();
successes++;
return true;
} else {
failures++;
}
iteration++;
return false;
}
private float calculateBaseChance(float chance) {
if (chance > 1.0) chance = 1.0f;
if (chance <= 0) chance = 1.0f;
// lets calculate the base chance by taking the number of times the action should hit
// e.g. with a chance of 0.25 -> 25% 1/4 should be a hit
return chance / ((1.0f / chance) - 1.0f);
}
}
----- Silthus's Generator -----
resets iteration to 0 on success
nextChance: chance(0.25) * (iteration + 1)
1: 44341432 (8.33%)
2: 81295346 (15.28%)
3: 101611626 (19.10%)
4: 101614106 (19.10%)
5: 84672084 (15.91%)
6: 59274814 (11.14%)
7: 34576154 (6.50%)
8: 16465103 (3.09%)
9: 6170278 (1.16%)
10: 1715980 (0.32%)
11: 314819 (0.06%)
12: 28475 (0.01%)
----- ForbiddenSoul's Generator -----
resets iteration to 0 on success
nextChance: chance(0.25) + (iteration * baseChance(0.083333336)
1: 193499790 (25.00%)
2: 193499148 (25.00%)
3: 161245141 (20.83%)
4: 112888333 (14.58%)
5: 65838645 (8.51%)
6: 31354807 (4.05%)
7: 11759047 (1.52%)
8: 3266233 (0.42%)
9: 598738 (0.08%)
10: 54290 (0.01%)
----- ForbiddenSoul's v2 Generator -----
nextChance: chance(0.25) + (failures * baseChance(0.083333336) - (successes * baseChance(0.083333336)
64: 2796203 (0.26%)
1: 1011750155 (94.23%)
65: 1398101 (0.13%)
129: 1398101 (0.13%)
2: 16841046 (1.57%)
66: 2796202 (0.26%)
3: 8868130 (0.83%)
4: 4654513 (0.43%)
5: 2327695 (0.22%)
6: 1185080 (0.11%)
7: 646652 (0.06%)
8: 1853248 (0.17%)
9: 401736 (0.04%)
10: 1791473 (0.17%)
11: 379572 (0.04%)
12: 307903 (0.03%)
13: 170789 (0.02%)
14: 108401 (0.01%)
15: 53570 (0.00%)
16: 2821542 (0.26%)
17: 1404970 (0.13%)
18: 2796202 (0.26%)
26: 1 (0.00%)
32: 2796203 (0.26%)
33: 1398101 (0.13%)
34: 2796202 (0.26%)
50: 1 (0.00%)
BUILD SUCCESSFUL in 1m 49s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment