C#:泛型Range助手

目录

介绍

背景

我们会做什么?

范围(Range)模型

创建整数范围(Range)

使用整数范围(Range)

创建日期时间范围(Range)

使用DateTime范围


介绍

默认情况下,在C#中,我们有Enumerable.Range(Int32, Int32) ,其生成一个指定范围内的整数序列。我们期待着一种管理不同数据类型范围而不仅仅是整数的解决方案。

背景

我们会做什么?

创建类型明确的范围模式类,它将:

  • 定义范围
  • 检查项是否在范围内
  • 检查范围是否在范围内
  • 检查范围是否与范围重叠
  • 填充范围的项
  • 列出有序范围
  • 列出指定开始和结束项的有序范围字符串列表
  • 在输入子范围中查找重叠项
  • 在输入子范围中查找缺少的项目
  • 在输入子范围中查找未知项

范围(Range)模型

这是基本范围模型:

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

public abstract class RangeMode<TSource, TDistance> : IComparer<TSource>
{
    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<TSource> _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);

    /// <summary>
    /// Value 
    /// less Than 0, x is less than y.
    /// equal 0, x equals y.
    /// grater than 0, x is greater than y.
    /// </summary>
    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<TSource, TDistance> range)
    {
        bool includes = Includes(range.ActualStartFrom) && Includes(range.ActualEndTo);
        return includes;
    }

    public bool Overlaps(RangeMode<TSource, TDistance> range)
    {
        bool includes = includes = Includes(range.ActualStartFrom) || 
                                   Includes(range.ActualEndTo);
        return includes;
    }

    protected IEnumerable<TSource> 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<TSource> Items()
    {
        if (_items == null)
        {
            SetItems(PopulateRangeItems());
        }
        return _items;
    }

    public void SetItems(IEnumerable<TSource> values)
    {
        _items = values;
    }

    public void RepopulateItems()
    {
        _items = null;
        Items();
    }

    public IEnumerable<TSource> Overlappings
             (IEnumerable<RangeMode<TSource, TDistance>> sourceRanges)
    {
        IEnumerable<TSource> 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<TSource> Missings
               (IEnumerable<RangeMode<TSource, TDistance>> sourceRanges)
    {
        IEnumerable<TSource> missing = Items().Where(i => sourceRanges.All(t =>
            t.Greater(t.ActualStartFrom, i)
            || t.Less(t.ActualEndTo, i)
        ));
        return missing;
    }

    public IEnumerable<TSource> Unknowns
              (IEnumerable<RangeMode<TSource, TDistance>> sourceRanges)
    {
        HashSet<TSource> hash = new HashSet<TSource>();
        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<List<TSource>> ToContiguousSequences
        (IEnumerable<TSource> sequence, RangeMode<TSource, TDistance> rangeMode)
    {
        sequence = sequence.OrderBy(x => x);
        var e = sequence.GetEnumerator();
        if (!e.MoveNext())
        {
            throw new InvalidOperationException("Sequence is empty.");
        }
        var currentList = new List<TSource> { 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<TSource> { current };
            }
        }
        yield return currentList;
    }

    public IEnumerable<List<TSource>> ToContiguousSequences(IEnumerable<TSource> sequence)
    {
        return ToContiguousSequences(sequence, this);
    }

    public IEnumerable<string> ToRangesString(IEnumerable<TSource> 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<TSource> 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<int, uint>
{
    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) 扣除当前值的距离

使用整数范围(Range)

定义预期范围

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<IntegerRange>()
{
    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<int> range = expectedRange.Items().ToList();

有序范围列表

List<List<int>> ranges = expectedRange.ToContiguousSequences(range).ToList();

指定开始和结束项的有序范围字符串列表

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

在输入子范围中查找重叠项

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

在输入子范围中查找缺少的项目

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

在输入子范围中查找未知项

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

创建日期时间范围(Range)

让我们为DateTime类型创建一个范围模型:

using System;

public class DateRange : RangeMode<DateTime, uint>
{
    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<DateRange>()
{
    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<DateTime> range = expectedRange.Items().ToList();
List<List<DateTime>> ranges = expectedRange.ToContiguousSequences(range).ToList();
List<string> rangeStrings = expectedRange.ToRangesString(range).ToList();

List<DateTime> overlappings = expectedRange.Overlappings(inputSubRanges).ToList();
List<string> overlappingRangeStrings = expectedRange.ToRangesString(overlappings).ToList();

List<DateTime> missings = expectedRange.Missings(inputSubRanges).ToList();
List<string> missingRangeStrings = expectedRange.ToRangesString(missings).ToList();

List<DateTime> unkowns = expectedRange.Unknowns(inputSubRanges).ToList();
List<string> unkownRangeStrings = expectedRange.ToRangesString(unkowns).ToList();

 

原文地址:https://www.codeproject.com/Tips/5093140/Csharp-Generic-Range-Helper

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值