selection based on percentage weighting

后端 未结 13 2057
忘掉有多难
忘掉有多难 2020-12-04 13:40

I have a set of values, and an associated percentage for each:

a: 70% chance
b: 20% chance
c: 10% chance

I want to select a value (a, b, c) based

相关标签:
13条回答
  • 2020-12-04 13:52
    1. Let T = the sum of all item weights
    2. Let R = a random number between 0 and T
    3. Iterate the item list subtracting each item weight from R and return the item that causes the result to become <= 0.
    0 讨论(0)
  • 2020-12-04 13:55

    I've my own solution for this:

    public class Randomizator3000 
    {    
    public class Item<T>
    {
        public T value;
        public float weight;
    
        public static float GetTotalWeight<T>(Item<T>[] p_itens)
        {
            float __toReturn = 0;
            foreach(var item in p_itens)
            {
                __toReturn += item.weight;
            }
    
            return __toReturn;
        }
    }
    
    private static System.Random _randHolder;
    private static System.Random _random
    {
        get 
        {
            if(_randHolder == null)
                _randHolder = new System.Random();
    
            return _randHolder;
        }
    }
    
    public static T PickOne<T>(Item<T>[] p_itens)
    {   
        if(p_itens == null || p_itens.Length == 0)
        {
            return default(T);
        }
    
        float __randomizedValue = (float)_random.NextDouble() * (Item<T>.GetTotalWeight(p_itens));
        float __adding = 0;
        for(int i = 0; i < p_itens.Length; i ++)
        {
            float __cacheValue = p_itens[i].weight + __adding;
            if(__randomizedValue <= __cacheValue)
            {
                return p_itens[i].value;
            }
    
            __adding = __cacheValue;
        }
    
        return p_itens[p_itens.Length - 1].value;
    
    }
    }
    

    And using it should be something like that (thats in Unity3d)

    using UnityEngine;
    using System.Collections;
    
    public class teste : MonoBehaviour 
    {
    Randomizator3000.Item<string>[] lista;
    
    void Start()
    {
        lista = new Randomizator3000.Item<string>[10];
        lista[0] = new Randomizator3000.Item<string>();
        lista[0].weight = 10;
        lista[0].value = "a";
    
        lista[1] = new Randomizator3000.Item<string>();
        lista[1].weight = 10;
        lista[1].value = "b";
    
        lista[2] = new Randomizator3000.Item<string>();
        lista[2].weight = 10;
        lista[2].value = "c";
    
        lista[3] = new Randomizator3000.Item<string>();
        lista[3].weight = 10;
        lista[3].value = "d";
    
        lista[4] = new Randomizator3000.Item<string>();
        lista[4].weight = 10;
        lista[4].value = "e";
    
        lista[5] = new Randomizator3000.Item<string>();
        lista[5].weight = 10;
        lista[5].value = "f";
    
        lista[6] = new Randomizator3000.Item<string>();
        lista[6].weight = 10;
        lista[6].value = "g";
    
        lista[7] = new Randomizator3000.Item<string>();
        lista[7].weight = 10;
        lista[7].value = "h";
    
        lista[8] = new Randomizator3000.Item<string>();
        lista[8].weight = 10;
        lista[8].value = "i";
    
        lista[9] = new Randomizator3000.Item<string>();
        lista[9].weight = 10;
        lista[9].value = "j";
    }
    
    
    void Update () 
    {
        Debug.Log(Randomizator3000.PickOne<string>(lista));
    }
    }
    

    In this example each value has a 10% chance do be displayed as a debug =3

    0 讨论(0)
  • 2020-12-04 13:59

    Take the list of and find the cumulative total of the weights: 70, 70+20, 70+20+10. Pick a random number greater than or equal to zero and less than the total. Iterate over the items and return the first value for which the cumulative sum of the weights is greater than this random number:

    def select( values ):
        variate = random.random() * sum( values.values() )
        cumulative = 0.0
        for item, weight in values.items():
            cumulative += weight
            if variate < cumulative:
                return item
        return item # Shouldn't get here, but just in case of rounding...
    
    print select( { "a": 70, "b": 20, "c": 10 } )
    

    This solution, as implemented, should also be able to handle fractional weights and weights that add up to any number so long as they're all non-negative.

    0 讨论(0)
  • 2020-12-04 14:00

    I think you can have an array of small objects (I implemented in Java although I know a little bit C# but I am afraid can write wrong code), so you may need to port it yourself. The code in C# will be much smaller with struct, var but I hope you get the idea

    class PercentString {
      double percent;
      String value;
      // Constructor for 2 values
    }
    
    ArrayList<PercentString> list = new ArrayList<PercentString();
    list.add(new PercentString(70, "a");
    list.add(new PercentString(20, "b");
    list.add(new PercentString(10, "c");
    
    double percent = 0;
    for (int i = 0; i < list.size(); i++) {
      PercentString p = list.get(i);
      percent += p.percent;
      if (random < percent) {
        return p.value;
      }
    }
    
    0 讨论(0)
  • 2020-12-04 14:04

    Knuth references Walker's method of aliases. Searching on this, I find http://code.activestate.com/recipes/576564-walkers-alias-method-for-random-objects-with-diffe/ and http://prxq.wordpress.com/2006/04/17/the-alias-method/. This gives the exact probabilities required in constant time per number generated with linear time for setup (curiously, n log n time for setup if you use exactly the method Knuth describes, which does a preparatory sort you can avoid).

    0 讨论(0)
  • 2020-12-04 14:04

    Based loosely on python's numpy.random.choice(a=items, p=probs), which takes an array and a probability array of the same size.

        public T RandomChoice<T>(IEnumerable<T> a, IEnumerable<double> p)
        {
            IEnumerator<T> ae = a.GetEnumerator();
            Random random = new Random();
            double target = random.NextDouble();
            double accumulator = 0;
            foreach (var prob in p)
            {
                ae.MoveNext();
                accumulator += prob;
                if (accumulator > target)
                {
                    break;
                }
            }
            return ae.Current;
        }
    

    The probability array p must sum to (approx.) 1. This is to keep it consistent with the numpy interface (and mathematics), but you could easily change that if you wanted.

    0 讨论(0)
提交回复
热议问题