- Notifications
You must be signed in to change notification settings - Fork0
.Net Library that aids in comparison and handling values ranges or time bounded periods.
License
reynj/reynj
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
.Net Library that aids in comparison and handling value ranges or time bounded periods. Other names for a Range are Interval, Sequence, Period, Span, ...
This implementation is based on theRange class as described by Martin Fowler.
The aim of this library is to provide a base Range class with all possible methods that can be performed on a Range, but also extension methods that can be used to handle and compare lists of Ranges.Below is my list of features I want to implement, feel free to open an issue if something is missing on my list.
Click to expand the list
- [ ] Range - [ ] Boundaries - [ ] Implicit Index & Range support, see [Adding Index and Range support to existing library types](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/ranges#adding-index-and-range-support-to-existing-library-types) - [ ] Type of Range should IComparable or IComparable<T> - [x] Implements - [x] IEquatable - [x] IComparable - [x] ICloneable - [ ] IEnumerable ?? - [x] Operators - [x] Equals - [x] CompareTo - [x] Convert to/from Tuple - [ ] Methods - [x] IsNullOrEmpty - [x] Includes & IncludesAll - [x] Overlaps - [x] Touches - [x] Gap - [x] Merge (Union) - [x] Split - [x] Intersection - [x] Exclusive - [ ] EnumerateBy - [x] Ascending (protected) - [ ] Descending (protected) - [ ] EnumerateBy without stepper function (via dynamic or Expressions) (public) - [ ] Expand (change the end) - [ ] Move (change the start and keep the gap between the end the same) - [ ] CenterValue (get the value in the middle between start and end) - [ ] Specific implemenations - [ ] DateRange or Period - [ ] EnumerateBy - [ ] TimeRange - [ ] EnumerateBy - [ ] NumericRange - [ ] EnumerateBy- [ ] Collection of Ranges - [ ] Methods - [x] Lowest/Highest - [x] IsContiguous - [ ] ToRange (only possible for a Contiguous collection) - [x] IsSingle - [x] Reduce - [x] Sort - [x] Union - [x] Intersect - [x] Inverse - [ ] Difference (Relative complement) - [ ] Exclusive - [ ] Enumerate (call EnumerateBy on all ranges) - [ ] ContainsOverlaps (if ranges in the collection overlap)- [ ] Serialize/Deserialize - [x] SerializableAttribute - [x] JsonConvertor (System.Text.Json) - [x] JsonConvertor (Newtonsoft Json.NET) - [ ] Entity Framework ValueConvertor - [ ] NHibernate IUserType- [x] Other - [x] Range<T>.Empty and methods like Merge, Overlaps, Touches, ... - [x] IsEmpty method vs Range<T>.Empty - [x] Support for conversion between System.Range (C# 8.0) and Range<int>
First,install NuGet. Then, installReynj from the package manager console:
PM> Install-Package Reynj
A Range is best visualized as a bar. It has a start and an end and contains everything between those two. Below is a visualization of Range of integers that start at 0 and end at 10. All whole numbers between 0 and 10 are included in the Range, except 10.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Range<int> Range[0,10] : 2018-01-01.00, 10h
To create this Range in code, you can do the following:
varrange=newRange<int>(0,10);
There is only one limitation, the start of Range must be lower or equal to the end.
The type of Range must derive from theIComparable interface. Below some common examples.
// Numeric RangesvarintRange=newRange<int>(0,10);vardoubleRange=newRange<double>(0.0,0.5);// Date and Time RangesvardateRange=newRange<DateTime>(newDateTime(2018,12,18),newDateTime(2018,12,25));// PeriodvartimeSpanRange=newRange<TimeSpan>(TimeSpan.FromHours(0),TimeSpan.FromHours(6));// Duration
Every Range where start and end are equal.
varrange1=newRange<int>(10,10);varrange2=newRange<int>(0,0);varrange3=Range<int>.Empty;// Emptyvarequals=range1.Equals(range2);// returns truevarequals=range2.Equals(range3);// returns truevarcompare=range1.CompareTo(range3);// returns 0
Because Range implements theIEquatable interface, including the operators the following can be done:
varrange1=newRange<int>(0,10);varrange2=newRange<int>(0,10);varrange3=newRange<int>(5,9);// Equalsvarres1=range1.Equals(range2);// returns truevarres2=range1.Equals(range3);// returns false// Equality Operatorsvarres3=range1==range2;// returns truevarres4=range1!=range2;// returns falsevarres5=range1==range3;// returns falsevarres6=range1!=range3;// returns true
Because Range implements theIComparable interface, including the operators the following can be done:
varrange1=newRange<int>(0,10);varrange2=newRange<int>(5,9);// CompareTovarres1=range1.CompareTo(range2);// returns -1// Equality Operatorsvarres2=range1<range2;// returns truevarres3=range1>range2;// returns falsevarres4=range1<=range2;// returns truevarres5=range1>=range2;// returns false
A Range has two primary properties, Start and End, because of that a Range can also be represented as a Tuple, in the lastest versions of .Net as aValueTuple.The constructor of a Range accepts a Tuple of two elements and there is an AsTuple method to convert a Range to a ValueTuple<T, T>.Conversion operators have been implemented to make this even more smoothly.
vartuple=(0,10);varrange=newRange<int>(tuple);// AsTuplevarotherTuple=range.AsTuple();// Conversion OperatorsvarotherTuple2=range;// implicit from Range to TuplevarotherRange=(Range<int>)tuple;// explicit from Tuple to Range
Includes will return true if the given value is a part of the Range, otherwise false.IncludesAll will return true if all of the given values are part of the Range, otherwise false.
varrange=newRange<int>(0,10);// Includes(T value)varres1=range1.Includes(5);// returns truevarres2=range1.Includes(20);// returns false// Includes(Range<T> range)varres1=range1.Includes(newRange<int>(2,7));// returns truevarres2=range1.Includes(newRange<int>(20,30));// returns false// IncludesAllvarres3=range1.IncludesAll(0,1,2,3,4,5,6,7,8,9);// returns truevarres4=range1.IncludesAll(0,1,2,3,4,20,6,7,8,9);// returns false
Overlaps will return true if two Ranges overlap. The following example are two overlapping ranges.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Overlaps Range[0,10] : 2018-01-01.00, 10h Range[5,15] : active, 2018-01-01.05, 10h
varrange1=newRange<int>(0,10);varrange2=newRange<int>(5,15);varrange3=newRange<int>(15,25);// Overlapsvarres1=range1.Overlaps(range2);// returns truevarres2=range2.Overlaps(range1);// returns truevarres3=range1.Overlaps(range3);// returns false
Touches will return true if two Ranges touch each other. The following example are two touching ranges.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Touches Range[0,5] : 2018-01-01.00, 5h Range[5,10] : 2018-01-01.05, 5h
varrange1=newRange<int>(0,10);varrange2=newRange<int>(10,20);varrange3=newRange<int>(11,20);// Overlapsvarres1=range1.Touches(range2);// returns truevarres2=range2.Touches(range1);// returns truevarres3=range1.Touches(range3);// returns false
Gap returns a new Range that represents the gap between two Ranges.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Gap section Ranges Range[0,5] : 2018-01-01.00, 5h Range[10,15] : 2018-01-01.10, 5h section Gap Range[5,10] : active, 2018-01-01.05, 5h
varrange1=newRange<int>(0,10);varrange2=newRange<int>(15,20);// Gapvargap1=range1.Gap(range2);// returns new Range<int>(5, 10)vargap2=range2.Gap(range1);// returns new Range<int>(5, 10)
Merge returns a new Range that represents the combined/merged Range, aLogical disjunction.An exception is thrown when the ranges do not overlap or touch each other.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Merge section Ranges Range[0,5] : 2018-01-01.00, 5h Range[5,20] : 2018-01-01.05, 15h section Merge Range[0,20] : active, 2018-01-01.00, 20h
varrange1=newRange<int>(0,10);varrange2=newRange<int>(5,20);// Mergevarmerge1=range1.Merge(range2);// returns new Range<int>(0, 20)varmerge2=range1|range2;// returns new Range<int>(0, 20)
Split returns a Tuple of two Ranges that have been split on the given value.An exception is thrown when the value is not included in the Range.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Split section Range Range[0,10] : 2018-01-01.00, 10h section Split Range[0,5] : active, 2018-01-01.00, 5h Range[5,10] : active, 2018-01-01.05, 5h
varrange=newRange<int>(0,10);// Splitvarsplit=range.Split(5);// returns (new Range<int>(0, 5), new Range<int>(5, 10))
Intersection returns a new Range that represents the intersection between the current Range and a given Range, aLogical conjunction.An exception is thrown when the ranges do not overlap each other.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Intersection section Ranges Range[0,10] : 2018-01-01.00, 10h Range[5,20] : 2018-01-01.05, 15h section Intersection Range[5,10] : active, 2018-01-01.05, 5h
varrange1=newRange<int>(0,10);varrange2=newRange<int>(5,20);// Intersectionvarintersection1=range1.Intersection(range2);// returns new Range<int>(5, 10)varintersection2=range1&range2;// returns new Range<int>(5, 10)
Exclusive returns a tuple of Ranges that that represent the parts they do not have in common, anExclusive or.An exception is thrown when the ranges are null or Empty.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Exclusive section Ranges Range[0,10] : 2018-01-01.00, 10h Range[5,20] : 2018-01-01.05, 15h section Exclusive Range[0, 5] : active, 2018-01-01.00, 5h Range[10, 20] : active, 2018-01-01.10, 10h
varrange1=newRange<int>(0,10);varrange2=newRange<int>(5,20);// Exclusivevarexclusive=range1.Exlusive(range2);// returns (new Range<int>(0, 5), new Range<int>(10, 20))
IsEmpty will return true if the start and the end of the Range are equal, otherwise false.
varrange1=newRange<int>(0,10);varrange2=newRange<int>(10,10);// IsEmptyvarres1=range1.IsEmpty();// returns falsevarres2=range2.IsEmpty();// returns true
EnumerateBy returns all values between start and end of the Range, given a step and stepper function.The stepper function should always return a value higher than the previous call. There is an overload that allows the step to be of a different type than the typeof start and end of the Range.This function is protected and should be used in class that inherits from Range.
// EnumerateBy(T step, Func<T, T, T> stepper)varrange=newRange<int>(0,10);varvalues=range.EnumerateBy(2,(value,step)=>value+step);// returns 0, 2, 4, 6, 8// EnumerateBy(TStep step, Func<T, TStep, T> stepper)varstartDate=newDateTimeOffset(2020,8,1,8,0,0,TimeSpan.FromHours(2));varendDate=newDateTimeOffset(2020,8,1,14,0,0,TimeSpan.FromHours(2));varrange=newRange<DateTimeOffset>(startDate,endDate);varvalues=range.EnumerateBy(TimeSpan.FromHours(2),(value,step)=>value.Add(step));// returns 2020-08-01 08:00, 2020-08-01 10:00, 2020-08-01 12:00
With the ToRange extension methods on both Range and System.Range a conversion can be done between them.A System.Range and a Reynj.Range have not so much in common, aSystem.Range is meant for accessing single elements or ranges in a sequence,but by converting from them to a Range it is possible to use all methods on Range and Enumerable<Range>.
varrange=newRange<int>(0,10);// ToRange()varsysRange=range.ToRange();// returns new System.Range(0, 10)// ToRange()varreynjRange=sysRange.ToRange();// returns new Range<int>(0, 10)
Provides a converter, namedRangeConverter
for the System.Text.Json library.Be aware that the type of Start and End also require a converter, either included in the System.Text.Json library or from another source.
varoptions=newJsonSerializerOptions{Converters={newRangeConverter()}};// Required// Json Serializevarrange=newRange<int>(0,10);varjsonText=JsonSerializer.Serialize(range,options);// returns '{"Start":0,"End":10}'// Json DeserializevarjsonRange=JsonSerializer.Deserialize(jsonRange,range.GetType(),options);// returns new Range<int>(0, 10)
In your ASP.NET project you can add the following code to the Startup.cs to register the converter.
services.AddControllers().AddJsonOptions(options=>{options.JsonSerializerOptions.Converters.Add(newRangeConverter());// ...});
Provides a converter, namedRangeConverter
for the Newtonsoft.Json library.Be aware that the type of Start and End also require a converter, either included in the Newtonsoft.Json library or from another source.
varsettings=newJsonSerializerSettings{Converters={newRangeConverter()}};// Required// Json Serializevarrange=newRange<int>(0,10);varjsonText=JsonConvert.SerializeObject(range,settings);// returns '{"Start":0,"End":10}'// Json DeserializevarjsonRange=JsonConvert.DeserializeObject(json,typeOfRange,settings);// returns new Range<int>(0, 10)
In your ASP.NET project you can add the following code to the Startup.cs to register the converter.
services.AddControllers().AddNewtonsoftJson(options=>{options.SerializerSettings.Converters.Add(newRangeConverter());// ...});
A Collection of Ranges is a group or list of Ranges of the same type.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title RangeCollection section Ranges Range[0,5] : 2018-01-01.00, 5h Range[7,10] : 2018-01-01.07, 3h Range[10,15] : 2018-01-01.10, 5h Range[18,25] : 2018-01-01.18, 7h
To create an IEnumerable<Range> in code, you can do the following:
// Collection based on an IEnumerable<Range<T>>varranges=newList<Range<int>>(){newRange<int>(0,10),newRange<int>(10,20)}
They return the lowest start or highest end of the all the Ranges in the collection.
varranges=newList<Range<int>>{newRange<int>(0,10),newRange<int>(10,20)};// Lowestvarlowest=ranges.Lowest();// returns 0// Highestvarhighest=ranges.Highest();// returns 20
Returns a new Collection of Ranges where all overlapping and touching Ranges have been merged and empty Ranges have been removed.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Reduce section Ranges Range[0,5] : 2018-01-01.00, 5h Range[3,10] : 2018-01-01.03, 7h Range[10,15] : 2018-01-01.10, 5h Range[18,25] : 2018-01-01.18, 7h section Reduced Range[0,15] : active, 2018-01-01.00, 15h Range[18,25] : active, 2018-01-01.18, 7h
varranges=new[]{newRange<int>(0,5),newRange<int>(3,10),newRange<int>(10,15),newRange<int>(18,25)});// Reducevarreduced=ranges.Reduce();// returns new[] { new Range<int>(0, 15), new Range<int>(18, 25) }
Returns theunion of two Collections of Ranges while reducing them.An exception is thrown when one or both of the ranges are null.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Union section Ranges 1 Range[0,5] : 2018-01-01.00, 5h Range[3,10] : 2018-01-01.03, 7h Range[10,15] : 2018-01-01.10, 5h section Ranges 2 Range[15,17] : 2018-01-01.15, 2h Range[18,25] : 2018-01-01.18, 7h section Unioned Range[0,17] : active, 2018-01-01.00, 17h Range[18,25] : active, 2018-01-01.18, 7h
varranges1=new[]{newRange<int>(0,5),newRange<int>(3,10),newRange<int>(10,15)});varranges2=new[]{newRange<int>(15,17),newRange<int>(18,25)});// Unionvarunioned=ranges1.Union(ranges2);// returns new[] { new Range<int>(0, 17), new Range<int>(18, 25) }
Returns theintersection of two Collections of Ranges while reducing them.An exception is thrown when one or both of the ranges are null.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Intersect section Ranges 1 Range[0,5] : 2018-01-01.00, 5h Range[3,10] : 2018-01-01.03, 7h Range[10,15] : 2018-01-01.10, 5h Range[18,20] : 2018-01-01.18, 2h section Ranges 2 Range[1,8] : 2018-01-01.01, 7h Range[12,25] : 2018-01-01.12, 13h section Intersection Range[1,8] : active, 2018-01-01.01, 7h Range[12,15] : active, 2018-01-01.12, 3h Range[18,20] : active, 2018-01-01.18, 2h
varranges1=new[]{newRange<int>(0,5),newRange<int>(3,10),newRange<int>(10,15),newRange<int>(18,20)});varranges2=new[]{newRange<int>(1,8),newRange<int>(12,25)});// Intersectvarintersection=ranges1.Intersect(ranges2);// returns new[] { new Range<int>(1, 8), new Range<int>(12, 15), new Range<int>(18, 20) }
Returns a new Collection of Ranges that is the inversion of the Ranges. Meaning all gaps between the ranges are returned including the gap between the minvalue and the first start and the last end and the maxvalue.Also known as theabsolute complement.An exception is thrown when the type of Range has no MinValue and MaxValue or when they are not passed.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title Inverse section Ranges Range[0,5] : 2018-01-01.00, 5h Range[10,15] : 2018-01-01.10, 5h Range[18,20] : 2018-01-01.18, 2h section Inversion Range[-∞, 0] : active, 2017-12-31.21, 3h Range[5,10] : active, 2018-01-01.05, 5h Range[15,28] : active, 2018-01-01.15, 3h Range[20,+∞] : active, 2018-01-01.20, 3h
varranges=new[]{newRange<int>(0,5),newRange<int>(10,15),newRange<int>(18,20)});// Inversevarinversion=ranges.Inverse();// returns new[] { new Range<int>(int.MinValue, 0), new Range<int>(5, 10), new Range<int>(15, 18), new Range<int>(20, int.MaxValue) }
Check if a collection of Ranges only contains touching Ranges and form a contiguous sequence.
gantt dateFormat YYYY-MM-DD.HH axisFormat %-H title IsContiguous section Is Contiguous Range[3,10] : 2018-01-01.03, 7h Range[10,15] : 2018-01-01.10, 5h section Is Contiguous (overlap) Range[0, 5] : crit, done, 2018-01-01.00, 5h Range[3,10] : crit, done, 2018-01-01.03, 7h Range[10,15] : active, 2018-01-01.10, 5h section Is Contiguous (gap) Range[3,10] : active, done, 2018-01-01.03, 7h Range[10,15] : crit, done, 2018-01-01.10, 5h Range[18,25] : crit, done, 2018-01-01.18, 7h
varcontiguousRanges=new[]{newRange<int>(10,15),newRange<int>(18,25)});varwithOverlapRanges=new[]{newRange<int>(0,5),newRange<int>(3,10),newRange<int>(10,15)});varnotTouchingRanges=new[]{newRange<int>(3,10),newRange<int>(10,15),newRange<int>(18,25)});// IsContiguousvarisContiguous1=contiguousRanges.IsContiguous();// returns truevarisContiguous2=withOverlapRanges.IsContiguous();// returns falsevarisContiguous3=notTouchingRanges.IsContiguous();// returns false
Helper method on IEnumerable that returns true if the sequence contains exacly one element.
varemptyList=newList<Range<int>>();varsingleItemList=new[]{newRange<int>(0,5)});varmultiItemList=new[]{newRange<int>(3,10),newRange<int>(10,15)});// IsSinglevarisSingle1=emptyList.IsSingle();// returns falsevarisSingle2=singleItemList.IsSingle();// returns truevarisSingle3=multiItemList.IsSingle();// returns false
About
.Net Library that aids in comparison and handling values ranges or time bounded periods.