Pool Of Randomness

c-Row October 9, 2013 0

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.

// 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();


    /// 
    /// Adds a new item to the hashtable or changes its weight if has been added before
    /// 
    /// The object you want to add to the pool
    /// The object's weight within the pool
    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;
        }
    }


    /// 
    /// Changes the weight of an object within the pool
    /// 
    /// The object whose weight you want to change
    /// The object's new weight within the pool
    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.");
        }
    }


    /// 
    /// Removes an object from the pool
    /// 
    /// The object you want to remove
    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.");
        }
    }


    /// 
    /// Returns a randomly chosen object from the pool depending on all members' weight
    /// 
    /// 
    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;
    }


    /// 
    /// Clears the pool
    /// 
    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. 🙂

Category: 

Leave a Comment