The .NET libraries provide a pretty simple API for generating random numbers. This works well for simple scenarios, but it can quickly result in some clunky and unmanageable tangles of code as the complexity of your problem rises. Specifically, I've found this to be the case when you want to generate multiple, related values. For example, I've been working on a WPF app recently where I needed to generate random, off-screen points for use in animations. For our purposes, "off-screen" means occupying a kind of "frame" around the screen; say, 500 pixels on each side. You can imagine the mess of if-statements that would result from trying to handle this situation directly in code. It occurred to me that it would be really nice to break the problem down into smaller chunks and filter appropriately. Well, why can't we?
When you define a new instance of Random, the result isn't too far off from an iterator: you simply call Next over and over again to process more values. With a little bit of wrapping code, we can treat it just like a stream. Something like this:
1: public static class RandomGenerator
2: { 3: private static Random _rand = new Random((int)DateTime.Now.Ticks);
4:
5: public static IEnumerable<int> Next(int maxValue)
6: { 7: while (true)
8: { 9: yield return _rand.Next(maxValue);
10: }
11: }
12: }
Which we can then evaluate like this:
1: static void Main(string[] args)
2: { 3: foreach (int i in RandomGenerator.Next(10).Take(5))
4: { 5: Console.WriteLine(i);
6: }
7: }
This is a pretty trivial example, but it shows you how to define your own infinite stream. Now that we have our random numbers coming back in an enumerable stream, we're free to use LINQ to filter them as we please. Note that filtering is actually required on an infinite sequence; otherwise, you'll end up taking the whole thing, and it's . . . well, infinite. Could take a while. So make sure to restrict your usage down to a finite number of terms (e.g. the Take(5) invocation in this example). Ok, so we have this, and it works:
But this isn't exactly interesting by itself. How can we use this to solve our problem? Well, the fact that we can deal with random numbers in streams makes it pretty simple to build smaller functions we can then compose to get what we want. This will also make the code trivially extensible, so reuse will be a snap. Let's take a look by adding a little bit to our RandomGenerator class. First, I'm going to add a couple extension methods to Random that will be useful to us:
1: private static double NextDouble(this Random rand, double minValue, double maxValue)
2: { 3: return minValue + (maxValue - minValue) * rand.NextDouble();
4: }
5:
6: private static double NextDouble(this Random rand, double maxValue)
7: { 8: return rand.NextDouble(0.0, maxValue);
9: }
NextDouble returns a value between 0.0 and 1.0, so you end up writing code like this a lot if you want to map to more interesting values. With that out of the way, let's dive into our problem space. First, we'll add some simple methods to return a stream of points:
1: public static IEnumerable<Point> GetRandomPoints
2: (double minX, double minY, double maxX, double maxY)
3: { 4: while (true)
5: { 6: yield return new Point(
7: _rand.NextDouble(minX, maxX),
8: _rand.NextDouble(minY, maxY));
9: }
10: }
11:
12: public static IEnumerable<Point> GetRandomPoints(Point min, Point max)
13: { 14: return GetRandomPoints(min.X, min.Y, max.X, max.Y);
15: }
16:
17: public static IEnumerable<Point> GetRandomPoints(Rect area)
18: { 19: return GetRandomPoints(area.TopLeft, area.BottomRight);
20: }
These are just a few overloads of a function for creating random points within the specified range. Let's try it out, like so:
1: foreach (var point in RandomGenerator
2: .GetRandomPoints(0.0, 0.0, 100.0, 100.0)
3: .Take(5))
4: { 5: Console.WriteLine(point);
6: }
Which produces something like this (results will vary):
Ok, not bad. Now we have a stream of points. Let's see what we can do about filtering them. We'll define another method in our static class:
1: private static readonly Rect OuterFrame =
2: new Rect(new Point(0.0, 0.0), new Point(100.0, 100.0));
3: private static readonly Rect InnerFrame =
4: new Rect(new Point(10.0, 10.0), new Point(90.0, 90.0));
5:
6: public static IEnumerable<Point> GetRandomOffscreenPoints()
7: { 8: foreach (var point in GetRandomPoints(OuterFrame)
9: .Where(p => !InnerFrame.Contains(p)))
10: { 11: yield return point;
12: }
13: }
We've merely layered another enumeration on top of our existing one. It grabs random points and filters based on our predicate. A simple test of our new method shows that it produces points lying in the expected "frame":
Of course, enumerations aren't always what you want to deal with, but it's not a stretch to create a wrapper returning a single point:
1: public static Point GetRandomOffscreenPoint()
2: { 3: return GetRandomOffscreenPoints().First();
4: }
Once you start thinking in terms of sequences, it's clear that a lot of problems can be solved likewise. My experience has been that this can lead to simple solutions to complex problems. The simplicity in each step makes it easy to verify the correctness of the algorithm. It also makes it really easy to extend for new problems in the same domain, since each step is separated into its own method.
Hope this helps.