Idea is simple, you pass two collections of intervals (Tuple<T, T>), one for included values, another for excluded. Then overlapping intervals of the same kind are combined, and excluding intervals are subtracted from including intervals. As a result you get a new set of intervals.
As T you can use any IComparable, like int, DateTime, double or your custom type.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
public class Interval
{
public enum Border { Left, Right };
public enum IntervalType { Including, Excluding };
struct Point<T>
{
public T Val;
public int Brdr;
public int Intr;
public Point(T value, Border border, IntervalType interval)
{
Val = value;
Brdr = (border == Border.Left) ? 1 : -1;
Intr = (int)interval;
}
public override string ToString() =>
(Brdr == 1 ? "L" : "R") + (Intr == 0 ? "+ " : "- ") + Val;
}
private static IEnumerable<Point<T>> GetBorders<T>
(IEnumerable<Tuple<T, T>> src,
IntervalType intr) =>
src.Select(p => new[] { p.Item1, p.Item2 }).SelectMany(p => p).
Select((v, idx) => new Point<T>(v, (Border)(idx % 2), intr));
public static IEnumerable<Tuple<T, T>> Combine<T>(
IEnumerable<Tuple<T, T>> Include, IEnumerable<Tuple<T, T>> Exclude)
where T : IComparable
{
var INs = GetBorders(Include, IntervalType.Including);
var EXs = GetBorders(Exclude, IntervalType.Excluding);
var intrs = new int[2];
T start = default(T);
foreach (var p in INs.Union(EXs).OrderBy(x => x.Val))
{
var change = (intrs[p.Intr] == 0) ^ (intrs[p.Intr] + p.Brdr == 0);
intrs[p.Intr] += p.Brdr;
if (!change) continue;
var In = p.Intr == 0 && intrs[1] == 0;
var Ex = p.Intr == 1 && intrs[0] > 0;
var Open = intrs[p.Intr] > 0;
var Close = !Open;
if (In && Open || Ex && Close)
{
start = p.Val;
}
else if (In && Close || Ex && Open)
{
yield return new Tuple<T, T>(start, p.Val);
}
}
}
}
}
Example of usage:
Code:
class Program
{
static void Main(string[] args)
{
var include = new[] {
new Tuple<int, int>(10, 100),
new Tuple<int, int>(200, 300),
new Tuple<int, int>(400, 500),
new Tuple<int, int>(420, 480),
};
var exclude = new[] {
new Tuple<int, int>(95, 200),
new Tuple<int, int>(410, 420),
};
foreach (var i in Interval.Combine(include, exclude))
Console.WriteLine(i.Item1 + "-" + i.Item2);
Console.ReadLine();
}
}
The output will be
10-95
205-300
400-410
420-500
NB! border values of excluding intervals are not excluded. E.g. when you subtract (5..10) from [1..7] you end up with [1..5]. To exclude 5, widen the excluding interval like (5..10) => (4..11).
No comments:
Post a Comment