Advertisement
Guest User

Untitled

a guest
May 23rd, 2018
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 7.28 KB | None | 0 0
  1. package test.random;
  2.  
  3. import java.util.Collection;
  4. import java.util.Collections;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7. import java.util.Map.Entry;
  8. import java.util.Random;
  9. import java.util.TreeMap;
  10. import java.util.function.Function;
  11. import java.util.function.Supplier;
  12.  
  13. /**
  14.  * Implements a random weighted chooser for the given result class. The weights are doubles,
  15.  * but the class can be used supplying purely integer weights. The class supports the Java 8 functional
  16.  * interfaces Supplier<E> and Function<Random, E> in addition to its own newChoice() methods.
  17.  * <p>
  18.  * The class supports giving <i>null</i> a weight. It's thread-safe.
  19.  * <p>
  20.  * The range of usable weights depends on the quality of the random number generator used.
  21.  * Given a sufficiently well-coded Random subclass, the full precision range of the <i>double</i>
  22.  * class (53 bits, or almost 16 orders of magnitude) can be utilized.
  23.  * <p>
  24.  * Negative weights are possible, and result in the specific value never being chosen.
  25.  * The one exception to it is <i>null</i> if there is no other valid choice.
  26.  * <p>
  27.  * Nothing in the class prevents from using <i>infinity</i> or <i>NaN</i> values as weights, even though
  28.  * it makes no sense to do so. Go right ahead.
  29.  */
  30. public class WeightedChoice<E> implements Supplier<E>, Function<Random, E> {
  31.     private final transient Object[] LOCK = new Object[0];
  32.    
  33.     private Map<E, Double> weights;
  34.     private transient TreeMap<Double, E> choices;
  35.     private boolean valid = false;
  36.     private double nullWeight = 0.0;
  37.     private double maxWeight = 0.0;
  38.     private Random rnd;
  39.    
  40.     public WeightedChoice(Random random) {
  41.         weights = new HashMap<E, Double>();
  42.         rnd = random;
  43.     }
  44.    
  45.     public WeightedChoice(long seed) {
  46.         this(new Random(seed));
  47.     }
  48.    
  49.     public WeightedChoice() {
  50.         this(new Random());
  51.     }
  52.  
  53.     /**
  54.      * Clears all current weights.
  55.      */
  56.     public void clear() {
  57.         synchronized(LOCK) {
  58.             weights = new HashMap<E, Double>();
  59.             nullWeight = 0.0;
  60.             valid = false;
  61.         }
  62.     }
  63.  
  64.     /**
  65.      * Add a (potentially negative) weight to the given choice.
  66.      * <p>
  67.      * If the choice doesn't exist yet, it's created with the supplied weight.
  68.      * If it exists, the weight is modified by the value.
  69.      * A resulting negative weight means that the choice is never used.
  70.      * <p>
  71.      * Adding a weight to <i>null</i> is a valid operation - it means that there is a
  72.      * non-zero chance for the randomizer to not return any value.
  73.      */
  74.     public void add(E choice, double weight) {
  75.         if( weight == 0.0 ) {
  76.             // No need to do anything
  77.             return;
  78.         }
  79.         synchronized(LOCK) {
  80.             internalAdd(choice, weight);
  81.         }
  82.     }
  83.    
  84.     /**
  85.      * Set a (potentially negative) weight to the given choice.
  86.      * Any existing weight gets overwritten.
  87.      * <p>
  88.      * A resulting negative weight means that the choice is never used.
  89.      * <p>
  90.      * Setting a weight for <i>null</i> is a valid operation - it means that there is a
  91.      * non-zero chance for the randomizer to not return any value.
  92.      */
  93.     public void set(E choice, double weight) {
  94.         synchronized(LOCK) {
  95.             internalSet(choice, weight);
  96.         }
  97.     }
  98.    
  99.     /**
  100.      * Remove a choice. Same as setting its weight to 0, but also cleans up the entry table.
  101.      * <p>
  102.      * Calling this method with <i>null</i> removes the choice to obtain a null result.
  103.      */
  104.     public void remove(E choice) {
  105.         synchronized(LOCK) {
  106.             internalRemove(choice);
  107.         }
  108.     }
  109.    
  110.     /**
  111.      * Add the given entry at the given choice. This is a convenience method, meant for quickly
  112.      * adding parts of a map.
  113.      */
  114.     public void add(Entry<E, Double> entry) {
  115.         synchronized(LOCK) {
  116.             internalAdd(entry.getKey(), entry.getValue());
  117.         }
  118.     }
  119.    
  120.     /**
  121.      * Add the contents of the map to the current weight map.
  122.      */
  123.     public void add(Map<E, Double> map) {
  124.         synchronized(LOCK) {
  125.             for(Entry<E, Double> entry : map.entrySet()) {
  126.                 internalAdd(entry.getKey(), entry.getValue());
  127.             }
  128.         }
  129.     }
  130.    
  131.     /**
  132.      * Sets the given entry at the given choice. This is a convenience method, meant for quickly
  133.      * setting parts of a map.
  134.      */
  135.     public void set(Entry<E, Double> entry) {
  136.         synchronized(LOCK) {
  137.             internalSet(entry.getKey(), entry.getValue());
  138.         }
  139.     }
  140.    
  141.     /**
  142.      * Set the contents of the map as values in the current weight map.
  143.      */
  144.     public void set(Map<E, Double> map) {
  145.         synchronized(LOCK) {
  146.             for(Entry<E, Double> entry : map.entrySet()) {
  147.                 internalSet(entry.getKey(), entry.getValue());
  148.             }
  149.         }
  150.     }
  151.    
  152.     /**
  153.      * Remove all weights in the given collection.
  154.      */
  155.     public void remove(Collection<E> collection) {
  156.         synchronized(LOCK) {
  157.             for(E choice : collection) {
  158.                 internalRemove(choice);
  159.             }
  160.         }
  161.     }
  162.    
  163.     /**
  164.      * Remove all weights returned by the given iterable class.
  165.      */
  166.     public void remove(Iterable<E> iterable) {
  167.         synchronized(LOCK) {
  168.             for(E choice : iterable) {
  169.                 internalRemove(choice);
  170.             }
  171.         }
  172.     }
  173.    
  174.     private void internalAdd(E choice, double weight) {
  175.         if( choice == null ) {
  176.             nullWeight += weight;
  177.         }
  178.         else {
  179.             double currentWeight = ( weights.containsKey(choice) ? weights.get(choice) : 0.0 );
  180.             weights.put(choice, currentWeight + weight);
  181.         }
  182.         valid = false;
  183.     }
  184.    
  185.     private void internalRemove(E choice) {
  186.         if( choice == null ) {
  187.             nullWeight = 0.0;
  188.         }
  189.         else {
  190.             if( !weights.containsKey(choice) ) {
  191.                 // NO-OP
  192.                 return;
  193.             }
  194.             weights.remove(choice);
  195.         }
  196.         valid = false;
  197.     }
  198.    
  199.     private void internalSet(E choice, double weight) {
  200.         if( choice == null ) {
  201.             nullWeight = weight;
  202.         }
  203.         else if( weights.containsKey(choice) && weights.get(choice) == weight ) {
  204.             // No need to do anything
  205.             return;
  206.         }
  207.         else {
  208.             weights.put(choice, weight);
  209.         }
  210.         valid = false;
  211.     }
  212.    
  213.     private void validate() {
  214.         if( !valid ) {
  215.             // Generate the distribution TreeMap
  216.             choices = new TreeMap<Double, E>();
  217.             double count = 0.0;
  218.             for(Entry<E, Double> entry : weights.entrySet()) {
  219.                 if( entry.getValue() > 0.0 ) {
  220.                     choices.put(count, entry.getKey());
  221.                     count += entry.getValue();
  222.                 }
  223.             }
  224.             maxWeight = count;
  225.             valid = true;
  226.         }
  227.     }
  228.    
  229.     /**
  230.      * Return a random weighted choice, using the built-in or supplied during creation randomizer.
  231.      */
  232.     @Override public E get() {
  233.         return apply(rnd);
  234.     }
  235.    
  236.     /**
  237.      * Return a random weighted choice, using the built-in or supplied during creation randomizer.
  238.      */
  239.     public E newChoice() {
  240.         return apply(rnd);
  241.     }
  242.    
  243.     /**
  244.      * Return a random weighted choice, using the supplied randomizer.
  245.      */
  246.     public E newChoice(Random random) {
  247.         return apply(random);
  248.     }
  249.  
  250.     /**
  251.      * Return a random weighted choice, using the supplied randomizer.
  252.      */
  253.     @Override public E apply(Random random) {
  254.         synchronized(LOCK) {
  255.             validate();
  256.            
  257.             double randomVal = (maxWeight + (nullWeight > 0.0 ? nullWeight : 0.0)) * random.nextDouble();
  258.             if( nullWeight > 0.0 ) {
  259.                 if( randomVal <= nullWeight ) {
  260.                     // We have chosen to not choose.
  261.                     return null;
  262.                 }
  263.                 randomVal -= nullWeight;
  264.             }
  265.             return choices.floorEntry(randomVal).getValue();
  266.         }
  267.     }
  268.    
  269.     /**
  270.      * Returns a read-only copy of the current weights.
  271.      */
  272.     public Map<E, Double> weights() {
  273.         synchronized(LOCK) {
  274.             return Collections.<E, Double>unmodifiableMap(weights);
  275.         }
  276.     }
  277.    
  278.     public void setSeed(long seed) {
  279.         rnd.setSeed(seed);
  280.     }
  281. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement