Excel ускоряет цикл for - PullRequest
       0

Excel ускоряет цикл for

0 голосов
/ 29 января 2020

У меня есть текущий код, и он работает так, как мне нужно. Проблема в том, что требуется время для запуска из-за значения l oop. Переменная x колеблется от 300 до около 8000. Я пытаюсь добавить в код, чтобы скрыть неважные даты, основанные на том, когда клиент платит. Мне нужно включить все даты, а не просто генерировать еженедельный / двухнедельный / ежемесячный календарь, так как другие события происходят в эти даты. Я буду добавлять код скрытия / отображения для других дат событий, но в данный момент я задаюсь вопросом, возможно ли ускорить этот код.

Sub Client_Payments()
    Application.ScreenUpdating = False
    Dim first As Integer
    Dim x As Integer
    Dim n As Integer
    Dim ws1 As Worksheet
    Set ws1 = Sheets("Payment_Summary")
    first = Application.Match(Range("Next_Payment").Value2, ws1.Range("A:A"), 0)
    x = ThisWorkbook.Sheets("Payment_Summary").Range("Duration").Value2 + 7
    ThisWorkbook.Sheets("Payment_Summary").Range(Cells(7, 1), Cells(x, 1)).EntireRow.Hidden = True

    If ThisWorkbook.Sheets("Client_Details").Range("Freq").Value = "Weekly" Then
        For n = 0 To x / 7
            ws1.Cells(first + (n * 7), 2).EntireRow.Hidden = False
        Next
    End If

    If ThisWorkbook.Sheets("Client_Details").Range("freq").Value = "Fortnightly" Then
        For n = 0 To x / 14
            ws1.Cells(first + (n * 14), 2).EntireRow.Hidden = False
        Next
    End If

    If ThisWorkbook.Sheets("Client_Details").Range("freq").Value = "Monthly" Then
        For n = 0 To x / 30
            If Day(ws1.Cells(n + first, 1).Value) = Day(ThisWorkbook.Sheets("Client_Details").Range("Next_payment").Value) Then
                ws1.Cells(n + first, 1).EntireRow.Hidden = False
            End If
        Next
    End If

End Sub

Ответы [ 2 ]

0 голосов
/ 29 января 2020

Есть способы ускорить ваш код - большинство из них были упомянуты в комментариях. Двумя основными из них являются считывание значений даты вашей ячейки в массив и заполнение одной Range строк, чтобы они не были скрыты.

Хотя я не совсем убежден в вашей логике кодирования c. Будут проблемы, если кто-то случайно удалит одну или несколько строк дат, и приращение дат выглядит немного шатким (например, что происходит в високосный год?). Интересно, лучше ли вам итерировать строку дат и, если вы найдете совпадение, сохранить строку, а затем увеличить дату на заданный интервал.

Не просматривая структуры листа (и имя вашего диапазона определения), трудно быть точным, но код может выглядеть примерно так, как показано ниже. Я не проверял скорость по сравнению с вашим текущим кодом, но я бы сказал, что вероятность того, что она будет достаточно высокой, будет выше:

Const FIRST_ROW As Long = 7 'first row of dates on Payment_Summary sheet.

Dim summaryWs As Worksheet
Dim summary, interval As Variant
Dim payDate As Long, duration As Long
Dim nextDate As Long, n As Long, i As Long, r As Long, p As Long
Dim frq As String
Dim rng As Range, unhideRng As Range
Dim showProgress As Boolean

'Display progress.
showProgress = True 'set to false if you don't want progress displayed.
Application.StatusBar = "Reading dates..."
DoEvents

'Acquire the payment parameters.
payDate = #3/3/2020# '-> just an example, read your own value.
duration = 8000 '-> just an example, read your own value.
frq = "Monthly" '-> just an example, read your own value.

'Read date values into an array.
Set summaryWs = ThisWorkbook.Worksheets("Payment_Summary")
With summaryWs
    Application.ScreenUpdating = False
    'Unhide the rows to read the values.
    .Rows.EntireRow.Hidden = False
    'Read values
    summary = .Range( _
                .Cells(FIRST_ROW, "A"), _
                .Cells(duration + FIRST_ROW - 1, "A")) _
            .Value2
    'Re-hide the rows.
    .Rows(FIRST_ROW).Resize(duration).EntireRow.Hidden = True
    Application.ScreenUpdating = True
End With

'Set the DateAdd parameters, based on frequency.
Select Case frq
    Case "Weekly": interval = Array("ww", 1)
    Case "Fortnightly": interval = Array("d", 14)
    Case "Monthly": interval = Array("m", 1)
End Select

p = 0 'progress indicator.
nextDate = payDate 'initialise target date.

