65.9K
CodeProject is changing. Read more.
Home

C#: Integer Range Helper

Jun 2, 2019

CPOL

1 min read

viewsIcon

15117

downloadIcon

67

Utility class and model to manage range related operations

Introduction

By default in C#, we have Enumerable.Range(Int32, Int32) which generates a sequence of integral numbers within a specified range. Here in this article, we are going to explore a few more options to extend the range related operations.

Background

What Are We Going to Do?

  • Create a model, which will:
    • Define a range
    • Check if an item is inside the range
    • Check if a range is inside the range
    • Check if a range is overlapping the range
  • Create a utility class:
    • Populating items of the range
    • List to sequence ranges
    • List to sequence range string list specifying the start and end item
    • Find overlapping items in input subranges
    • Find missing items in input subranges
    • Find unknown items in input subranges

Range Model

Here is our range model:

using System;

public class IntegerRangeModel
{
    public int StartFrom { get; protected set; }
    public int EndTo { get; protected set; }
    public int Distance { get; protected set; }

    public bool IncludeStartFrom { get; protected set; }
    public bool IncludeEndTo { get; protected set; }
    public int ActualStartFrom { get; protected set; }
    public int ActualEndTo { get; protected set; }


    public IntegerRangeModel(int startFrom, int endTo, int distance = 1, 
                             bool includeStartFrom = true, bool includeEndTo = true)
    {
        StartFrom = startFrom;
        EndTo = endTo;
        Distance = distance;
        IncludeStartFrom = includeStartFrom;
        IncludeEndTo = includeEndTo;

        ActualStartFrom = IncludeStartFrom ? StartFrom : StartFrom + distance;
        ActualEndTo = IncludeEndTo ? EndTo : EndTo - distance;
        if (ActualStartFrom > ActualEndTo)
        {
            throw new ArgumentException("Range start shouldn't be greater than range start");
        }
    }

    public bool Includes(int value)
    {
        bool includes = ActualStartFrom <= value && value <= ActualEndTo;
        return includes;
    }

    public bool Includes(IntegerRangeModel range)
    {
        bool includes = Includes(range.ActualStartFrom) && Includes(range.ActualEndTo);
        return includes;
    }

    /*https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap*/
    public bool Overlapes(IntegerRangeModel range)
    {
        bool includes = false;
        includes = ActualStartFrom <= range.ActualEndTo && 
        ActualEndTo >= range.ActualStartFrom;    /*(StartA <= EndB)  and(EndA >= StartB)*/
        //includes = Includes(range.StartFrom) || 
        //     Includes(range.EndTo);    /*can also us this one*/
        return includes;
    }
}

Utility Class

Using the range model in the utility class:

using System;
using System.Collections.Generic;
using System.Linq;

public class IntegerRangeUtility
{
    private static IEnumerable<int> Range(int start, int end)
    {
        if (start > end)
        {
            throw new ArgumentException("Range start shouldn't be greater than range start");
        }
        int count = end - start + 1;
        return Enumerable.Range(start, count);
    }

    public static IEnumerable<int> Range(IntegerRangeModel model)
    {
        int start = model.ActualStartFrom;
        int end = model.ActualEndTo;
        return Range(start, end);
    }

    /*
    * missing, overlapping
    * https://stackoverflow.com/questions/7024051/
    * find-missing-and-overlapping-numbers-in-sequences
    */
    public static IEnumerable<int> Overlappings
    (IEnumerable<int> expectedRangeItems, IEnumerable<IntegerRangeModel> sourceRanges)
    {
        IEnumerable<int> overlapping = expectedRangeItems.Where
        (i => sourceRanges.Count(t => t.ActualStartFrom <= i && t.ActualEndTo >= i) > 1);
        return overlapping;
    }

    public static IEnumerable<int> Missings(IEnumerable<int> expectedRangeItems, 
                                   IEnumerable<IntegerRangeModel> sourceRanges)
    {
        IEnumerable<int> missing = expectedRangeItems.Where
           (i => sourceRanges.All(t => t.ActualStartFrom > i || t.ActualEndTo < i));
        return missing;
    }

    public static IEnumerable<int> Unknowns(IEnumerable<int> expectedRangeItems, 
                           IEnumerable<IntegerRangeModel> sourceRanges)
    {
        HashSet<int> hash = new HashSet<int>();
        foreach (var sourceRange in sourceRanges.OrderBy(x => x.ActualStartFrom))
        {
            foreach (var item in Range(sourceRange.ActualStartFrom, sourceRange.ActualEndTo))
            {
                if (!expectedRangeItems.Contains(item))
                {
                    if (hash.Add(item))
                    {
                        yield return item;
                    }
                }
            }
        }
    }

    /*
    * https://stackoverflow.com/questions/19576504/
    * find-available-numbers-from-a-list-of-numbers-in-a-particular-range
    * https://stackoverflow.com/questions/4936876/
    * grouping-into-ranges-of-continuous-integers/4937283#4937283
    */
    public static IEnumerable<List<T>> ToContiguousSequences<T>
                       (IEnumerable<T> sequence, Func<T, T> next)
    {
        sequence = sequence.OrderBy(x => x);                            
        var e = sequence.GetEnumerator();
        if (!e.MoveNext())
        {
            throw new InvalidOperationException("Sequence is empty.");
        }
        var currentList = new List<T> { e.Current };
        while (e.MoveNext())
        {
            T current = e.Current;
            if (current.Equals(next(currentList.Last())))
            {
                currentList.Add(current);
            }
            else
            {
                yield return currentList;
                currentList = new List<T> { current };
            }
        }
        yield return currentList;
    }

