2

我正在研究最好和最快的方法是在两个特定日期之间获得所有日子(周一、周二、周三)。

日期作为参数以逗号分隔的字符串传递给 Web 服务,例如mon,tue,wed,

目前这是伪代码。

所以我的(伪)代码如下

function Add(startdate, enddate, days)
{
    dateList as New List(Of Date)
    if days.split(',').count < 7 then
    {
        // This is the problem area.

    }

    while(startdate < enddate)
    {
        // do some processing of either the dates in dateList or just all days
    }
}

这个问题对我来说是我

1)

  • days参数拆分为数组,
  • 循环天数数组
  • 每个day天数:
    • 经历和之间的每个startdate日期enddate
    • 如果date.dayday,则在此方法中添加到 dateList 可能有 (365 * 6) 检查(假设开始日期和结束日期相隔一年),如果选择了所有日期,我可以忽略将它们放入列表

2)

  • 经历和之间的每个startdate日期enddate
  • 开关(日期.dayofweek.tostring())
  • case 'mon': if days.contains('mon') then addDatetoList

3)

  • 对于每个everyday之间startdateenddate
  • 如果days.contains(everyday.dayofweek.tostring()) 然后 addDatetoList

4)有没有我忽略的另一种方法,所有这些都是完全低效的?

计算需要在 vb.net 代码隐藏中进行,否则我会考虑使用 sql server 来完成。

编辑

作为功​​能计时的练习,我快速构建了以下内容来尝试我的方法和其他方法。

Dim DaysOfWeek As String = "Monday,Tuesday,Friday"
        Dim startDate As DateTime = DateTime.Now
        Dim endDate As DateTime = DateTime.Now.AddDays(365)
        Dim dateList As New List(Of DateTime)()

        Dim method1StopWatch As New Stopwatch
        Dim method2StopWatch As New Stopwatch
        Dim method3StopWatch As New Stopwatch
        Dim method4StopWatch As New Stopwatch

        Dim executionStart As Long
        Dim executionEnd As Long
        Dim noDays As Integer = 0

        'Method3

        method3StopWatch.Start()
        Do While (startDate < endDate)
            If DaysOfWeek.Contains(startDate.DayOfWeek.ToString()) Then
                dateList.Add(startDate)
                noDays = noDays + 1
            End If
            startDate = startDate.AddDays(1)

        Loop
        method3StopWatch.Stop()
        Label3.Text = method3StopWatch.ElapsedTicks.ToString() & " " & noDays & " Days"

        noDays = 0
        dateList.Clear()
        startDate = DateTime.Now

        'Method2

        method2StopWatch.Start()

        Do While (startDate < endDate)
            Select startDate.DayOfWeek.ToString()
                Case "Monday"
                    If DaysOfWeek.Contains("Monday") Then
                        dateList.Add(startDate)
                        noDays = noDays + 1
                    End If
                Case "Tuesday"
                    If DaysOfWeek.Contains("Monday") Then
                        dateList.Add(startDate)
                        noDays = noDays + 1
                    End If
                Case "Wednesday"
                    If DaysOfWeek.Contains("Wednesday") Then
                        dateList.Add(startDate)
                        noDays = noDays + 1
                    End If
                Case "Thursday"
                    If DaysOfWeek.Contains("Thursday") Then
                        dateList.Add(startDate)
                        noDays = noDays + 1
                    End If
                Case "Friday"
                    If DaysOfWeek.Contains("Friday") Then
                        dateList.Add(startDate)
                        noDays = noDays + 1
                    End If
                Case "Saturday"
                    If DaysOfWeek.Contains("Saturday") Then
                        dateList.Add(startDate)
                        noDays = noDays + 1
                    End If
                Case "Sunday"
                    If DaysOfWeek.Contains("Sunday") Then
                        dateList.Add(startDate)
                        noDays = noDays + 1
                    End If
            End Select
            startDate = startDate.AddDays(1)

        Loop
        method2StopWatch.Stop()
        Label2.Text = (method2StopWatch.ElapsedTicks).ToString() & " " & noDays & " Days"

        noDays = 0
        dateList.Clear()
        startDate = DateTime.Now

        method1StopWatch.Start()
        For Each Day As String In DaysOfWeek.Split(CChar(","))
            Do While (startDate < endDate)
                If startDate.DayOfWeek.ToString() = Day Then
                    noDays = noDays + 1
                    dateList.Add(startDate)
                End If
                startDate = startDate.AddDays(1)
            Loop
            startDate = DateTime.Now
        Next


        method1StopWatch.Stop()
        Label1.Text = (method1StopWatch.ElapsedTicks).ToString() & " " & noDays & " Days"


        noDays = 0
        dateList.Clear()
        startDate = DateTime.Now

        method4StopWatch.Start()
        Dim daysList As New List(Of DayOfWeek)()
        daysList.Add(DayOfWeek.Monday)
        daysList.Add(DayOfWeek.Tuesday)
        daysList.Add(DayOfWeek.Friday)
        Dim datesList As List(Of Date) = GetDayOfWeekDates(startDate, endDate, daysList)
        method4StopWatch.Stop()
        Label4.Text = (method4StopWatch.ElapsedTicks).ToString() & " " & datesList.Count & " Days"

    End Sub

    Public Function GetDayOfWeekDates(startDate As Date, endDate As Date, daysOfWeek As List(Of DayOfWeek)) As List(Of Date)
        Dim liReturn As New List(Of Date)()
        Dim currDay As Date = startDate
        While currDay <= endDate
            If daysOfWeek.Contains(currDay.Date.DayOfWeek) Then
                liReturn.Add(currDay.Date)
            End If
            currDay = currDay.AddDays(1)
        End While
        Return liReturn
    End Function

