2012-05-04 112 views
5

Sử dụng Excel và VBA, tôi muốn có một số lời khuyên về cách lọc dữ liệu tốt nhất trong một mảng (theo cùng cách mà người ta có thể sử dụng bảng tổng hợp) sử dụng VBA. Tôi đang tạo một UserForm sẽ đưa ra một số quyết định dữ liệu dựa trên dữ liệu hiện có. Tôi có thể hình dung làm thế nào để làm điều đó đủ tốt nhưng không phải là thông thạo trong lập trình VBA.Lọc các mảng 2D trong Excel VBA

Dưới đây là một ví dụ

A  B  C 
bob  12  Small 
sam  16  Large 
sally 1346 Large 
sam  13  Small 
sally 65  Medium 
bob  1  Medium 

Để lấy các dữ liệu trong một mảng, tôi có thể sử dụng

Dim my_array As Variant 

my_array = Range("A1").CurrentRegion 

Bây giờ, tôi làm quen với Looping qua mảng 2D, nhưng tôi tự hỏi: gì cách hiệu quả nhất để lọc dữ liệu mảng 2D (không lặp qua thời gian mảng và lần nữa)?

Ví dụ, làm thế nào để tôi nhận được sẽ nói được loại dữ liệu này:

data_for_sally As Variant 'rows with sally as name in ColA 
data_for_sally_less_than_ten As Variant ' all rows with sally's name in ColA and colB < 10 
data_for_all_mediums as Variant ' all rows where ColC is Medium 

Gợi ý? Tôi có thể làm việc này với một loạt các chức năng tùy chỉnh và vòng lặp nhưng tôi nghĩ rằng phải có một cách tốt hơn. Cảm ơn.

+0

Lưu ý rằng ví dụ thứ 4 không phải là một bộ lọc nhưng một hoạt động trên mảng, có thể dẫn đến một câu trả lời khác. – assylias

+0

Không chắc chắn nó có thể không có chức năng lặp/tùy chỉnh trong VBA. Bạn nói rằng bạn có kinh nghiệm trong các ngôn ngữ khác, có bạn coi là một VSTO/.NET impementation sau đó sử dụng LINQ? –

+0

Đối với loại điều này trong VBA tôi sẽ sử dụng một recordset ADO bị ngắt kết nối. Nó cho phép bạn phân loại và lọc. –

Trả lời

5

Tôi giả sử bạn chỉ muốn sử dụng VBA.

Tôi nghĩ rằng nó phụ thuộc vào nhiều thông số, chủ yếu vào:

  • mức độ thường xuyên bạn chạy cùng một điều kiện => bạn lưu trữ các kết quả của một bộ lọc hay bạn tính toán lại mỗi lần?
  • mức độ thường xuyên bạn cần lọc nội dung => nếu thông thường, có giá trị phù hợp với cấu trúc mã phù hợp, nếu không thì một vòng lặp tắt rõ ràng là cách để đi.

Từ quan điểm OO, giả định hiệu suất (tốc độ & bộ nhớ) không phải là vấn đề, tôi sẽ đi cho thiết kế sau (tôi sẽ không đi vào chi tiết triển khai, chỉ đưa ra ý tưởng chung). Tạo một lớp (chúng ta hãy gọi nó là tưởng tượng ArrayFilter) mà bạn có thể sử dụng như thế này.

Thiết lập các bộ lọc

Dim filter As New ArrayFilter 
With filter 
    .name = "sam" 
    .category = "Medium" 
    .maxValue = 10 
End With 

Hoặc

filter.add(1, "sam") 'column 1 
filter.add(3, "Medium") 'column 3 
filter.addMax(2, 10) 'column 2 

Tạo dữ liệu đã lọc thiết

filteredArray = getFilteredArray(originalArray, filter) 

Các getFilteredArray là khá đơn giản để lệnh e: bạn lặp qua mảng kiểm tra nếu các giá trị phù hợp với các bộ lọc và đưa các dòng có giá trị trong một mảng mới:

If filter.isValidLine(originalArray, lineNumber) Then 'append to new array 