    public static IEnumerable<List<int>> ToContiguousSequences
                          (IEnumerable<int> source, int distance = 1)
    {
        Func<int, int> nextFunc = n => n + distance;
        return ToContiguousSequences(source, nextFunc);
    }

    public static IEnumerable<List<int>> ToContiguousSequences
                (IEnumerable<int> source, IntegerRangeModel sourceRange)
    {
        return ToContiguousSequences(source, sourceRange.Distance);
    }


    public static IEnumerable<string> ToRangesString(IEnumerable<int> source)
    {
        foreach (var sequence in ToContiguousSequences(source))
        {
            string rangeString = String.Format(@"{0}-{1}", sequence.First(), sequence.Last());
            yield return rangeString;
        }
    }
}

Using the Range Model

Define an Expected Range

var intRange = new IntegerRangeModel(1, 100);
bool result;

Check if an Item Is in the Range

result = intRange.Includes(0);     /*false*/
result = intRange.Includes(1);     /*true*/
result = intRange.Includes(100);   /*true*/
result = intRange.Includes(50);    /*true*/
result = intRange.Includes(101);   /*false*/

Check if a Range Is in the Range

result = intRange.Includes(new IntegerRangeModel(-10, 10));     /*false*/
result = intRange.Includes(new IntegerRangeModel(1, 100));      /*true*/
result = intRange.Includes(new IntegerRangeModel(2, 99));       /*true*/
result = intRange.Includes(new IntegerRangeModel(90, 110));     /*false*/

Check if a Range Is Overlapping the Range

result = intRange.Overlapes(new IntegerRangeModel(-20, -10));  /*false*/
result = intRange.Overlapes(new IntegerRangeModel(-10, 10));   /*true*/
result = intRange.Overlapes(new IntegerRangeModel(1, 100));    /*true*/
result = intRange.Overlapes(new IntegerRangeModel(2, 99));     /*true*/
result = intRange.Overlapes(new IntegerRangeModel(90, 110));   /*true*/
result = intRange.Overlapes(new IntegerRangeModel(101, 110));  /*false*/

Let's get started with the utility class.

Using the Utility Class

var expectedRange = new IntegerRangeModel(1, 100);  /*target range 1-100*/
var inputSubRanges = new List<IntegerRangeModel>()
{
    new IntegerRangeModel(-10, 0),          /*unknown: -10-0 will not appear in overlapping*/
    new IntegerRangeModel(-10, 0),          /*unknown: -10-0*/
    new IntegerRangeModel(1, 10),           /*overlapping 5-10*/
    new IntegerRangeModel(5, 15),           /*overlapping 5-10*/
    //new IntegerRangeModel(16, 30),        /*missing 16-30*/
    new IntegerRangeModel(31, 40),          /*overlapping 31-40*/
    new IntegerRangeModel(31, 40),          /*overlapping 31-40*/
    new IntegerRangeModel(41, 70),    
    //new IntegerRangeModel(71, 80),        /*missing 71-80*/
    new IntegerRangeModel(81, 100),
    new IntegerRangeModel(101, 115),        /*unknown: 101-120*/
    new IntegerRangeModel(105, 120),        /*unknown: 101-120 will not appear in overlapping*/
};

Populating a Range of Items

List<int> range = IntegerRangeUtility.Range(expectedRange).ToList();

List to Sequence Ranges

List<List<int>> ranges = IntegerRangeUtility.ToContiguousSequences
                            (range).ToList();    /*distance 1*/
List<List<int>> rangesAsSourceDistance = IntegerRangeUtility.ToContiguousSequences
                       (range, expectedRange).ToList();  /*distance as source*/

List to Sequence Range String List Specifying the Start and End Item

List<string> rangeStrings = IntegerRangeUtility.ToRangesString(range).ToList();

Find Overlapping Items in Input Subranges

List<int> overlappings = IntegerRangeUtility.Overlappings(range, inputSubRanges).ToList();
List<string> overlappingRangeStrings = 
        IntegerRangeUtility.ToRangesString(overlappings).ToList();

Find Missing Items in Input Subranges

List<int> missings = IntegerRangeUtility.Missings(range, inputSubRanges).ToList();
List<string> missingRangeStrings = IntegerRangeUtility.ToRangesString(missings).ToList();

Find Unknown Items in Input Subranges

List<int> unkowns = IntegerRangeUtility.Unknowns(range, inputSubRanges).ToList();
List<string> unkownRangeStrings = IntegerRangeUtility.ToRangesString(unkowns).ToList();

Generic Solution

To manage ranges of different types like DateTime, double or any user-defined type please check  C#: Generic Range Helper

Future Improvements

Combining overlapping items http://stackoverflow.com/questions/32196549/combining-overlapping-date-ranges-java

 

Please find Visual Studio 2017 solution as an attachment.

History

  • 2nd June, 2019: Initial version