如何计算具有相似时间跨度的一天中的时间跨度总和

时间:2019-08-07 11:17:38

标签: c#

我在日期中有多个“从时间”和“到时间”。

例如:

from time    | - to Time -     |---- Date ---- | -- diff date -- 
 -- 10:00 -- |   -- 12:00 --   |  2019-08-07   |      2 Hours
 -- 10:00 -- |   -- 12:00 --   |  2019-08-07   |      2 Hours
 -- 11:00 -- |   -- 12:00 --   |  2019-08-07   |      1 Hours
 -- 11:00 -- |   -- 14:00 --   |  2019-08-07   |      3 Hours
 -- 14:00 -- |   -- 18:00 --   |  2019-08-07   |      4 Hours
 -- 15:00 -- |   -- 17:00 --   |  2019-08-07   |      2 Hours
 -- 18:00 -- |   -- 19:00 --   |  2019-08-07   |      1 Hours

上述时间总和是:15小时

但是它是错误的。因为有些时候是重复的。正确答案是9小时。

如何计算此问题的正确答案?

3 个答案:

答案 0 :(得分:3)

这比您想象的要难,至少在一般情况下如此。

这是我用来计算数字范围并说明重叠区域的一类的修改版本(完整的类还可以处理排除的区域,这是我已包括但未用于此答案的区域):

public sealed class RangeCombiner
{
    public void Include(long start, long end)
    {
        _boundaries.Add(new Boundary(start, isStart: true, isIncluded: true));
        _boundaries.Add(new Boundary(end, isStart: false, isIncluded: true));

        _sorted = false;
    }

    public void Exclude(long start, long end)
    {
        _boundaries.Add(new Boundary(start, isStart: true, isIncluded: false));
        _boundaries.Add(new Boundary(end, isStart: false, isIncluded: false));

        _sorted = false;
    }

    public void Clear()
    {
        _boundaries.Clear();
    }

    public long TotalIncludedRange()
    {
        sortIfNecessary();

        return totalIncludedRange();
    }

    void sortIfNecessary()
    {
        if (_sorted)
            return;

        _boundaries.Sort();
        _sorted = true;
    }

    long totalIncludedRange()
    {
        int  included = 0;
        int  excluded = 0;
        long start    = 0;
        long total    = 0;

        for (int i = 0; i < _boundaries.Count; ++i)
        {
            if (_boundaries[i].IsStart)     // Starting a region...
            {
                if (_boundaries[i].IsIncluded)      // Starting an included region...
                {
                    if (++included == 1 && excluded == 0)       // Starting a new included region,
                        start = _boundaries[i].Value;            // so remember its start time.
                }
                else                                // Starting an excluded region...
                {
                    if (++excluded == 1 && included > 0)        // Ending an included region,
                        total += _boundaries[i].Value - start;   // so add its range to the total.
                }
            }
            else                            // Ending a region...
            {
                if (_boundaries[i].IsIncluded)      // Ending an included region...
                {
                    if (--included == 0 && excluded == 0)       // Ending an included region,
                        total += _boundaries[i].Value - start;   // so add its range to the total.
                }
                else                                // Ending an excluded region...
                {
                    if (--excluded == 0 && included > 0)        // Starting an included region,
                        start = _boundaries[i].Value;            // so remember its start time.
                }
            }
        }

        return total;
    }

    readonly List<Boundary> _boundaries = new List<Boundary>();

    bool _sorted;

    struct Boundary : IComparable<Boundary>
    {
        public Boundary(long value, bool isStart, bool isIncluded)
        {
            Value      = value;
            IsStart    = isStart;
            IsIncluded = isIncluded;
        }

        public int CompareTo(Boundary other)
        {
            if (this.Value < other.Value)
                return -1;

            if (this.Value > other.Value)
                return 1;

            if (this.IsStart == other.IsStart)
                return 0;

            if (this.IsStart)
                return -1;

            return 1;
        }

