From 66e0874633f28aa4bb2b10cf87a7243a62dcac70 Mon Sep 17 00:00:00 2001 From: "Ray J. Greenwell" Date: Wed, 23 Apr 2014 10:08:18 -0700 Subject: [PATCH] Added Weigher interface, and a pick(Iterable, Weigher). I wanted something like this before, but with guava's Function, and samskivert doesn't depend have dependencies. But with Java 8 coming, functional interfaces make the distinction between different interfaces less important. --- .../java/com/samskivert/util/Randoms.java | 83 ++++++++++++------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/samskivert/util/Randoms.java b/src/main/java/com/samskivert/util/Randoms.java index 84a520c7..ccf3765e 100644 --- a/src/main/java/com/samskivert/util/Randoms.java +++ b/src/main/java/com/samskivert/util/Randoms.java @@ -49,6 +49,17 @@ public class Randoms return _localRandoms.get(); } + /** + * An interface for weighing pickable elements. + */ + public interface Weigher + { + /** + * Get the weight of the specified value. + */ + public double getWeight (T value); + } + /** * Returns a pseudorandom, uniformly distributed int value between 0 * (inclusive) and high (exclusive). @@ -208,47 +219,61 @@ public class Randoms * @throws IllegalArgumentException if any weight is less than 0. */ public T pick (Map weightMap, T ifEmpty) + { + Map.Entry pick = pick( + weightMap.entrySet(), null, new Weigher>() { + public double getWeight (Map.Entry entry) { + return entry.getValue().doubleValue(); + } + }); + return (pick == null) ? ifEmpty : pick.getKey(); + } + + /** + * Pick a random element from the specified iterable, or return + * ifEmpty if no element has a weight greater than 0. + * + *

Implementation note: a random number is generated for every entry with a + * non-zero weight after the first such entry. + * + * @throws NullPointerException if values is null. + * @throws IllegalArgumentException if any weight is less than 0. + */ + public T pick (Iterable values, T ifEmpty, Weigher weigher) + { + return pick(values.iterator(), ifEmpty, weigher); + } + + /** + * Pick a random element from the specified iterator, or return + * ifEmpty if no element has a weight greater than 0. + * + *

Implementation note: a random number is generated for every entry with a + * non-zero weight after the first such entry. + * + * @throws NullPointerException if values is null. + * @throws IllegalArgumentException if any weight is less than 0. + */ + public T pick (Iterator values, T ifEmpty, Weigher weigher) { T pick = ifEmpty; double total = 0.0; - for (Map.Entry entry : weightMap.entrySet()) { - double weight = entry.getValue().doubleValue(); + while (values.hasNext()) { + T val = values.next(); + double weight = weigher.getWeight(val); if (weight > 0.0) { total += weight; if ((total == weight) || ((_r.nextDouble() * total) < weight)) { - pick = entry.getKey(); + pick = val; } } else if (weight < 0.0) { - throw new IllegalArgumentException("Weight less than 0: " + entry); - } // else: weight == 0.0 is OK + throw new IllegalArgumentException("Weight less than 0: " + val); + } + // else: weight == 0.0 is OK } return pick; } -// public T pick ( -// Iterable iterable, Function weightFunction, -// T ifEmpty) -// { -// T pick = ifEmpty; -// double total = 0.0; -// for (T element : iterable) { -// double weight = weightFunction.apply(element).doubleValue(); -// // The rest is like the current pick(Map) -// } -// return pick; -// } -// -// public T pick (Map weightMap, T ifEmpty) -// { -// Map.Entry pick = pick(weightMap.entrySet(), -// new Function, Number>() { -// public Number apply (Map.Entry entry) { -// return entry.getValue(); -// } -// }, null); -// return (pick != null) ? pick.getKey() : ifEmpty; -// } - /** * Pluck (remove) a random element from the specified Iterable, or return ifEmpty * if it is empty.