Click here to Skip to main content
15,566,064 members
Articles / Programming Languages / C#
Tip/Trick
Posted 2 Jun 2019

Tagged as

Stats

11.9K views
66 downloads
6 bookmarked

C#: Integer Range Helper

Rate me:
Please Sign up or sign in to vote.
4.80/5 (7 votes)
2 Jun 2019CPOL1 min read
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:

C#
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:

C#
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

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

Check if an Item Is in the Range

C#
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

C#
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

C#
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

C#
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

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

List to Sequence Ranges

C#
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

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

Find Overlapping Items in Input Subranges

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

Find Missing Items in Input Subranges

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

Find Unknown Items in Input Subranges

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Bangladesh Bangladesh
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Praisevery nice Pin
BillW3318-Jun-19 8:39
professionalBillW3318-Jun-19 8:39 
GeneralRe: very nice Pin
DiponRoy19-Jun-19 10:35
DiponRoy19-Jun-19 10:35 
SuggestionOverlaps instead of Overlappes? Pin
F Margueirat3-Jun-19 11:15
F Margueirat3-Jun-19 11:15 
GeneralRe: Overlaps instead of Overlappes? Pin
DiponRoy9-Jun-19 22:10
DiponRoy9-Jun-19 22:10 
GeneralRe: Overlaps instead of Overlappes? Pin
DiponRoy10-Jun-19 8:48
DiponRoy10-Jun-19 8:48 
QuestionGeneric range class Pin
LucianPopescu3-Jun-19 1:00
LucianPopescu3-Jun-19 1:00 
using System;
using System.Collections.Generic;

namespace Utilities
{
    public class Range<T>
    {
        public T Start { get; }

        public T End { get; }

        private IComparer<T> Comparer { get; }

        private bool IncludesStart { get; }

        private bool IncludesEnd { get; }

        public Range(T start, T end)
            : this(start, end, Comparer<T>.Default, true, true)
        {
        }

        public Range(T start, T end, IComparer<T> comparer, bool includeStart = true, bool includeEnd = true)
        {
            if (comparer.Compare(start, end) > 0)
            {
                throw new ArgumentOutOfRangeException(nameof(end),
                    "start must be lower than or equal to end according to comparer");
            }

            this.Start = start;
            this.End = end;
            this.Comparer = comparer;
            this.IncludesStart = includeStart;
            this.IncludesEnd = includeEnd;
        }

        public bool Contains(T value)
        {
            var lowerBound = this.Comparer.Compare(value, this.Start);
            if ((lowerBound < 0 || lowerBound == 0) && !this.IncludesStart) return false;

            var upperBound = this.Comparer.Compare(value, this.End);
            return (upperBound < 0 || upperBound == 0) && this.IncludesEnd;
        }
    }
}

AnswerRe: Generic range class Pin
DiponRoy9-Jun-19 22:21
DiponRoy9-Jun-19 22:21 
AnswerRe: Generic range class Pin
DiponRoy10-Jun-19 8:51
DiponRoy10-Jun-19 8:51 
AnswerRe: Generic range class Pin
DiponRoy18-Jun-19 6:50
DiponRoy18-Jun-19 6:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.