'Iterate the dates.
For i = 1 To duration
    'Show progress.
    If showProgress Then
        If Int(i / duration * 100) > p Then
            p = Int(i / duration * 100)
            Application.StatusBar = p & "% complete"
            DoEvents
        End If
    End If

    'Check for a skipped date.
    'Logic:
    '   The current summary date should never be greater
    '   than the next date we're looking for.
    '   If it is, a day is missing from the summary sheet,
    '   so increment the next date to be more than the
    '   current summary date.
    Do While summary(i, 1) > nextDate
        nextDate = DateAdd(interval(0), interval(1), nextDate)
    Loop

    'Check for a matching date.
    If summary(i, 1) = nextDate Then
        'Set the row.
        r = i + FIRST_ROW - 1
        Set rng = summaryWs.Rows(r)
        'Add row to unhide range.
        If unhideRng Is Nothing Then
            Set unhideRng = rng
        Else
            Set unhideRng = Union(unhideRng, rng)
        End If
        'Increment the date.
        nextDate = DateAdd(interval(0), interval(1), nextDate)
    End If
Next

'Unhide the target rows
If Not unhideRng Is Nothing Then
    unhideRng.EntireRow.Hidden = False
End If

'Clear the progress bar.
If showProgress Then
    Application.StatusBar = False
End If
0 голосов
/ 29 января 2020

В попытке найти способы ускорить его, я переписал ваш код и прокомментировал его. Это то, что я придумал.

Private Sub TestOpen()

    Dim WsSum As Worksheet                      ' give a meaningful name
    Dim WsClient As Worksheet
    Dim Tmp As Variant
    Dim First As Long                           ' rows and columns are generally Long
    Dim x As Long
    Dim n As Long

    Set WsSum = ThisWorkbook.Sheets("Payment_Summary")
    Set WsClient = ThisWorkbook.Sheets("Client_Details")
    Application.ScreenUpdating = False

    ' For the next line no worksheet is specified.
    ' Therefore the ActiveSheet will be referenced.
    ' Better specify the sheet.
    Tmp = Range("Next_Payment").Value2          ' this must be a single cell

    ' Once you (correctly!) assign a sheet to a variable
    ' use the variable to refer to it in the code that follows.
    With WsSum
        On Error Resume Next
        If Err Then
            MsgBox "The match criterium """ & Tmp & """ wasn't found.", _
                   vbCritical, "Data error"
        Else
            First = Application.Match(Tmp, .Columns("A"), 0)
            x = .Range("Duration").Value2 + 7
            .Range(Rows(7), Rows(x)).EntireRow.Hidden = True

            Select Case WsClient.Range("Freq").Value
                Case "Weekly"
                    For n = 0 To Int(x / 7)
                        .Rows(First + (n * 7)).EntireRow.Hidden = False
                    Next
                Case "Fortnightly"
                    For n = 0 To Int(x / 14)
                        .Rows(First + (n * 14)).EntireRow.Hidden = False
                    Next
                Case "Monthly"
                    For n = 0 To Int(x / 30)
                        Tmp = WsClient.Range("Next_payment").Value
                        ' which value should this be if Tmp is not a date
                        ' from which the Day can be extracted? (I assign 1)
                        Tmp = IIf(IsDate(Tmp), Day(Tmp), 1)
                        If Day(.Cells(n + First, 1).Value) = Tmp Then
                            .Cells(n + First, 1).EntireRow.Hidden = False
                        End If
                    Next
            End If
        End If
    End With
    Application.ScreenUpdating = True
End Sub

Не задерживайте дыхание, моя версия не будет работать быстрее, чем ваша. На самом деле, это может не работать вообще. В этом случае, пожалуйста, потерпите меня и исправьте ошибки, которые вам покажет отладчик. Я не смог выполнить тест, и могут быть некоторые опечатки.

Цель этого упражнения состояла в том, чтобы понять код. Это то, что я понял. Вы смотрите на диапазон Freq и принимаете меры в зависимости от его значения, которое описывает период типа «еженедельно», «ежемесячно», «раз в две недели». Поскольку ячейка может содержать только одно значение за раз, оператор Select Case более подходит чем повторяется Ifs.

В каждом случае вы ищите значение «x», которое, как я понимаю, находится в области 8000. У меня есть некоторые сомнения относительно точности вашего определения строк, которые вы sh скрыть, потому что x + 7 выглядит так, как будто он предназначен для еженедельных списков. Когда вы делите на 30, результат может оказаться не тем, что вы хотите. Я бы использовал Ws.Cells(Ws.Rows.Count, "A").End(xlUp).Row, чтобы определить последнюю строку (= x), а затем l oop с For n = First To x Step 7 (или 14 или 30) для определения каждого 7-го, 14-го или 30-го ряда. Но это также не будет быстрее, чем у вас.

Спросил, как бы я сделал это быстрее Я должен сказать, что я бы вообще не использовал этот вид кода. Я бы искал способ применения фильтра. Если лучшего способа не найти, создайте вспомогательный столбец и напишите код для ввода * 1015. * в строках, которые вы хотите показать. Фильтр n для «x».

Чтобы быстро назначить «x» для 8000 строк, используйте этот вид конструкции.

Dim Arr As Variant
Dim Rng As Range
Dim i As Long
Redim Arr(1 to 8000)     ' use x
For i = 1 to Ubound(Arr) Step 7
    Arr(i) = "x"
Next i
Set Rng = Ws.Cells(First, Helper).Resize(Ubound(Arr),1)
Rng = Application.Transpose(Arr)

Вы можете использовать большую часть существующего кода для написания этой процедуры и я гарантирую, что это будет быстрее. Вы можете даже добавить код для применения фильтра.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...