2017-06-15 166 views
1

我有大量的FROM和TO对日期,我需要统计它们中的所有日期。但是如果两个范围重叠,那么重叠的天数不应该被计算两次。在VB.NET重叠日期范围内计算日期

这里有一个统计,我都几天代码:

Dim total_days_used = 0 
For Each row As DataRow In MY_DATA.Tables(0).Rows 
    Dim total_days As Double = 0 

    Dim date_from = MY_FROM_DATE_FROM_DATA 
    Dim date_to = MY_TO_DATE_FROM_DATA 

    Dim span = date_to - date_from 

    total_days = span.TotalDays '=4 
    total_days_used += total_days 
Next 

我想不出一个简单的方法,虽然减去重叠数天甚至跟踪它们。我想这应该是一种方法,另一种方法是将重叠范围合并,直到最终得到一组没有重叠的范围,但似乎太复杂了。应该有一个简单的方法来做到这一点?

+0

当你迭代时,你可以把重叠的日期放在列表或字典中,这样你就知道哪些已经被计数了 – Plutonix

+0

我会先得到一个没有重叠的范围列表。在其他列表中循环并合并重叠范围。然后你可以得到天数。 –

回答

1

像这样的东西应该工作。 我们首先命令范围来确定最后一个是否与当前重叠。 然后计算重叠天数并减去总数。 +1天,如果你想范围包容,否则删除。

Private Sub Main() 
    Dim ranges = New List(Of Range)() From { _ 
     {New Range(New DateTime(2000, 1, 1), New DateTime(2000, 1, 30))}, _ 
     {New Range(New DateTime(2000, 1, 28), New DateTime(2000, 2, 3))} _ 
    } 
    CountNonOverlappingsDays(ranges).Dump() '34 days 
End Sub 

Private Function CountNonOverlappingsDays(ranges As IEnumerable(Of Range)) As Integer  
    Dim isFirst = True 
    Dim last As Range = Nothing 
    Dim overlapping As Integer = 0 
    Dim total As Integer = 0 
    For Each current In ranges.OrderBy(Function(r) r.[To]) 
     total += CInt((current.[To] - current.From).TotalDays) + 1 '+1 if we want Inclusive count 

     If isFirst Then 
      isFirst = False 
      last = current 
      Continue For 
     End If 
     If (last.From <= current.[To]) AndAlso (last.[To] >= current.From) Then 
      Dim start = current.From 
      Dim [end] = last.[To] 
      overlapping += CInt(([end] - start).TotalDays) + 1 '+1 if we want Inclusive count 
     End If 
     last = current 
    Next 
    Return total - overlapping 
End Function 

    Public Class Range 
    Public Sub New([from] As DateTime, [to] As DateTime) 
     [From] = [from] 
     [To] = [to] 
    End Sub 
    Public Property [From]() As DateTime 
     Get 
      Return m_From 
     End Get 
     Set 
      m_From = Value 
     End Set 
    End Property 
    Private m_From As DateTime 
    Public Property [To]() As DateTime 
     Get 
      Return m_To 
     End Get 
     Set 
      m_To = Value 
     End Set 
    End Property 
    Private m_To As DateTime 
End Class 
+0

谢谢你,设法让它几乎没有调整的工作。非常感谢帮助! – mmvsbg

0

使用以下(或类似的DateRange类)。

Class DateRange 
    Implements IEnumerable(Of DateTime) 
    Public Sub New(startDate As DateTime, endDate As DateTime) 
     me.StartDate = startDate 
     me.EndDate = endDate 
    End Sub 

    Public ReadOnly Property StartDate() As DateTime 
    Public ReadOnly Property EndDate() As DateTime 

    Public Function GetEnumerator() As IEnumerator(Of DateTime) Implements IEnumerable(of DateTime).GetEnumerator 
     Return Enumerable.Range(0, 1 + EndDate.Subtract(StartDate).Days).[Select](Function(offset) StartDate.AddDays(offset)).GetEnumerator() 
    End Function 

    Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator 
     Return GetEnumerator() 
    End Function 
End Class 

这个类的重要部分是它是可枚举的。也就是说,它返回开始日期和结束日期之间的日期序列(包括在for each循环中使用)。

然后你可以用这样的代码来得到你想要的东西:

Dim ranges = New List(Of DateRange)() 
ranges.Add(New DateRange(#2017/1/1#,#2017/1/10#)) 
ranges.Add(New DateRange(#2017/1/8#,#2017/1/20#)) 

Dim merged = ranges.SelectMany(Function(r) r.AsEnumerable()).Distinct().OrderBy(Function(dt) dt) 
Console.WriteLine($"{merged.Count()} days: ") 
For Each [date] As DateTime In merged 
    Console.WriteLine([date].ToShortDateString()) 
Next 
Console.ReadLine() 

这使用LINQ SelectMany功能,所有在该DateRange实例压扁日期的列表(由IEnuemrableDateRange创建)列表到DateTime的单个列表。然后它获取不同的(唯一的)值,并对列表进行排序。

输出显示来自包含2个实例的列表的输出。第一个是2017年1月1日至2017年1月10日,第二个是2017年1月8日至2017年1月20日。这些范围在第8,9和10日重叠。如您所见,这些重叠日期只包含一次。

以下输出产生:

20天:
2017年1月1日
2017年1月2日
2017年1月3日
2017年1月4日
1 /二千○十七分之五
2017年1月6日
2017年1月7日
2017年1月8日
2017年1月9日
2017年1月10日
2017年1月11日
2017年1月12日
2017年1月13日
2017年1月14日
2017年1月15日
2017年1月16日
2017年1月17日
2017年1月18日
2017年1月19日
二零一七年一月二十零日

0

试试这个:

Dim range As New List(Of DateTime)() 

For Each row As DataRow In MY_DATA.Tables(0).Rows 
    Dim date_from = MY_FROM_DATE_FROM_DATA 
    Dim date_to = MY_TO_DATE_FROM_DATA 
    range.AddRange(Enumerable.Range(0, (date_from-date_to).TotalDays).Select(d => date_from.AddDays(d)) 
Next 
Dim total_days_used As Integer = range.Distinct().Count() 

魔法分两部分。第一部分使用Enumerable.Range()实际投影每个范围内的所有日期,因此我们可以将它们添加到列表中。然后第二部分只需要清单中不同的成员并对它们进行计数。