我有大量的日期FROM和TO对,我需要计算其中的所有日期。但如果两个范围重叠,那么重叠天数不应计算两次。
这里是我所有日子的代码:
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 :(得分: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
答案 1 :(得分: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
函数将列表中所有IEnuemrable
个实例的日期列表(由DateRange
DateRange
创建)展平为单个列表DateTime
。然后它获取不同的(唯一)值,并对列表进行排序。
输出显示包含2个DateRange
个实例的列表的输出。第一个是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日
2017年1月5日
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日
2017年1月20日
答案 2 :(得分: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()
来实际投影每个范围中的所有日期,因此我们可以将它们添加到列表中。然后第二部分只取该列表中不同的成员并计算它们。