Найти наибольший диапазон видимых ячеек в отфильтрованном диапазоне

#vba #excel

#vba #excel

Вопрос:

Я хотел бы иметь возможность находить наибольшую видимую область непрерывных строк в отфильтрованной таблице. Я знаю, что одним из возможных способов было бы перебирать видимые ячейки, используя свойство «xlCellTypeVisible», и подсчитывать ячейки в каждой видимой области. Однако данные состоят из десятков, а иногда и сотен тысяч строк, поэтому мне было интересно, есть ли более быстрый и эффективный способ сделать это.

Ответ №1:

Несколько месяцев назад у меня было смутно похожее требование, и я был недоволен лучшим решением, которое я обнаружил. Глядя на ваш вопрос, я внезапно подумал о двух новых методах. Приведенный ниже макрос демонстрирует и то, и другое. Оба дают приемлемые результаты, хотя я не могу представить ситуацию, в которой метод 2 не является более быстрым.

Мой макрос запускается:

 Option Explicit
Sub LargestVisibleRange()

  Dim Count As Long
  Dim NumRowsInLargestRange As Long
  Dim RngCrnt As Range
  Dim RngTgt As Range
  Dim RowCrnt As Long
  Dim RowCrntRangeStart As Long
  Dim RowLargestRangeEnd As Long
  Dim RowLargestRangeStart As Long
  Dim RowMax As Long
  Dim RowPrev As Long
  Dim StartTime As Single

  With Worksheets("TrainData")

    RowMax = .Cells(Rows.Count, "A").End(xlUp).Row
    Debug.Print "1  RowMax " amp; RowMax

    .Cells.AutoFilter
    .Range(.Cells(2, 1), .Cells(RowMax, "Z")).AutoFilter Field:=2, Criteria1:=ChrW$(amp;H2116) amp; " 9/10"
  

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

Макрос продолжается:

 Set RngTgt = .Range(.Rows(2), .Rows(RowMax)).SpecialCells(xlCellTypeVisible)
Debug.Print "2  RngTgt " amp; RngTgt.Address
Count = 1
Debug.Print "3  ";
For Each RngCrnt In RngTgt
  Debug.Print RngCrnt.Address amp; " ";
  Count = Count   1
  If Count = 30 Then Exit For
Next
Debug.Print

Set RngTgt = RngTgt.EntireRow
Debug.Print "4  RngTgt " amp; RngTgt.Address

Count = 1
Debug.Print "5  ";
For Each RngCrnt In RngTgt
  Debug.Print RngCrnt.Address amp; " ";
  Count = Count   1
  If Count = 30 Then Exit For
Next
Debug.Print
  

Вывод из приведенного выше утверждения:

 1  RowMax 5691
2  RngTgt $2:$4,$20:$22,$38:$40,$56:$58,$74:$76,$92:$94,$110:$112,$128:$130,$146:$148,$164:$166,$182:$184,$200:$202,$218:$220,$236:$238,$254:$256,$272:$274,$290:$292,$308:$310,$326:$328,$344:$346,$362:$364,$380:$382,$398:$400,$416:$418,$434:$436,$452:$454,$470:$472
3  $A$2 $B$2 $C$2 $D$2 $E$2 $F$2 $G$2 $H$2 $I$2 $J$2 $K$2 $L$2 $M$2 $N$2 $O$2 $P$2 $Q$2 $R$2 $S$2 $T$2 $U$2 $V$2 $W$2 $X$2 $Y$2 $Z$2 $AA$2 $AB$2 $AC$2 
4  RngTgt $2:$4,$20:$22,$38:$40,$56:$58,$74:$76,$92:$94,$110:$112,$128:$130,$146:$148,$164:$166,$182:$184,$200:$202,$218:$220,$236:$238,$254:$256,$272:$274,$290:$292,$308:$310,$326:$328,$344:$346,$362:$364,$380:$382,$398:$400,$416:$418,$434:$436,$452:$454,$470:$472
5  $2:$2 $3:$3 $4:$4 $20:$20 $21:$21 $22:$22 $38:$38 $39:$39 $40:$40 $56:$56 $57:$57 $58:$58 $74:$74 $75:$75 $76:$76 $92:$92 $93:$93 $94:$94 $110:$110 $111:$111 $112:$112 $128:$128 $129:$129 $130:$130 $146:$146 $147:$147 $148:$148 $164:$164 $165:$165
  

Строка 1 показывает, что у меня 5690 строк данных. Это намного меньше, чем у вас есть, но этого достаточно, чтобы дать адекватное представление о производительности.

Строка 2 является результатом:

 Set RngTgt = .Range(.Rows(2), .Rows(RowMax)).SpecialCells(xlCellTypeVisible)
Debug.Print "2  RngTgt " amp; RngTgt.Address
  

Обратите внимание, что адреса диапазона являются $2:$4, $20:$22 и так далее. Обратите также внимание, что строка усечена. Свойство Address предоставляет как можно больше целых диапазонов, чтобы общая длина строки составляла менее 255 символов.

Строка 3 является результатом:

 Debug.Print "3  ";
