Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C#: Integer Range Helper

0.00/5 (No votes)
2 Jun 2019 1  
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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here