目录
介绍
背景
我们会做什么?
范围(Range)模型
创建整数范围(Range)
使用整数范围(Range)
创建日期时间范围(Range)
使用DateTime范围
- 下载解决方案 - 7.3 KB
默认情况下,在C#中,我们有Enumerable.Range(Int32, Int32) ,其生成一个指定范围内的整数序列。我们期待着一种管理不同数据类型范围而不仅仅是整数的解决方案。
背景 我们会做什么?创建类型明确的范围模式类,它将:
- 定义范围
- 检查项是否在范围内
- 检查范围是否在范围内
- 检查范围是否与范围重叠
- 填充范围的项
- 列出有序范围
- 列出指定开始和结束项的有序范围字符串列表
- 在输入子范围中查找重叠项
- 在输入子范围中查找缺少的项目
- 在输入子范围中查找未知项
这是基本范围模型:
using System;
using System.Collections.Generic;
using System.Linq;
public abstract class RangeMode : IComparer
{
public TSource StartFrom { get; protected set; }
public TSource EndTo { get; protected set; }
public TDistance Distance { get; protected set; }
public bool IncludeStartFrom { get; protected set; }
public bool IncludeEndTo { get; protected set; }
public TSource ActualStartFrom { get; protected set; }
public TSource ActualEndTo { get; protected set; }
private IEnumerable _items;
protected RangeMode(TSource startFrom, TSource endTo,
TDistance distance, bool includeStartFrom, bool includeEndTo)
{
StartFrom = startFrom;
EndTo = endTo;
Distance = distance;
IncludeStartFrom = includeStartFrom;
IncludeEndTo = includeEndTo;
ActualStartFrom = IncludeStartFrom ? StartFrom : NextValue(StartFrom);
ActualEndTo = IncludeEndTo ? EndTo : PreviousValue(EndTo);
if (Greater(ActualStartFrom, ActualEndTo))
{
throw new ArgumentException("Range start shouldn't be greater than range end");
}
}
protected virtual string ValueString(TSource value)
{
return value.ToString();
}
protected virtual string RangeStringFormat()
{
var value = @"{0}-{1}";
return value;
}
private string RangeString(TSource startFrom, TSource endTo)
{
var value = String.Format
(RangeStringFormat(), ValueString(startFrom), ValueString(endTo));
return value;
}
public string RangeString(bool considerActualStartEndValues = false)
{
var value = considerActualStartEndValues
? RangeString(ActualStartFrom, ActualEndTo)
: RangeString(StartFrom, EndTo);
return value;
}
protected abstract TSource NextValue(TSource currentValue);
protected abstract TSource PreviousValue(TSource currentValue);
///
/// Value
/// less Than 0, x is less than y.
/// equal 0, x equals y.
/// grater than 0, x is greater than y.
///
public abstract int Compare(TSource x, TSource y);
private bool Equal(TSource x, TSource y)
{
var value = Compare(x, y) == 0;
return value;
}
private bool Greater(TSource x, TSource y)
{
var value = Compare(x, y) > 0;
return value;
}
private bool Less(TSource x, TSource y)
{
var value = Compare(x, y) < 0;
return value;
}
public bool Includes(TSource value)
{
bool includes = (Less(ActualStartFrom, value) || Equal(ActualStartFrom, value))
&& (Greater(ActualEndTo, value) || Equal(value, ActualEndTo));
return includes;
}
public bool Includes(RangeMode range)
{
bool includes = Includes(range.ActualStartFrom) && Includes(range.ActualEndTo);
return includes;
}
public bool Overlaps(RangeMode range)
{
bool includes = includes = Includes(range.ActualStartFrom) ||
Includes(range.ActualEndTo);
return includes;
}
protected IEnumerable PopulateRangeItems()
{
/*return start value*/
TSource currentValue = ActualStartFrom;
yield return currentValue;
/*values between start and end*/
while (true)
{
currentValue = NextValue(currentValue);
if (Greater(currentValue, ActualEndTo) || Equal(currentValue, ActualEndTo))
{
break;
}
yield return currentValue;
}
/*return end value*/
currentValue = ActualEndTo;
yield return currentValue;
}
public IEnumerable Items()
{
if (_items == null)
{
SetItems(PopulateRangeItems());
}
return _items;
}
public void SetItems(IEnumerable values)
{
_items = values;
}
public void RepopulateItems()
{
_items = null;
Items();
}
public IEnumerable Overlappings
(IEnumerable sourceRanges)
{
IEnumerable overlapping = Items().Where(i => sourceRanges.Count(t =>
(t.Less(t.ActualStartFrom, i) || t.Equal(t.ActualStartFrom, i))
&& (t.Greater(t.ActualEndTo, i) || t.Equal(t.ActualEndTo, i))
) > 1);
return overlapping;
}
public IEnumerable Missings
(IEnumerable sourceRanges)
{
IEnumerable missing = Items().Where(i => sourceRanges.All(t =>
t.Greater(t.ActualStartFrom, i)
|| t.Less(t.ActualEndTo, i)
));
return missing;
}
public IEnumerable Unknowns
(IEnumerable sourceRanges)
{
HashSet hash = new HashSet();
foreach (var sourceRange in sourceRanges.OrderBy(x => x.ActualStartFrom))
{
foreach (var item in sourceRange.Items())
{
if (!Items().Contains(item))
{
if (hash.Add(item))
{
yield return item;
}
}
}
}
}
protected IEnumerable ToContiguousSequences
(IEnumerable sequence, RangeMode rangeMode)
{
sequence = sequence.OrderBy(x => x);
var e = sequence.GetEnumerator();
if (!e.MoveNext())
{
throw new InvalidOperationException("Sequence is empty.");
}
var currentList = new List { e.Current };
while (e.MoveNext())
{
TSource current = e.Current;
TSource targetNextValue = rangeMode.NextValue(currentList.Last());
if (current.Equals(targetNextValue))
{
currentList.Add(current);
}
else
{
yield return currentList;
currentList = new List { current };
}
}
yield return currentList;
}
public IEnumerable ToContiguousSequences(IEnumerable sequence)
{
return ToContiguousSequences(sequence, this);
}
public IEnumerable ToRangesString(IEnumerable source)
{
foreach (var sequence in ToContiguousSequences(source, this))
{
string rangeString = this.RangeString(sequence.First(), sequence.Last());
yield return rangeString;
}
}
}
在构造函数RangeMode(T startFrom, T endTo, uint distance, bool includeStartFrom, bool includeEndTo) 中,我们有以下选项:
- 在范围或任何逻辑中包含/排除startFrom值
- 在范围或任何逻辑中包含/排除endTo值
- 可以设置两个项之间的距离
如果已经有一个填充的范围列表,并且我们不希望从模型中重新填充它,那么使用SetItems(IEnumerable values)。
abstract类将迫使任何派生类来实现:
- 考虑当前项目和所需距离abstract T NextValue(T currentValue)创建下一个项
- 考虑当前项目和所需距离abstract T PreviousValue(T currentValue) 创建下一个项
- abstract int Compare(T x, T y) 比较两个数据对象
还有一些可用的方法如string ValueString(T value),string RangeStringFormat(),如果需要,其也可以由派生类实现。
创建整数范围(Range)扩展整数数据类型的基本范围模型:
using System;
public class IntegerRange : RangeMode
{
public IntegerRange(int startFrom, int endTo, uint distance = 1,
bool includeStartFrom = true, bool includeEndTo = true)
: base(startFrom, endTo, distance, includeStartFrom, includeEndTo)
{
}
public override int Compare(int x, int y)
{
var value = x.CompareTo(y);
return value;
}
protected override int NextValue(int currentValue)
{
var value = currentValue + (int)Distance;
return value;
}
protected override int PreviousValue(int currentValue)
{
var value = currentValue - (int)Distance;
return value;
}
}
正如我们在构造函数中看到的那样,我们设置了默认选项,如:
- 包括/排除范围或任何逻辑(default: true)的startFrom值
- 包括/排除范围或任何逻辑(default: true)的endTo值
- 设置两个项 (default: 1, can set it to 2 to create range like 1, 3, 5, 7 ... N)之间的距离
其他:
- abstract T NextValue(T currentValue):添加到当前值的距离
- abstract T PreviousValue(T currentValue) 扣除当前值的距离
定义预期范围
var intRange = new IntegerRange(1, 100);
bool result;
检查项目是否在范围内
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*/
检查范围是否在范围内
result = intRange.Includes(new IntegerRange(-10, 10)); /*false*/
result = intRange.Includes(new IntegerRange(1, 100)); /*true*/
result = intRange.Includes(new IntegerRange(2, 99)); /*true*/
result = intRange.Includes(new IntegerRange(90, 110)); /*false*/
检查范围是否重叠范围
result = intRange.Overlaps(new IntegerRange(-20, -10)); /*false*/
result = intRange.Overlaps(new IntegerRange(-10, 10)); /*true*/
result = intRange.Overlaps(new IntegerRange(1, 100)); /*true*/
result = intRange.Overlaps(new IntegerRange(2, 99)); /*true*/
result = intRange.Overlaps(new IntegerRange(90, 110)); /*true*/
result = intRange.Overlaps(new IntegerRange(101, 110)); /*false*/
范围和子范围操作
var expectedRange = new IntegerRange(1, 100); /*target range 1-100*/
var inputSubRanges = new List()
{
new IntegerRange(-10, 0), /*unknown: -10-0 will not appear in overlapping*/
new IntegerRange(-10, 0), /*unknown: -10-0*/
new IntegerRange(1, 10), /*overlapping 5-10*/
new IntegerRange(5, 15), /*overlapping 5-10*/
//new IntegerRange(16, 30), /*missing 16-30*/
new IntegerRange(31, 40), /*overlapping 31-40*/
new IntegerRange(31, 40), /*overlapping 31-40*/
new IntegerRange(41, 70),
//new IntegerRange(71, 80), /*missing 71-80*/
new IntegerRange(81, 100),
new IntegerRange(101, 115), /*unknown: 101-120*/
new IntegerRange(105, 120), /*unknown: 101-120 will not appear in overlapping*/
};
填充项范围
List range = expectedRange.Items().ToList();
有序范围列表
List ranges = expectedRange.ToContiguousSequences(range).ToList();
指定开始和结束项的有序范围字符串列表
List rangeStrings = expectedRange.ToRangesString(range).ToList();
在输入子范围中查找重叠项
List overlappings = expectedRange.Overlappings(inputSubRanges).ToList();
List overlappingRangeStrings = expectedRange.ToRangesString(overlappings).ToList();
在输入子范围中查找缺少的项目
List missings = expectedRange.Missings(inputSubRanges).ToList();
List missingRangeStrings = expectedRange.ToRangesString(missings).ToList();
在输入子范围中查找未知项
List unkowns = expectedRange.Unknowns(inputSubRanges).ToList();
List unkownRangeStrings = expectedRange.ToRangesString(unkowns).ToList();
创建日期时间范围(Range)
让我们为DateTime类型创建一个范围模型:
using System;
public class DateRange : RangeMode
{
public const string DefaultFormatString = "dd-MM-yyyy";
public static string FormatString = ""; /*Set if need different format here*/
public DateRange(DateTime startFrom, DateTime endTo,
uint distance = 1, bool includeStartFrom = true, bool includeEndTo = true)
: base(startFrom, endTo, distance,
includeStartFrom, includeEndTo) /*deference in days*/
{
}
public override int Compare(DateTime x, DateTime y)
{
var value = x.CompareTo(y);
return value;
}
protected override DateTime NextValue(DateTime currentValue)
{
var value = currentValue.AddDays((int)Distance); /*deference in days,
or do as needed*/
return value;
}
protected override DateTime PreviousValue(DateTime currentValue)
{
var value = currentValue.AddDays(-1*(int)Distance); /*deference in days,
or do as needed*/
return value;
}
protected override string ValueString(DateTime value)
{
var format = string.IsNullOrEmpty(FormatString) ?
DefaultFormatString : FormatString;
return value.ToString(format);
}
}
构造函数与整数类型相同:
- 包括/排除范围或任何逻辑(default: true)的startFrom值
- 包括/排除范围或任何逻辑(default: true)的endTo值
- 设置两个项(default: 1)之间的距离
通过从当前DateTime值中扣除/添加距离作为一天来计算Previous 和Next值。
还使用默认日期时间格式将DateTime对象转换为DateTime string。
使用DateTime范围一种创建DateTime对象的辅助方法。
private static DateTime Date(int day, int hour = 0)
{
/*May 2019*/
int year = 2019;
int month = 5;
DateTime dateTime = new DateTime(year: year, month: month, day: day,
hour: hour, minute:0, second:0);
return dateTime;
}
与整数范围模型相同的示例。
var dateRange = new DateRange(Date(10), Date(20));
bool result;
result = dateRange.Includes(Date(1)); /*false*/
result = dateRange.Includes(Date(10)); /*true*/
result = dateRange.Includes(Date(20)); /*true*/
result = dateRange.Includes(Date(15)); /*true*/
result = dateRange.Includes(Date(21)); /*false*/
result = dateRange.Includes(new DateRange(Date(1), Date(9))); /*false*/
result = dateRange.Includes(new DateRange(Date(10), Date(20))); /*true*/
result = dateRange.Includes(new DateRange(Date(11), Date(19))); /*true*/
result = dateRange.Includes(new DateRange(Date(21), Date(30))); /*false*/
result = dateRange.Overlaps(new DateRange(Date(1), Date(9))); /*false*/
result = dateRange.Overlaps(new DateRange(Date(5), Date(15))); /*true*/
result = dateRange.Overlaps(new DateRange(Date(10), Date(20))); /*true*/
result = dateRange.Overlaps(new DateRange(Date(11), Date(19))); /*true*/
result = dateRange.Overlaps(new DateRange(Date(15), Date(25))); /*true*/
result = dateRange.Overlaps(new DateRange(Date(21), Date(30))); /*false*/
DateRange.FormatString = "ddMMMyyyy"; /*display date format*/
var expectedRange = new DateRange(Date(4), Date(26)); /*target range 04May2019 - 26May2019*/
var inputSubRanges = new List()
{
new DateRange(Date(1), Date(3)), /*unknown: 01May2019 - 03May2019
will not appear in overlapping*/
new DateRange(Date(1), Date(3)), /*unknown: 01May2019 - 03May2019*/
new DateRange(Date(4), Date(6)), /*overlapping 05May2019 - 06May2019*/
new DateRange(Date(5), Date(7)), /*overlapping 05May2019 - 06May2019*/
//new DateRange(Date(8), Date(11)), /*missing 08May2019 - 11May2019*/
new DateRange(Date(12), Date(15)), /*overlapping 12May2019 - 15May2019*/
new DateRange(Date(12), Date(15)), /*overlapping 12May2019 - 15May2019*/
new DateRange(Date(16), Date(19)),
//new DateRange(Date(20), Date(23)), /*missing 20May2019 - 23May2019*/
new DateRange(Date(24), Date(26)),
new DateRange(Date(27), Date(29)), /*unknown: 27May2019 - 30May2019*/
new DateRange(Date(28), Date(30)), /*unknown: 27May2019 - 30May2019
will not appear in overlapping*/
};
List range = expectedRange.Items().ToList();
List ranges = expectedRange.ToContiguousSequences(range).ToList();
List rangeStrings = expectedRange.ToRangesString(range).ToList();
List overlappings = expectedRange.Overlappings(inputSubRanges).ToList();
List overlappingRangeStrings = expectedRange.ToRangesString(overlappings).ToList();
List missings = expectedRange.Missings(inputSubRanges).ToList();
List missingRangeStrings = expectedRange.ToRangesString(missings).ToList();
List unkowns = expectedRange.Unknowns(inputSubRanges).ToList();
List unkownRangeStrings = expectedRange.ToRangesString(unkowns).ToList();
原文地址:https://www.codeproject.com/Tips/5093140/Csharp-Generic-Range-Helper