For Each RngCrnt In RngTgt
  Debug.Print RngCrnt.Address amp; " ";
  Count = Count   1
  If Count = 30 Then Exit For
Next
Debug.Print
  

Обратите внимание, что, хотя адреса диапазона были для целых строк, For Each возвращает отдельные ячейки. Обратите также внимание, что, хотя у меня 26 столбцов данных, возвращаемые ячейки включают AA2, AB2 и так далее.

Строка 4 является результатом:

 Set RngTgt = RngTgt.EntireRow
Debug.Print "4  RngTgt " amp; RngTgt.Address
  

Казалось бы, новое Set RngTgt не оказало никакого эффекта.

Однако строка 5, которая была создана тем же способом, что и строка 3, содержит строку вместо ячеек. Если вы используете Excel 2003, обработка измененных RngTgt данных будет в 256 раз быстрее, чем обработка неизмененных RngTgt . Если вы используете более позднюю версию Excel, это будет на 16 384 быстрее.

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

 Duration 1: 0.073
Largest range 579 to 582
Duration 2: 0.003
Largest range 579 to 582
  

Я полагаю, что длительность 1 демонстрирует, что метод 1 даст приемлемые результаты, но метод 2, очевидно, значительно быстрее.

Оставшаяся часть макроса:

     StartTime = Timer

    RowCrntRangeStart = 0           ' No current visible range
    RowLargestRangeStart = 0        ' No range found so far

    RowCrnt = 2
    Do While True
      ' Search for visible row
      Do While True
        If Not .Rows(RowCrnt).Hidden Then
          RowCrntRangeStart = RowCrnt
          Exit Do
        End If
        RowCrnt = RowCrnt   1
        If RowCrnt > RowMax Then
          Exit Do
        End If
      Loop

      If RowCrntRangeStart = 0 Then
        ' No unprocessed visible row found
        Exit Do
      End If

      ' Search for invisble row
      Do While True
        If .Rows(RowCrnt).Hidden Then
          ' Visible range is RowCrntRangeStart to RowCrnt-1
          If RowLargestRangeStart = 0 Then
            ' This is the first visible range
            RowLargestRangeStart = RowCrntRangeStart
            RowLargestRangeEnd = RowCrnt - 1
            NumRowsInLargestRange = RowLargestRangeEnd - RowLargestRangeStart   1
          Else
            ' Check for new range being larger thsn previous
            If RowCrnt - RowCrntRangeStart > NumRowsInLargestRange Then
              ' This visible range is larger than previous largest
              RowLargestRangeStart = RowCrntRangeStart
              RowLargestRangeEnd = RowCrnt - 1
              NumRowsInLargestRange = RowLargestRangeEnd - RowLargestRangeStart   1
            End If
          End If
          RowCrntRangeStart = 0     ' Not within visible range
          RowCrnt = RowCrnt   1     ' Step over first row of invisible range
          Exit Do
        End If
        RowCrnt = RowCrnt   1
        If RowCrnt > RowMax Then
          Exit Do
        End If
      Loop

      If RowCrnt > RowMax Then
        Exit Do
      End If

    Loop

    Debug.Print "Duration 1: " amp; Format(Timer - StartTime, "##0.####")
    Debug.Print "Largest range " amp; RowLargestRangeStart amp; " to " amp; RowLargestRangeEnd

  End With

  StartTime = Timer

  RowCrntRangeStart = 0           ' No current visible range
  RowLargestRangeStart = 0        ' No range found so far

  For Each RngCrnt In RngTgt
    If RowCrntRangeStart = 0 Then
      ' Start of visible range
      RowPrev = RngCrnt.Row
      RowCrntRangeStart = RowPrev
    Else
      ' Already within visible range
      If RowPrev   1 = RngCrnt.Row Then
        ' Within same visible range
        RowPrev = RngCrnt.Row
      Else
        ' Have start of new visible range
        ' Last visible range was RowCrntRangeStart to Rowprev
        If RowLargestRangeStart = 0 Then
          ' This is the first visible range
          RowLargestRangeStart = RowCrntRangeStart
          RowLargestRangeEnd = RowPrev
          NumRowsInLargestRange = RowLargestRangeEnd - RowLargestRangeStart   1
        Else
          ' Check for new range being larger thsn previous
          If RowPrev - RowCrntRangeStart   1 > NumRowsInLargestRange Then
            ' This visible range is larger than previous largest
            RowLargestRangeStart = RowCrntRangeStart
            RowLargestRangeEnd = RowPrev
            NumRowsInLargestRange = RowLargestRangeEnd - RowLargestRangeStart   1
          End If
        End If
        RowCrntRangeStart = RngCrnt.Row       ' Start of new visible range
        RowPrev = RngCrnt.Row
      End If
    End If
  Next

  Debug.Print "Duration 2: " amp; Format(Timer - StartTime, "##0.####")
  Debug.Print "Largest range " amp; RowLargestRangeStart amp; " to " amp; RowLargestRangeEnd

End Sub
  

Я надеюсь, что метод 2 полезен для вас. Это, безусловно, будет полезно для меня.

Комментарии:

1. Спасибо, что поделились решением. Я нашел другой способ сделать это, используя разделение для результирующего массива