Skip to content

Instantly share code, notes, and snippets.

@monsieurh
Last active July 19, 2017 11:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save monsieurh/91d6a83308840ca75b60efe9682019dd to your computer and use it in GitHub Desktop.
Save monsieurh/91d6a83308840ca75b60efe9682019dd to your computer and use it in GitHub Desktop.
Shuffles a list in C# (Unity compatible)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Utils
{
/// <summary>
/// Implements "iTunes shuffle" that is to say, it's a random where you can't get twice the same value unless you
/// already got all the values.
///
/// To avoid having a chance to get twice the same value when refilling the pool, we store the last picked value in
/// `_last` and do not put it back when refilling the pool
///
/// author: ray.hubert@riseup.net
/// </summary>
public class Shuffler<T> : IEnumerable
{
private readonly List<T> _allItems = new List<T>(); // All items used in the shuffler
private readonly List<T> _pool = new List<T>(); // Remaining items for current series
private Random _random = new Random();
private T _last; //Last object extracted from the pool
private int PoolCount
{
get { return _pool.Count; }
}
public Shuffler(Random random = null)
{
SetRandom(random);
}
public Shuffler(IEnumerable<T> allItems, Random random = null)
{
_allItems.AddRange(allItems);
SetRandom(random);
}
/// <summary>
/// Always returns next element. If the pool is empty, GetNext() refills it before returning the next element
/// </summary>
/// <returns>The next element</returns>
public T GetNext()
{
if (IsPoolEmpty()) PopulatePool();
return PopRandomElement();
}
/// <summary>
/// Enumerates the REMAINING elements in the pool. Stops when the pool is empty
/// </summary>
/// <returns>enumerator on elements left</returns>
public IEnumerator GetEnumerator()
{
if (IsPoolEmpty()) PopulatePool();
while (PoolCount > 0)
{
yield return PopRandomElement();
}
}
/// <summary>
/// A readonly list of all items in the Shuffler. Some of them may already have been picked and may not be
/// chosen until the pool is refiled
/// </summary>
/// <returns>readonly list of all possible values</returns>
public ReadOnlyCollection<T> GetAllPossibleValues()
{
return _allItems.AsReadOnly();
}
public void Add(T element)
{
_allItems.Add(element);
}
private T PopRandomElement()
{
if (PoolCount == 0) throw new ArgumentException("List of elements is empty !");
int randomIndex = _random.Next(PoolCount);
T item = _pool[randomIndex];
_pool.RemoveAt(randomIndex);
if (IsPoolEmpty()) _last = item;
return item;
}
private void PopulatePool()
{
_pool.Clear();
_pool.AddRange(_allItems);
if (PoolCount > 1) // If _allItems is size 1 we don't remove from the pool : it would be empty
{
RemoveLastFromPool();
}
}
private void RemoveLastFromPool()
{
_pool.Remove(_last);
_last = default(T);
}
private bool IsPoolEmpty()
{
return PoolCount == 0;
}
private void SetRandom(Random random)
{
if (random != null) _random = random;
}
}
}
@monsieurh
Copy link
Author

monsieurh commented Jun 19, 2017

Rev 2 can now be used as follows :

Shuffler<int> shuffler = new Shuffler<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
foreach (int number in shuffler)
{
	Debug.Log("Random element : " + number);
}

bool gameIsPlaying = true;
while (gameIsPlaying)
{
	Debug.Log("Random element : " + shuffler.GetNext());
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment