Login


Extending LINQ with Random Operations

By Jonathan Wood on 9/22/2012
Language: C#
Technology: .NETLINQ
Platform: Windows
License: CPOL
Views: 20,894
Frameworks & Libraries » LINQ » General » Extending LINQ with Random Operations

Adding Random Operations to IEnumerable<>

Introduction

LINQ is built using a number of extension methods, which add additional methods to existing types. The result is that it's very easy to extend LINQ by simply writing your own extension methods.

Although the existing functionality in LINQ will support most of the operations you will ever need, from time to time you may run across instances where functionality you want is not included. This happened to me recently when I needed some support for random operations such as returning a random item from a list, or shuffling an entire list.

I was able to add this functionality using extension methods. The result is a collection of methods that I can easily reuse. This code is shown in Listing 1.

Listing 1: RandomExtensions Class, which Adds Random Operations to IEnumerable and IList

static class RandomExtensions
{
    /// <summary>
    /// Returns a random element from a list, or null if the list is empty.
    /// </summary>
    /// <typeparam name="T">The type of object being enumerated</typeparam>
    /// <param name="rand">An instance of a random number generator</param>
    /// <returns>A random element from a list, or null if the list is empty</returns>
    public static T Random<T>(this IEnumerable<T> list, Random rand)
    {
        if (list != null && list.Count() > 0)
            return list.ElementAt(rand.Next(list.Count()));
        return default(T);
    }

    /// <summary>
    /// Returns a shuffled IEnumerable.
    /// </summary>
    /// <typeparam name="T">The type of object being enumerated</typeparam>
    /// <returns>A shuffled shallow copy of the source items</returns>
    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
    {
        return source.Shuffle(new Random());
    }

    /// <summary>
    /// Returns a shuffled IEnumerable.
    /// </summary>
    /// <typeparam name="T">The type of object being enumerated</typeparam>
    /// <param name="rand">An instance of a random number generator</param>
    /// <returns>A shuffled shallow copy of the source items</returns>
    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rand)
    {
        var list = source.ToList();
        list.Shuffle(rand);
        return list;
    }

    /// <summary>
    /// Shuffles an IList in place.
    /// </summary>
    /// <typeparam name="T">The type of elements in the list</typeparam>
    public static void Shuffle<T>(this IList<T> list)
    {
        list.Shuffle(new Random());
    }

    /// <summary>
    /// Shuffles an IList in place.
    /// </summary>
    /// <typeparam name="T">The type of elements in the list</typeparam>
    /// <param name="rand">An instance of a random number generator</param>
    public static void Shuffle<T>(this IList<T> list, Random rand)
    {
        int count = list.Count;
        while (count > 1)
        {
            int i = rand.Next(count--);
            T temp = list[count];
            list[count] = list[i];
            list[i] = temp;
        }
    }
}

Selecting Random Items

The first task I need to perform was to return a random item from a list--that is, randomly select one of the items from the list. At first I thought about shuffling the list, but that's an expensive operation and completely unnecessary in this case. All that's required is to use a psuedo-random number generator, such as the .NET Random class, to come up with a random index into the list. Then I simply grab the item at that index. That's what my Random() method does.

The Random() method also performs a little error checking. If the list is empty or null, then the operation cannot be performed and my extension method just returns null.

I considered and then abandoned a few variations on this method. For example, I considered a version that does not require a Random argument for the convenience of the caller. However, since the default Random constructor seeds the random number generator based on the current time, I found that if my method created a new Random instance each time it is called, and that method was called several times in quick succession, that the new instances were created so quickly that the seed value was the same and a dozen calls could produce the exact same result.

Also, I thought about adding a predicate argument that would filter which items were candidates to be randomly selected. But in the end I figured that LINQ already has this built in. It can easily be accomplished using syntax such as list.Where(item => item < 10).Random(new Random()).

Shuffling Lists

I then thought it might be interesting to go ahead and explore implementing the shuffle operation I mentioned earlier. So I went ahead and added some shuffling methods to my class.

There are a couple of ways to handling shuffling. With the code I was writing at the time, my list was stored in a List<>. List<> lists can be treated like arrays in many ways. So one version of my Shuffle() extension method works with IList<> and efficiently shuffles the items in place. That is, it modifies the list directly.

In the case of an IEnumerable<> list, shuffling in place is more problematic. And so I have another version of Shuffle() that works with IEnumerable<>. This method creates a List<> copy of the collection and then simply delegates to the Shuffle() method for List<>. This version returns a shuffled shallow copy of the list, which the caller must assign to a variable in order to access. Note that List<> also implements IEnumerable<>, however C# will call the more specialized version and so the appropriate version is called depending on if your list is of type List<> or any other type that implements IEnumerable<>.

Unlike my Random() extension method, the Shuffle() methods work on many items at a time and so it does make sense to have a version that creates its own Random instance if the caller does not supply one. If you have an instance of a Random object, you should pass it to the Shuffle() method. Otherwise, the method will produce it's own.

Using the Code

Listing 1 demonstrates use of my extension methods. The code starts by creating an ordered list of ten integers and printing that list. Next, it uses the Random() method to randomly print several items from the list. Finally, it calls Shuffle() to shuffle the entire list and then print the list again.

Listing 2: Using the RandomExtensions Extension Methods

static void Main(string[] args)
{
    // Create ordered list
    List<int> list = new List<int>();
    for (int i = 1; i <= 10; i++)
        list.Add(i);

    // Create psuedo-random number generator
    Random rand = new Random();

    // Print original list
    Console.WriteLine("Original List:");
    Console.WriteLine(String.Join("\r\n", list));
    Console.WriteLine();

    // Print random items from the list
    Console.WriteLine("Random Elements:");
    Console.WriteLine(list.Random(rand));
    Console.WriteLine(list.Random(rand));
    Console.WriteLine(list.Random(rand));
    Console.WriteLine(list.Random(rand));
    Console.WriteLine(list.Random(rand));
    Console.WriteLine();

    // Shuffle and print list
    list.Shuffle(rand);
    Console.WriteLine("Shuffled List:");
    Console.WriteLine(String.Join("\r\n", list));
    Console.WriteLine();

    Console.ReadKey();
}

Conclusion

Extension methods make it very easy to add functionality to existing types. You can use those extension methods just as easily as any built-in method for that type. So I think extension methods are a great addition to C# and the code I've presented above demonstrates that.

I should point out that the .NET Random class has limitations on just how random it is. For my purposes, it was more than adequate. However, if you find you need more randomness, I recommend you expore the .NET System.Security.Cryptography namespace for code that can be used to generate sequences that are more random.

End-User License

Use of this article and any related source code or other files is governed by the terms and conditions of The Code Project Open License.

Author Information

Jonathan Wood

I'm a software/website developer working out of the greater Salt Lake City area in Utah. I've developed many websites including Black Belt Coder, Insider Articles, and others.