Search Unity

selection based on percentage weighting in C#

Discussion in 'Scripting' started by roylisto, Oct 18, 2014.

  1. roylisto

    roylisto

    Joined:
    Jul 3, 2012
    Posts:
    14
    I want to select a value based on the percentage chance given
    i found simmilar problem in this link
    http://stackoverflow.com/questions/3655430/selection-based-on-percentage-weighting

    and the code is like this
    Code (CSharp):
    1. public class ProportionValue<T>
    2. {
    3.     public double Proportion { get; set; }
    4.     public T Value { get; set; }
    5. }
    6.  
    7. public static class ProportionValue
    8. {
    9.     public static ProportionValue<T> Create<T>(double proportion, T value)
    10.     {
    11.         return new ProportionValue<T> { Proportion = proportion, Value = value };
    12.     }
    13.  
    14.     static Random random = new Random();
    15.     public static T ChooseByRandom<T>(
    16.         this IEnumerable<ProportionValue<T>> collection)
    17.     {
    18.         var rnd = random.NextDouble();
    19.         foreach (var item in collection)
    20.         {
    21.             if (rnd < item.Proportion)
    22.                 return item.Value;
    23.             rnd -= item.Proportion;
    24.         }
    25.         throw new InvalidOperationException(
    26.             "The proportions in the collection do not add up to 1.");
    27.     }
    28. }
    and usage :
    Code (CSharp):
    1. var list = new[] {
    2.     ProportionValue.Create(0.7, "a"),
    3.     ProportionValue.Create(0.2, "b"),
    4.     ProportionValue.Create(0.1, "c")
    5. };
    6.  
    7. // Outputs "a" with probability 0.7, etc.
    8. Console.WriteLine(list.ChooseByRandom());
    but when i use that code in unity3d , there is lines contains error (red color)
    var rnd = random.NextDouble();
    foreach (var item in collection)
    {
    if (rnd < item.Proportion)
    return item.Value;
    rnd -= item.Proportion;
    }
    throw new InvalidOperationException(
    "The proportions in the collection do not add up to 1.");

    How to implement that code in unity C# ?
     
  2. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Here is what i have used in the past.
    What i like about it, is the weighting uses ints, and doesnt need to add up to 1.
    Consequently, it's easy to add a new thing, without needed to do all the math to re-proportion out all the other weights

    Code (CSharp):
    1.     int[] weights;
    2.     int weightTotal;
    3.  
    4.     struct things { //this is just for code-read niceness
    5.         public const int aThing = 0;
    6.         public const int anotherThing = 1;
    7.         public const int something = 2;
    8.         public const int somethingElse = 3;
    9.     }
    10.  
    11.     void Awake () {
    12.         weights = new int[4]; //number of things
    13.  
    14.         //weighting of each thing, high number means more occurrance
    15.         weights[things.aThing] = 2;
    16.         weights[things.anotherThing] = 3;
    17.         weights[things.something] = 8;
    18.         weights[things.somethingElse] = 20;
    19.  
    20.         weightTotal = 0;
    21.         foreach ( int w in weights ) {
    22.             weightTotal += w;
    23.         }
    24.     }
    25.  
    26.  
    27.     int RandomWeighted () {
    28.         int result = 0, total = 0;
    29.         int randVal = Random.Range( 0, weightTotal + 1 );
    30.         for ( result = 0; result < weights.Length; result++ ) {
    31.             total += weights[result];
    32.             if ( total >= randVal ) break;
    33.         }
    34.         return result;
    35.     }
     
    Westland, Kirbyrawr and mgear like this.
  3. roylisto

    roylisto

    Joined:
    Jul 3, 2012
    Posts:
    14
    thanks man , more simple and get same result :D
     
  4. RockoDyne

    RockoDyne

    Joined:
    Apr 10, 2014
    Posts:
    2,234
  5. Robdon

    Robdon

    Joined:
    Jan 10, 2014
    Posts:
    141
    I know this is bringing up an old thread, but I feel its important for people to know, this code is incorrect, and will actually 'favour' the first element in the array. I felt I should post this, as its not immediately obvious, and maybe people could use this and not spot the problem.

    You can see the problem easily, if you set the weights all to 1, and call it say 10000 times and count in an array how many of each element are selected. It will show that element 0 will be select about twice as many times as the others.

    There are 2 bits that need fixing, firstly the remove of the '+1' in the random statement, and secondly the '>=' should be '>' in the if statement. The correct code should be....

    Code (CSharp):
    1.     int[] weights;
    2.     int weightTotal;
    3.  
    4.     struct things { //this is just for code-read niceness
    5.         public const int aThing = 0;
    6.         public const int anotherThing = 1;
    7.         public const int something = 2;
    8.         public const int somethingElse = 3;
    9.     }
    10.  
    11.     void Awake () {
    12.         weights = new int[4]; //number of things
    13.  
    14.         //weighting of each thing, high number means more occurrance
    15.         weights[things.aThing] = 2;
    16.         weights[things.anotherThing] = 3;
    17.         weights[things.something] = 8;
    18.         weights[things.somethingElse] = 20;
    19.  
    20.         weightTotal = 0;
    21.         foreach ( int w in weights ) {
    22.             weightTotal += w;
    23.         }
    24.     }
    25.  
    26.  
    27.     int RandomWeighted () {
    28.         int result = 0, total = 0;
    29.         int randVal = Random.Range( 0, weightTotal );
    30.         for ( result = 0; result < weights.Length; result++ ) {
    31.             total += weights[result];
    32.             if ( total > randVal ) break;
    33.         }
    34.         return result;
    35.     }
    HTHs.
     
  6. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Yep, not sure how I let that slip by.
    2 years are 3200 thread views... wonder how many people have put this in without noticing
     
  7. WarpZone

    WarpZone

    Joined:
    Oct 29, 2007
    Posts:
    326
    The answer is all of them.

    I love the Unity community dearly, but it's a never-ending noob factory. The people experienced enough to catch that error probably wrote their own implementations from scratch, rather than googling "Unity3D weighted decisions" and clicking on the first thing that looked promising. (Which is certainly how I got here!)
     
    loydb likes this.
  8. Deleted User

    Deleted User

    Guest

    This approach will fail if the weights table isn't sorted in ascending order as in the example provided.

    So let's say

    randVal == 6

    For weights table [2, 3, 8, 20] result = 1;
    For weights table [2, 20, 8, 3] result = 1;

    So be aware a preventive sorting is needed to make sure it works. And, of course, since the sorting alters the indexes in the weights table, a reference to the original order is likewise needed.
     
  9. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Why does that matter?


    The idea is that D is always more likely to get picked, assuming an equal chance of choosing any single square
    (randVal has linear distribution with random.range)
     
  10. digestives27

    digestives27

    Joined:
    Feb 16, 2020
    Posts:
    1
    Colour me stupid, or maybe just out of date, but I can't run this code because: 'Random' does not contain a definition for Range. Is there a new way to code this bit or is it best I just seek out a new solution? Essentially, I'm trying to do something similar where I need to randomly print a combination of a mathematical operator (only +, - and *) alongside an integer and I need to weight the operator so + is around 60%, - is around 30% and * is around 10%
     
  11. Robdon

    Robdon

    Joined:
    Jan 10, 2014
    Posts:
    141
    Do you have a 'using UnityEngine;' at the top?

    Either add that, or prefix with:
    int rnd = UnityEngine.Random.Range(0, 5);

    If you don't do that, it will possibly be trying to use System.Random, which isn't what you want.