在Excel VBA中过滤2D数组

时间:2012-05-04 14:17:18

标签: arrays excel vba multidimensional-array filter

使用Excel和VBA,我想要一些关于如何最好地使用VBA过滤数组中的数据(与人们可能使用数据透视表的方式相同)的建议。我正在创建一个UserForm,它将根据当前存在的数据做出一些数据决策。我可以想象如何做得好,但我不熟悉VBA编程。

这是一个例子

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

要获取数组中的数据,我可以使用

Dim my_array As Variant

my_array = Range("A1").CurrentRegion

现在,我熟悉循环2D数组,但我想知道:过滤二维数组数据的最有效方法(没有时间循环遍历数组)?

例如,我如何得到这样的数据:

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

连连呢?我可以使用一堆自定义函数和循环来解决这个问题,但我认为必须有更好的方法。感谢。

2 个答案:

答案 0 :(得分:5)

我假设您只想使用VBA。

我认为这取决于几个参数,主要是:

  • 您运行相同条件的频率=&gt;你存储过滤器的结果还是每次重新计算?
  • 你需要多久过滤一次东西=&gt;如果经常的话,值得拥有适当的代码结构,如果没有,那么一个关闭循环显然是要走的路。

从OO的角度来看,假设性能(速度和内存)不是问题,我会采用以下设计(我不会详细介绍实现,只给出一般的想法)。创建一个你可以像这样使用的类(让我们称之为ArrayFilter)。

设置过滤器

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

或者

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

创建过滤后的数据集

filteredArray = getFilteredArray(originalArray, filter)

getFilteredArray编写非常简单:循环遍历数组,检查值是否与过滤器匹配,并将有效行放在新数组中:

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

优点

  • 清洁设计
  • 可重复使用,尤其是使用列号的第二个版本。这可以用来真正过滤任何数组。
  • 过滤代码在一个可以测试的功能中
  • 推论:避免重复代码

<强>缺点

  • 每次重新计算过滤,即使您使用相同的过滤器两次。例如,您可以将结果存储在字典中 - 请参阅下文。
  • 内存:每次调用getFilteredArray都会创建一个新数组,但不知道如何才能避免这种情况
  • 这增加了很多行代码,所以只有当它有助于使代码更易于阅读/维护时,我才会这样做。

ps:如果需要缓存结果以提高性能,一种方法是将结果存储在字典中并向getFilteredArray函数添加一些逻辑。请注意,除非您的数组非常大并且/或者您经常运行相同的过滤器,否则这可能是不值得的。

filters.add filter, filteredArray 'filters is a dictionary

这样,下次调用getFilteredArray时,你可以这样做:

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

答案 1 :(得分:2)

试试这个

' 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