Ưu

  • thiết kế sạch
  • Reusable, đặc biệt là với phiên bản thứ hai nơi bạn sử dụng số cột. Điều này có thể được sử dụng để lọc bất kỳ mảng thực sự.
  • đang
  • Filtering là trong một chức năng mà bạn có thể kiểm tra
  • Hệ luỵ: tránh trùng lặp mã

Nhược điểm

  • Filtering được tính toán lại mỗi khi, ngay cả khi bạn sử dụng bộ lọc tương tự hai lần. Bạn có thể lưu trữ các kết quả trong một từ điển ví dụ - xem dưới đây.
  • Bộ nhớ: mọi lệnh gọi tới getFilteredArray đều tạo ra một mảng mới, nhưng không chắc chắn cách này có thể tránh được anyway
  • Điều này bổ sung thêm một vài dòng mã, vì vậy tôi sẽ làm điều đó chỉ khi nó giúp làm cho mã dễ dàng hơn đọc/duy trì.

ps: Nếu bạn cần lưu vào bộ nhớ cache kết quả để cải thiện hiệu suất, một cách để lưu kết quả vào từ điển và thêm một số logic vào hàm getFilteredArray. Lưu ý rằng trừ khi mảng của bạn thực sự lớn và/hoặc bạn chạy cùng một bộ lọc rất nhiều, điều này có lẽ không đáng giá.

filters.add filter, filteredArray 'filters is a dictionary 

Bằng cách đó, khi bạn gọi getFilteredArray thời gian tiếp theo, bạn có thể làm một cái gì đó như thế này:

For each f in filters 
    'Check if all conditions in f and newFilter are the same 
    'If they are: 
    getFilteredArray = filters(f) 
    Exit Function 
Next 

'Not found in cache: compute the result 
+0

Đó là một cách tiếp cận thú vị - tôi đã hy vọng rằng VBA có thứ gì đó được xây dựng trong tôi có thể sử dụng khi đang bay. Tuy nhiên, việc sử dụng lại cách tiếp cận OO có thể phải là điều tốt nhất tiếp theo. Cảm ơn bạn đã trả lời chi tiết. Sẽ phải thử nó. – thornomad

+0

Đọc lại nó, getFilteredArray có lẽ nên là một hàm bên trong lớp ArrayFilter. – assylias

0

Hãy thử điều này

' credited to ndu 
Function Filter2DArray(ByVal sArray, ByVal ColIndex As Long, ByVal FindStr As String, ByVal HasTitle As Boolean) 
    Dim tmpArr, i As Long, j As Long, Arr, Dic, TmpStr, Tmp, Chk As Boolean, TmpVal As Double 
    On Error Resume Next 
    Set Dic = CreateObject("Scripting.Dictionary") 
    tmpArr = sArray 
    ColIndex = ColIndex + LBound(tmpArr, 2) - 1 
    Chk = (InStr("><=", Left(FindStr, 1)) > 0) 
    For i = LBound(tmpArr, 1) - HasTitle To UBound(tmpArr, 1) 
    If Chk Then 
     TmpVal = CDbl(tmpArr(i, ColIndex)) 
     If Evaluate(TmpVal & FindStr) Then Dic.Add i, "" 
    Else 
     If UCase(tmpArr(i, ColIndex)) Like UCase(FindStr) Then Dic.Add i, "" 
    End If 
    Next 
    If Dic.Count > 0 Then 
    Tmp = Dic.Keys 
    ReDim Arr(LBound(tmpArr, 1) To UBound(Tmp) + LBound(tmpArr, 1) - HasTitle, LBound(tmpArr, 2) To UBound(tmpArr, 2)) 
    For i = LBound(tmpArr, 1) - HasTitle To UBound(Tmp) + LBound(tmpArr, 1) - HasTitle 
     For j = LBound(tmpArr, 2) To UBound(tmpArr, 2) 
     Arr(i, j) = tmpArr(Tmp(i - LBound(tmpArr, 1) + HasTitle), j) 
     Next 
    Next 
    If HasTitle Then 
     For j = LBound(tmpArr, 2) To UBound(tmpArr, 2) 
     Arr(LBound(tmpArr, 1), j) = tmpArr(LBound(tmpArr, 1), j) 
     Next 
    End If 
    End If 
    Filter2DArray = Arr 
End Function