        public readonly long Value;
        public readonly bool IsStart;
        public readonly bool IsIncluded;
    }
}

这是解决问题的方法。请注意如何将DateTime值转换为区域的滴答计数:

以下代码的输出为Total = 09:00:00

class Program
{
    static void Main()
    {
        var combiner = new RangeCombiner();

        var from1 = new DateTime(2019, 08, 07, 10, 00, 00);
        var to1   = new DateTime(2019, 08, 07, 12, 00, 00);
        var from2 = new DateTime(2019, 08, 07, 10, 00, 00);
        var to2   = new DateTime(2019, 08, 07, 12, 00, 00);
        var from3 = new DateTime(2019, 08, 07, 11, 00, 00);
        var to3   = new DateTime(2019, 08, 07, 12, 00, 00);
        var from4 = new DateTime(2019, 08, 07, 11, 00, 00);
        var to4   = new DateTime(2019, 08, 07, 14, 00, 00);
        var from5 = new DateTime(2019, 08, 07, 14, 00, 00);
        var to5   = new DateTime(2019, 08, 07, 18, 00, 00);
        var from6 = new DateTime(2019, 08, 07, 15, 00, 00);
        var to6   = new DateTime(2019, 08, 07, 17, 00, 00);
        var from7 = new DateTime(2019, 08, 07, 18, 00, 00);
        var to7   = new DateTime(2019, 08, 07, 19, 00, 00);

        combiner.Include(from1.Ticks, to1.Ticks);
        combiner.Include(from2.Ticks, to2.Ticks);
        combiner.Include(from3.Ticks, to3.Ticks);
        combiner.Include(from4.Ticks, to4.Ticks);
        combiner.Include(from5.Ticks, to5.Ticks);
        combiner.Include(from6.Ticks, to6.Ticks);
        combiner.Include(from7.Ticks, to7.Ticks);

        Console.WriteLine("Total = " + TimeSpan.FromTicks(combiner.TotalIncludedRange()));
    }
}

复杂度:

  • 添加数据是一项O(N)操作
  • 计算不排除的总不重叠量为O(N.Log(N)) 操作。
  • 因此,添加的计算总体上也为O(N.Log(N))。

答案 1 :(得分:0)

考虑到LINQ的算法(请注意,我不写C#):

  1. 具有一个包含以下字段的类:totime,fromtime,date,计算出的差异字段。
  2. 此类的实现等于和哈希方法
  3. 使用LINQ将集合从数据库转换为Set。 -HashSet<T> foo = new HashSet<T>(from x in bar.Items select x);
  4. 转换设置回列表。
  5. 使用差异求和来求和。

没有LINQ,IMO会更容易。

答案 2 :(得分:0)

以下是有效代码:
https://dotnetfiddle.net/myhStL

这将为您提供每个不同日期的总小时数列表。

List<DateTime> dates = new List<DateTime>(){
    new DateTime(2019,1,1,10,0,0),
    new DateTime(2019,1,1,12,0,0),
    new DateTime(2019,1,1,13,0,0),
    new DateTime(2019,1,1,14,0,0),
    new DateTime(2019,1,2,10,0,0),
    new DateTime(2019,1,2,12,0,0),
    new DateTime(2019,1,2,14,0,0),
    new DateTime(2019,1,3,10,0,0),
    new DateTime(2019,1,3,11,0,0),
    new DateTime(2019,1,3,12,0,0)
   };


  var result = dates
    .OrderBy(d => d.Date)
    .ThenBy(d => d.TimeOfDay)
    .GroupBy(d => d.Date)
    .Select(bla => new
    {
      Date = bla.First().Date,
      Hours = bla.Last() - bla.First()
    }).ToList();

结果:

  

日期:1/1/2019 12:00:00 AM营业时间:04:00:00
  日期:1/2/2019 12:00:00 AM营业时间:04:00:00
  日期:1/3/2019 12:00:00 AM时间:02:00:00