Pool Of Randomness

For my current Unity project I had to convert Kipp Ashford’s Weighted Array script into something I could use in my C# environment. I had a similar solution already working in ISIS but Ashford’s script was much more elegant since it includes a number of useful functions my bare-boned version was lacking.

A clever (or so I hope) use of a hashtable allowed me to trim some of the variables the orginial script had used to keep it as slim as possible.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// WeightedRandomPool.cs
// 2013-10-09 by Thomas Touzimsky
// http://www.simplypointless.com/
//
// Adds, deletes and returns a random item from a pool of objects based on each item's associated weight
//
// A conversion of the original JS script by Kipp Ashford
// http://blog.teamthinklabs.com/index.php/2011/08/23/grabbing-items-from-an-array-based-on-probability/
 
using UnityEngine;
using System.Collections;
 
public class WeightedRandomPool
{
 
    // Our pool is stored in a hashtable where
    // key   = (Object)item
    // value = (int)weight
    public Hashtable htItems = new Hashtable();
 
 
    /// <summary>
    /// Adds a new item to the hashtable or changes its weight if has been added before
    /// </summary>
    /// <param name="item">The object you want to add to the pool</param>
    /// <param name="weight">The object's weight within the pool</param>
    public void Add(Object item, int weight)
    {
        if (!htItems.ContainsKey(item))
        {
            htItems.Add(item, weight);
        }
        else
        {
            Debug.Log("WeightedRandomPool::Add() --- WARNING: Object is already added. Changing weight of object instead.");
 
            htItems[item] = weight;
        }
    }
 
 
    /// <summary>
    /// Changes the weight of an object within the pool
    /// </summary>
    /// <param name="item">The object whose weight you want to change</param>
    /// <param name="nWeight">The object's new weight within the pool</param>
    public void ChangeWeight(Object item, int nWeight)
    {
        if (htItems.ContainsKey(item))
        {
            htItems[item] = nWeight;
        }
        else
        {
            Debug.Log("WeightedRandomPool::ChangeWeight() --- WARNING: Object '" + item + "' is not a member of this pool.");
        }
    }
 
 
    /// <summary>
    /// Removes an object from the pool
    /// </summary>
    /// <param name="item">The object you want to remove</param>
    public void Remove(Object item)
    {
        if (htItems.ContainsKey(item))
        {
            htItems.Remove(item);
        }
        else
        {
            Debug.Log("WeightedRandomPool::ChangeWeight() --- WARNING: Object '" + item + "' is not a member of this pool.");
        }
    }
 
 
    /// <summary>
    /// Returns a randomly chosen object from the pool depending on all members' weight
    /// </summary>
    /// <returns></returns>
    public Object Get()
    {
        int nSumOfWeights = 0;
 
        foreach (DictionaryEntry item in htItems)
        {
            nSumOfWeights += (int)item.Value;
        }
 
        int k = Random.Range(0, nSumOfWeights + 1);
        // +1 to make sure we can actually find the last item if it has a weight of 1
        // since Random.Range never hits the maximum value
 
        foreach (DictionaryEntry item in htItems)
        {
            // walk the pool one item at a time and see whether its weight is lower than k
            // if yes, return the current item ; if no, subtract the item's weight from k
 
            // if, for example, the pool includes three items with a weight of 40/40/60 and
            // k = 70, the first loop's result will be negative and k gets reduced by 40;
            // the next loop's result is positive since k (now 30) is lower than the second
            // item's weight
 
            if (k > (int)item.Value)
            {
                k -= (int)item.Value;
            }
            else
            {
                return (Object)item.Key;
            }
        }
 
        return null;
    }
 
 
    /// <summary>
    /// Clears the pool
    /// </summary>
    public void Clear()
    {
        htItems.Clear();
    }
 
}

The class itself is used just like Ashford’s original.

WeightedRandomPool myPool = new WeightedRandomPool();
 
myPool.Add(myFirstItem, 15);
myPool.Add(mySecondItem, 5);
myPool.Add(myThirdItem, 2);
 
UnityEngine.Object myRandomItem = myPool.Get();

Feel free to suggest any ideas on how to improve the script – after all, I am still a novice coder. :)