From cfa7c9c79b685cfee711368415d26f9826322b55 Mon Sep 17 00:00:00 2001 From: "ray.j.greenwell" Date: Thu, 30 Sep 2010 22:12:56 +0000 Subject: [PATCH] A new replacement for RandomUtil. - Instead of duplicating every method twice, once allowing you to specify your own Random object, there is a static thread-safe sharable instance that anyone can use, and a factory method to create your own instance with a supplied Random. - The goofy methods for picking an element from a collection but skipping a particular value, or picking from an iterator but providing a count, are gone. - Instead there are two methods: pick and pluck, for picking an element or picking and removing an element. For Iterator, only pick is available. Optimized code paths are provided for Lists and Collections, but the API is kept simple. git-svn-id: https://samskivert.googlecode.com/svn/trunk@2901 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../java/com/samskivert/util/Randoms.java | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 src/main/java/com/samskivert/util/Randoms.java diff --git a/src/main/java/com/samskivert/util/Randoms.java b/src/main/java/com/samskivert/util/Randoms.java new file mode 100644 index 00000000..04cc023f --- /dev/null +++ b/src/main/java/com/samskivert/util/Randoms.java @@ -0,0 +1,241 @@ +// +// $Id$ + +package com.samskivert.util; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * Provides utility routines to simplify obtaining randomized values. + */ +public class Randoms +{ + /** A default Randoms that can be safely shared by any caller. */ + public static final Randoms RAND = with(new Random()); + + /** + * A factory to create a new Randoms object. + */ + public static Randoms with (Random rand) + { + return new Randoms(rand); + } + + /** + * Returns a pseudorandom, uniformly distributed int value between 0 (inclusive) + * and the specified value (exclusive). + * + * @param high the high value limiting the random number sought. + * + * @throws IllegalArgumentException if high is not positive. + */ + public int getInt (int high) + { + return _r.nextInt(high); + } + + /** + * Returns a pseudorandom, uniformly distributed int value between + * low (inclusive) and high (exclusive). + * + * @throws IllegalArgumentException if high - low is not positive. + */ + public int getInRange (int low, int high) + { + return low + _r.nextInt(high - low); + } + + /** + * Returns a pseudorandom, uniformly distributed float value between 0.0 (inclusive) and the + * specified value (exclusive). + * + * @param high the high value limiting the random number sought. + */ + public float getFloat (float high) + { + return _r.nextFloat() * high; + } + + /** + * Returns a pseudorandom, uniformly distributed float value between + * low (inclusive) and high (exclusive). + */ + public float getInRange (float low, float high) + { + return low + (_r.nextFloat() * (high - low)); + } + + /** + * Returns true approximately one in n times. + */ + public boolean getChance (int n) + { + return (0 == _r.nextInt(n)); + } + + /** + * Has a probability p of returning true. + */ + public boolean getProbability (float p) + { + return _r.nextFloat() < p; + } + + /** + * Returns true or false with approximately even distribution. + */ + public boolean getBoolean () + { + return _r.nextBoolean(); + } + + /** + * Returns a key from the supplied map according to a probability computed as + * the key's value divided by the total of all the key's values. + * + * @throws NullPointerException if the map is null. + * @throws IllegalArgumentException if the sum of the weights is not positive. + */ + public T getWeighted (Map valuesToWeights) + { + // TODO: validation? + int idx = _r.nextInt(Folds.sum(0, valuesToWeights.values())); + for (Map.Entry entry : valuesToWeights.entrySet()) { + idx -= entry.getValue(); + if (idx < 0) { + return entry.getKey(); + } + } + throw new AssertionError("Not possible"); + } + + /** + * Pick a random element from the specified Iterator, or return ifEmpty + * if it is empty. + * + *

Implementation note: because the total size of the Iterator is not known, + * the random number generator is queried after the second element and every element + * thereafter. + * + * @throws NullPointerException if the iterator is null. + */ + public T pick (Iterator iterator, T ifEmpty) + { + if (!iterator.hasNext()) { + return ifEmpty; + } + T pick = iterator.next(); + for (int count = 2; iterator.hasNext(); count++) { + T next = iterator.next(); + if (0 == _r.nextInt(count)) { + pick = next; + } + } + return pick; + } + + /** + * Pick a random element from the specified Iterable, or return ifEmpty + * if it is empty. + * + *

Implementation note: optimized implementations are used if the Iterable + * is a List or Collection. Otherwise, it behaves as if calling {@link #pick(Iterator)} with + * the Iterable's Iterator. + * + * @throws NullPointerException if the iterable is null. + */ + public T pick (Iterable iterable, T ifEmpty) + { + return pickPluck(iterable, ifEmpty, false); + } + + /** + * Pluck (remove) a random element from the specified Iterable, or return ifEmpty + * if it is empty. + * + *

Implementation note: optimized implementations are used if the Iterable + * is a List or Collection. Otherwise, two Iterators are created from the Iterable + * and a random number is generated after the second element and all beyond. + * + * @throws NullPointerException if the iterable is null. + * @throws UnsupportedOperationException if the iterable is unmodifiable or its Iterator + * does not support {@link Iterator#remove()}. + */ + public T pluck (Iterable iterable, T ifEmpty) + { + return pickPluck(iterable, ifEmpty, true); + } + + /** + * Construct a Randoms. + */ + protected Randoms (Random rand) + { + _r = rand; + } + + /** + * Shared code for pick and pluck. + */ + @SuppressWarnings("unchecked") + protected T pickPluck (Iterable iterable, T ifEmpty, boolean remove) + { + if (iterable instanceof Collection) { + // optimized path for Collection + Collection coll = (Collection)iterable; + int size = coll.size(); + if (size == 0) { + return ifEmpty; + } + if (coll instanceof List) { + // extra-special optimized path for Lists + List list = (List)coll; + int idx = _r.nextInt(size); + return (T)(remove ? list.remove(idx) : list.get(idx)); + } + // for other Collections, we must iterate + Iterator it = coll.iterator(); + for (int idx = _r.nextInt(size); idx > 0; idx--) { + it.next(); + } + try { + return it.next(); + } finally { + if (remove) { + it.remove(); + } + } + } + + if (!remove) { + return pick(iterable.iterator(), ifEmpty); + } + + // from here on out, we're doing a pluck with a complicated two-iterator solution + Iterator it = iterable.iterator(); + if (!it.hasNext()) { + return ifEmpty; + } + Iterator lagIt = iterable.iterator(); + T pick = it.next(); + lagIt.next(); + for (int count = 2, lag = 1; it.hasNext(); count++, lag++) { + T next = it.next(); + if (0 == _r.nextInt(count)) { + pick = next; + for ( ; lag > 0; lag--) { + lagIt.next(); + } + } + } + lagIt.remove(); + return pick; + } + + /** The random number generator. */ + protected final Random _r; +}