这导致周一、周二、周五的以下输出(经过的滴答声):

Method 1 : 10650 - 157 Days
Method 2 : 4152 - 157 Days
Method 3 : 4084 - 157 Days
Method 4 : 179 - 157 Days

这也意味着我的方法和逻辑很糟糕。

4

2 回答 2

3

This is the way I'd do it. I don't know if it is the most performant way possible, but in my opinion it is the easiest code to read and understand, and should execute quite quickly even if you are checking a large date range.

Public Function GetDayOfWeekDates(startDate As Date, endDate As Date, daysOfWeek As List(Of DayOfWeek)) As List(Of Date)
    Dim liReturn As New List(Of Date)()
    Dim currDay As Date = startDate
    While currDay <= endDate
        If daysOfWeek.Contains(currDay.Date.DayOfWeek) Then
            liReturn.Add(currDay.Date)
        End If
        currDay = currDay.AddDays(1)
    End While
    Return liReturn
End Function

You would call the function by building a list of DayOfWeek to check against:

    Dim daysList As New List(Of DayOfWeek)()
    daysList.Add(DayOfWeek.Monday)
    daysList.Add(DayOfWeek.Friday)
    Dim datesList As List(Of Date) = GetDayOfWeekDates(New Date(2012, 1, 1), New Date(2012, 12, 31), daysList)

In this example, your result would be in List(Of Date) datesList.

Edit: I just noticed that you mentioned a web service will receive a comma-delimited list with the days of the week. In my opinion you should definitely parse the list in a separate function at a level in your code where errors can be returned via the web service if the list is invalid. The function to find the list of dates should take a strongly typed parameter as I have shown above.

于 2013-01-08T14:59:09.900 回答
2

EDIT 2

I've tested the updated function and I was able to simplify which should also make it a little faster.

