2012-05-04 47 views
5

Utilizando Excel y VBA, quería algunos consejos sobre cómo filtrar mejor los datos en una matriz (del mismo modo que se puede usar una tabla dinámica) usando estrictamente VBA. Estoy creando un UserForm que tomará algunas decisiones de datos en base a los datos existentes actualmente. Puedo visualizar cómo hacerlo lo suficientemente bien, pero no estoy tan versado en la programación de VBA.Filtrado de matrices 2D en Excel VBA

Aquí es un ejemplo

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

Para conseguir los datos en una matriz, podría usar

Dim my_array As Variant 

my_array = Range("A1").CurrentRegion 

Ahora, estoy familiarizado con bucle a través de matrices 2D, pero me preguntaba: lo la forma más efectiva de filtrar los datos de la matriz 2D (sin bucles a través de la matriz una y otra vez)?

Por ejemplo, ¿cómo me sería decir obtener este tipo de datos:

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 

Sugerencias? Podría resolver esto con un montón de funciones y bucles personalizados, pero pensé que debía haber una forma mejor. Gracias.

+0

Tenga en cuenta que el cuarto ejemplo no es un filtro, sino una operación en la matriz, lo que probablemente conduzca a una respuesta diferente. – assylias

+0

No estoy seguro de que sea posible sin las funciones de bucle/personalizado en VBA. Usted dice que tiene experiencia en otros idiomas, ¿ha considerado una impedancia de VSTO/.NET y luego utiliza LINQ? –

+0

Para este tipo de cosas en VBA usaría un conjunto de registros ADO desconectado. Te da clasificación y filtrado. –

Respuesta

5

Supongo que desea utilizar solo VBA.

Creo que depende de varios parámetros, principalmente en:

  • con qué frecuencia se ejecuta la misma condición => ¿Almacena el resultado de un filtro o qué vuelve a calcular cada vez?
  • con qué frecuencia necesita filtrar material => si con frecuencia, vale la pena tener una estructura de código adecuada en su lugar, si no, entonces un bucle único es claramente el camino a seguir.

Desde una perspectiva OO, suponiendo que el rendimiento (velocidad & memoria) no es un problema, me gustaría ir para el siguiente diseño (no voy a entrar en los detalles de la aplicación, sólo dan la idea general). Cree una clase (llamémoslo imaginativamente ArrayFilter) que pueda usar así.

de configuración del filtro

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

O

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

Crear los datos filtrados establecen

filteredArray = getFilteredArray(originalArray, filter) 

El getFilteredArray es bastante sencillo de auto e: se recorre la matriz de comprobar si los valores coinciden con el filtro y poner las líneas válidos en una nueva matriz:

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

Pros

  • Diseño limpio
  • reutilizable, especialmente con la segunda versión donde usa el número de columna. Esto se puede usar para filtrar cualquier arrays realmente.
  • código
  • filtrado está en una función que se puede probar
  • Corolario: evitar la duplicación de código

Contras

  • de filtrado se vuelve a calcular cada vez, incluso si se utiliza el mismo filtro dos veces. Puede almacenar los resultados en un diccionario, por ejemplo, ver a continuación.
  • Memoria: cada llamada a getFilteredArray crea una nueva matriz, pero no estoy seguro de cómo se puede evitar de todos modos
  • Esto agrega bastantes líneas de código, así que lo haría solo si ayuda a hacer que el código sea más fácil leer/mantener

ps: Si necesita almacenar en caché los resultados para mejorar el rendimiento, una manera sería almacenar los resultados en un diccionario y agregar algo de lógica a la función getFilteredArray. Tenga en cuenta que a menos que sus matrices sean realmente grandes y/o ejecute el mismo filtro mucho, probablemente esto no valga la pena.

filters.add filter, filteredArray 'filters is a dictionary 

De esta manera, cuando se llama getFilteredArray próxima vez, se puede hacer algo como esto:

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

Ese es un enfoque interesante: esperaba que VBA tuviera algo incorporado que pudiera usar sobre la marcha. Pero reutilizar un enfoque OO puede ser la mejor opción. Gracias por la respuesta detallada. Tendré que probarlo. – thornomad

+0

Relectándolo, getFilteredArray probablemente sea una función dentro de la clase ArrayFilter. – assylias

0

probar este

' 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