I would do a little pre-processing before drawing the numbers to get a list of possible ranges. So let's assume we have a Range structure like so:
/// <summary> A possible range of values. </summary>
public struct Range
{
/// <summary> Min value, inclusive. </summary>
public readonly double Min;
/// <summary> Max value, inclusive. </summary>
public readonly double Max;
public Range(double min, double max) { Min = min; Max = max; }
/// <summary> Range length, distance between Min and Max. </summary>
public double Length { get { return Max - Min; } }
}
And another structure RangeList which holds several ranges together. Range list also contains a cumulative length array of successive length sums of your Ranges, like so:
/// <summary> All possible ranges grouped together. </summary>
public struct RangeList
{
/// <summary> Possible range. </summary>
public readonly Range[] Ranges;
/// <summary> Sum of each range length. </summary>
public readonly double Length;
/// <summary> Cumulative lengths values of each ranges. </summary>
public readonly double[] CumulLengths;
public RangeList(Range[] ranges)
{
Ranges = ranges;
Length = 0;
CumulLengths = new double[ranges.Length];
for (var i = 0; i < ranges.Length; ++i)
{
Length += ranges[i].Length;
CumulLengths[i] = Length;
}
}
}
We can then write easily a function that creates a RangeList from a given list of excluded ranges:
/// <summary> Get possible ranges to draw from, considering exclusions. </summary>
public static RangeList GetRangeList(Range range, params Range[] exclusions)
{
var ranges = new List<Range>();
ranges.Add(range);
if (exclusions != null)
{
foreach (var exclusion in exclusions)
{ // progressively eat latest range added to the list, cutting exclusions.
var lastRange = ranges[ranges.Count - 1];
if (exclusion.Min < lastRange.Max)
{
ranges[ranges.Count - 1] = new Range(lastRange.Min, exclusion.Min);
if (exclusion.Max < lastRange.Max)
{
ranges.Add(new Range(exclusion.Max, lastRange.Max));
}
}
}
}
return new RangeList(ranges.ToArray());
}
This method relies on several assumptions, including that not all space is excluded, exclusions are not overlapping, and exclusions are given in ascending order.
It is then straight-forward to draw a number from the possible ranges:
/// <summary> Assume exclusions are also given in ranges. </summary>
public static double RangeWithExclusions(this Random random, Range range, params Range[] exclusions)
{
var rangeList = GetRangeList(range, exclusions);
var rnd = random.NextDouble() * rangeList.Length;
var rangeIndex = Array.BinarySearch(rangeList.CumulLengths, rnd);
if (rangeIndex < 0)
{ // 'unlucky', we didn't hit a length exactly
rangeIndex = ~rangeIndex;
}
var previousLength = rangeIndex > 0 ? rangeList.CumulLengths[rangeIndex - 1] : 0;
var rndRange = rangeList.Ranges[rangeIndex]; // result range of our random draw
return rndRange.Min + (rnd - previousLength); // scale rnd back into range space
}
The following NUnit test demonstrate how to use the solution:
[TestFixture]
public class TestRandom
{
[Test]
public void Tests()
{
var random = new Random();
double rnd;
rnd = random.RangeWithExclusions(new Range(0, 1));
Assert.IsTrue(rnd >= 0 && rnd <= 1);
rnd = random.RangeWithExclusions(new Range(-100, 1));
Assert.IsTrue(rnd >= -100 && rnd <= 1);
rnd = random.RangeWithExclusions(new Range(0, 1), new Range(0.1, 0.9));
Assert.IsTrue(rnd >= 0 && rnd <= 1 && (rnd <= 0.1 || rnd >= 0.9));
rnd = random.RangeWithExclusions(new Range(0, 1), new Range(0, 0.9));
Assert.IsTrue(rnd >= 0 && rnd <= 1 && (rnd >= 0.9));
rnd = random.RangeWithExclusions(new Range(0, 1), new Range(0.2, 0.4), new Range(0.6, 0.8));
Assert.IsTrue(rnd >= 0 && rnd <= 1 && (rnd <= 0.2 || rnd >= 0.4) && (rnd <= 0.6 || rnd >= 0.8));
}
}
Hope this helps