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. 🙂