package org.magenta.random;
import java.util.List;
import java.util.Random;
import com.google.common.base.Preconditions;
import com.google.common.collect.BoundType;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
/**
* Helper class that generates random integers.
* @author ngagnon
*
*/
public class RandomInteger {
private Random random;
private Range<Integer> constraint;
private int resolution;
private static final Range<Integer> ALL = Range.all();
private static final Range<Integer> EVERY_POSITIVE_BUT_ZERO = Range.greaterThan(0);
private static final Range<Integer> EVERY_POSITIVE_INCLUDING_ZERO = Range.atLeast(0);
private static final Range<Integer> EVERY_NEGATIVE_BUT_ZERO = Range.lessThan(0);
private static final Range<Integer> EVERY_NEGATIVE_INCLUDING_ZERO = Range.atMost(0);
/**
* Construct a {@link RandomInteger} instance using a resolution of 1 and no
* constraint.
*
* @param random
* used to generate random numbers
*/
public RandomInteger(Random random) {
this(random, ALL, 1);
}
/**
* Construct a {@link RandomInteger} instance using the specified
* <code>resolution</code> within the specified <code>constraint</code>.
*
* @param random
* used to generate random numbers
* @param constraint
* the integer range from which to select an integer
* @param resolution
* the minimum possible delta between two generated integers
*/
public RandomInteger(Random random, Range<Integer> constraint, int resolution) {
this.random = random;
this.constraint = constraint;
this.resolution = resolution;
}
/**
* Generate a random integer within the intersection of the specified
* <code>range</code> and this instance current range.
*
* @param range
* the desired range
* @return a randomly generated integer
*/
public int any(final Range<Integer> range) {
Range<Integer> r = range.intersection(constraint);
if (r.isEmpty()) {
throw new IllegalStateException(String.format(
"The intersection of the passed in range %s and this class constrained range %s result in a empty range", range, constraint));
}
int upperBound = r.hasUpperBound() ? r.upperEndpoint() : Integer.MAX_VALUE;
int lowerBound = r.hasLowerBound() ? r.lowerEndpoint() : Integer.MIN_VALUE;
if (r.hasUpperBound() && BoundType.CLOSED == r.upperBoundType()) {
// upperBound is not included in the random.nextInt() method
upperBound++;
}
if (r.hasLowerBound() && BoundType.OPEN == r.lowerBoundType()) {
// lowerBound is included in the random.nextInt() method
lowerBound++;
}
int randomInt;
if (lowerBound < 0 && upperBound > 0) {
// special case
int lowerRandomInt = (((this.random.nextInt((-lowerBound) / resolution))) * resolution) + lowerBound;
int upperRandomInt = (((this.random.nextInt((upperBound) / resolution))) * resolution);
randomInt = lowerRandomInt + upperRandomInt;
} else {
int delta = Math.abs(upperBound - lowerBound);
randomInt = (this.random.nextInt(delta / resolution) * resolution) + lowerBound;
}
return randomInt;
}
/**
* Generate a random integer within this instance current range.
*
* @return a randomly generated integer
*/
public int any() {
return any(ALL);
}
public List<Integer> some(int size) {
ContiguousSet<Integer> a = ContiguousSet.create(constraint, DiscreteDomain.integers());
Preconditions.checkArgument(size <= a.size(), "the number of items to pick (%s) must be lower than the number of integers available in the range (%s): ",size, a.size());
RandomList<Integer> integers = new RandomList<Integer>(random, this, Lists.newArrayList(a));
return integers.some(size);
}
/**
* Generate a random integer within the intersection of the range specified by
* <code>includedMin</code> and <code>includedMax</code> and this instance
* current range.
*
* @param includedMin
* the lower bound
* @param includedMax
* the upper bound
* @return a randomly generated integer
*/
public int anyBetween(int includedMin, int includedMax) {
return any(Range.closed(includedMin, includedMax));
}
/**
* Generate a random positive integer (including zero) that is within this
* instance current range.
*
* @return a randomly generated integer
*/
public int anyPositive() {
return any(EVERY_POSITIVE_INCLUDING_ZERO);
}
/**
* Generate a random positive integer (excluding zero) that is within this
* instance current range.
*
* @return a randomly generated integer
*/
public int anyPositiveButZero() {
return any(EVERY_POSITIVE_BUT_ZERO);
}
/**
* Generate a random positive integer (including zero) that is within this
* instance current range and below the specified <code>max</code>.
*
* @param max
* the maximum value
* @return a randomly generated integer
*/
public int anyPositive(int max) {
return any(Range.closedOpen(0, max));
}
/**
* Generate a random positive integer (excluding zero) that is within this
* instance current range and below the specified <code>max</code>.
*
* @param max
* the maximum value
* @return a randomly generated integer
*/
public int anyPositiveButZero(int max) {
return any(Range.open(0, max));
}
/**
* Generate a random negative integer (including zero) that is within this
* instance current range.
*
* @return a randomly generated integer
*/
public int anyNegative() {
return any(EVERY_NEGATIVE_INCLUDING_ZERO);
}
/**
* Generate a random negative integer (excluding zero) that is within this
* instance current range.
*
* @return a randomly generated integer
*/
public int anyNegativeButZero() {
return any(EVERY_NEGATIVE_BUT_ZERO);
}
/**
* Generate a random negative integer (including zero) that is within this
* instance current range and above the specified <code>min</code>.
*
* @param min
* the maximum value
* @return a randomly generated integer
*/
public int anyNegative(int min) {
return any(Range.openClosed(min, 0));
}
/**
* Generate a random negative integer (excluding zero) that is within this
* instance current range and above the specified <code>min</code>.
*
* @param min
* the maximum value
* @return a randomly generated integer
*/
public int anyNegativeButZero(int min) {
return any(Range.open(min, 0));
}
/**
* Return a new copy of this instance having the specified
* <code>resolution</code>.
*
* @param resolution
* the resolution
* @return a new instance
*/
public RandomInteger resolution(int resolution) {
return new RandomInteger(random, constraint, resolution);
}
/**
* Return a new copy of this instance having the specified
* <code>constraint</code>.
*
* @param constraint
* the constraint
* @return a new instance
*/
public RandomInteger constraint(Range<Integer> constraint) {
return new RandomInteger(random, this.constraint.intersection(constraint), resolution);
}
/**
* Return a new copy of this instance having the range specified by the
* interval <code>minIncluded</code> and <code>maxIncluded</code>.
*
* @param minIncluded
* lower bound
* @param maxIncluded
* upper bound
* @return a new instance
*/
public RandomInteger constraint(int minIncluded, int maxIncluded) {
return constraint(Range.closed(minIncluded, maxIncluded));
}
}