Public Shared Function DaysOfWeekRange(
    ByVal startDateTime As DateTime, _
    ByVal endDateTime As DateTime, _
    ByVal daysOfWeek As ISet(Of DayOfWeek)) As IEnumerable(Of DateTime)

    If (endDateTime < startDateTime) Then
        Return Enumerable.Empty(Of DateTime)()
    End If

    Dim startDate = startDateTime.Date
    Dim endDate = endDateTime.Date

    Dim firstDate As DateTime?
    Dim lastDate As DateTime
    Dim increment = 0
    Dim increments = New List(Of Integer)(7)

    ' Get Increments
    For d = 0 To 6
       If firstDate.HasValue Then
           increment += 1
           Dim day = startDate.AddDays(d)
           If daysOfWeek.Contains(day.DayOfWeek) Then
               lastDate = day
               increments.Add(increment)
               increment = 0
           End If           
       Else
           Dim possibleFirst = startDate.AddDays(d)
           If daysOfWeek.Contains(possibleFirst.DayOfWeek) Then
               firstDate = possibleFirst
               lastDate = firstDate
           End If
       End If
    Next

    If Not firstDate.HasValue Then
        Return Enumerable.Empty(Of DateTime)()
    End If

    ' Add loop back increment
    increments.Add((7 - lastDate.DayOfWeek) + firstDate.Value.DayOfWeek)

    ' Prepare iteration
    Dim wholeWeeks = Math.Floor(endDate.Subtract(firstDate).TotalDays() /7)
    Dim results = new List(Of DateTime)((wholeWeeks + 1) * daysOfWeek.Count)
    Dim thisDate = firstDate

    ' Whole Weeks
    For i = 1 To wholeWeeks Step 1
        For Each increment In increments
            results.Add(thisDate)
            thisDate = thisDate.AddDays(increment)
        Next
    Next

    ' Last Partial Week
    If (thisDate <= endDate) Then
        For Each increment In Increments
            results.Add(thisDate)
            thisDate = thisDate.AddDays(increment)
            If (thisDate > endDate) Then
                Exit For
            End If
        Next
    End if

    Return results
End Function

I can think of a more efficient way but, it involves implementing Enumerator(Of DateTime), its a shame we don't have a yield return equivalent in Visual Basic, then there would be a simple efficient option. If you want the Enumerator option then please ask.

Anyway this function is simple to write and shouldn't be too slow.

Public Shared Function DaysOfWeekRange(
    ByVal startDateTime As DateTime, _
    ByVal endDateTime As DateTime, _
    ByVal daysOfWeek As ISet(Of DayOfWeek)) As IEnumerable(Of DateTime)

    If (endDateTime < startDateTime) Then
        Return Enumerable.Empty(Of DateTime)()
    End If

    Dim startDate = startDateTime.Date
    Dim endDate = endDateTime.Date

    Return Enumerable.Range(0, endDate.Subtract(startDate).TotalDays) _
        .Select(Function(i) startDate.AddDays(i)) _
        .Where(Function(day) daysOfWeek.Contains(day.DayOfWeek))
End Function

EDIT

In this approach I calculate the cycle of increments up front and repeat for the number of whole weeks without checks. Then I increment for the last partial week with checks. If the range is sufficiently wide I think this should have a performance adavantage.

Public Shared Function DaysOfWeekRange(
    ByVal startDateTime As DateTime, _
    ByVal endDateTime As DateTime, _
    ByVal daysOfWeek As ISet(Of DayOfWeek)) As IEnumerable(Of DateTime)

    If (endDateTime < startDateTime) Then
        Return Enumerable.Empty(Of DateTime)()
    End If

    Dim startDate = startDateTime.Date
    Dim endDate = endDateTime.Date

    Dim firstDate = Enumerable.Range(0, 6) _
        .Select(Function(i) startDate.AddDays(i)) _
        .First(Function(d) daysOfWeek.Contains(d.DayOfWeek))

    Dim firstDayOfWeek = firstDate.DayOfWeek
    Dim offsets = New SortedSet(Of Integer)()
    For Each dayOfWeek in daysOfWeek
       Dim offset = dayOfWeek - firstDayOfWeek

       Select Case offset
           Case Is > 0
               offsets.Add(offset)
           Case Is < 0
               offsets.Add(7 + offset)
       End Select
    Next

    Dim increments = New List(Of Integer)()
    Dim lastOffset = 0
    For Each offset In offsets
        increments.Add(offset - lastOffset)
        lastOffset = offset
    Next
    increments.Add(7 - lastOffset)

    Dim results = new List(Of DateTime)()
    Dim wholeWeeks = Math.Floor(endDate.Subtract(firstDate).TotalDays() /7)
    Dim thisDate = firstDate

    'Whole Weeks
    For i = 1 To wholeWeeks Step 1
        For Each increment In increments
            results.Add(thisDate)
            thisDate = thisDate.AddDays(increment)
        Next
    Next

    'Last Partial Week
    If (thisDate <= endDate) Then
        For Each increment In Increments
            results.Add(thisDate)
            thisDate = thisDate.AddDays(increment)
            If (thisDate > endDate) Then
                Exit For
            End If
        Next
    End if

    Return results
End Function

It looks like a lot more code but I think that in most cases the preperation up front results in less work overall.

于 2013-01-08T15:37:22.897 回答