2017-06-15 203 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()實際投影每個範圍內的所有日期,因此我們可以將它們添加到列表中。然後第二部分只需要清單中不同的成員並對它們進行計數。