2017-07-10 2 views
5

ich nach einer Möglichkeit, ein Array mit einem ausgewerteten Ausdruck zu filtern, wie:Wie filtert man ein Array mit einer Arbeitsblattfunktion?

Dim arr1(), arr2(), arr3() 

arr1 = Array(1, 2, 3, 4, 5)   ' > [1, 2, 3, 4, 5] 

arr2 = Map(arr1, "Values() * 2")  ' > [2, 4, 6, 8, 10] 

arr3 = Filter(arr2, "Values() > 6") ' > [8, 10] 

Ich habe bereits implementiert, um die Map Funktion mit einem UDF und mit Application.Evaluate("INDEX(expression,)"), aber ich bin zu kämpfen, es zu machen Arbeit für Filter:

Private arr_() 

Public Function Values() As Variant() 
    Values = arr_ 
End Function 

Public Function Map(arr(), expression As String) As Variant() 
    arr_ = arr 
    Map = Application.Evaluate("INDEX(" & expression & ",)") 
End Function 

Public Function Filter(arr(), expression As String) As Variant() 
    arr_ = arr 
    Filter = Application.Evaluate("INDEX(Values(), " & expression & ")") 
End Function 

gibt es eine Möglichkeit andere als Looping/jeden Wert Verschiebung? Maby mit VLOOKUP?

+2

Ich denke, Sie müssen Benutze eine Schleife. –

+0

Welche Excel verwenden Sie? Es gibt eine Möglichkeit, wenn Sie Office 365 Excel mit TEXTJOIN verwenden. Sonst ist es eine Iteration. INDEX wird ein Array mit der gleichen Größe wie die Eingabe zurückgeben, sie werden mit "FALSE" und den Zahlen, die auf TRUE auflösen, gefüllt. –

Antwort

1

Obwohl ich ein großer Fan von Arrays bin und den größten Teil der Arbeit an Excel-Einbauten delegiere, fand ich für diesen den am besten geeigneten Job in VBA unter Verwendung von Excel zu Evaluate den Ausdruck für Einzelstücke.

Public Function FilterArr(arr(), expression As String) 
    Dim match As Boolean, i As Long, val 
    ReDim ret(LBound(arr) To UBound(arr)) 
    i = LBound(arr) - 1 
    On Error Resume Next 
    For Each val In arr 
    match = False 
    match = Application.Evaluate(val & expression) 
    If match Then 
     i = i + 1 
     ret(i) = val 
    End If 
    Next 
    If i >= LBound(arr) Then 
    ReDim Preserve ret(LBound(arr) To i) 
    FilterArr = ret 
    End If 
End Function 

Sub test() 
    Dim arr1(), arr2(), arr3() 
    arr1 = Array(10, 20, 30, 40, 50) 
    arr3 = FilterArr(arr1, ">25") ' <--- usage like this 
    ' arr3 = (30, 40, 50) 
End Sub 

P. S. eine interessante Erweiterung wäre, mehrere Kriterien zuzulassen (d. h. AND ed zusammen) unter Verwendung eines ParamArray. Gute Kandidaten für die künftige Arbeit ...

+0

Wie im Beitrag erwähnt, suche ich nach einer Arbeitsblattfunktion und nicht nach einer Schleife. Die Idee ist, 'Application.Evaluate' nur einmal aufzurufen, um die Leistung in Ordnung zu halten und ein 2d/1d-Array als Eingabe zuzulassen. – michael

+0

@michael Es gibt einen grundlegenden Fehler in dem, was Sie sagen. ** Wie filtern Sie ein 2D-Array? ** Normalerweise geben Sie eine Spalte an und entfernen oder blenden ganze Zeilen aus. Ihr "Filter" überprüft alle Einträge. Was tun, wenn ein Eintrag nicht übereinstimmt, entfernen Sie die Zeile? die Kolumne? Um den Fehler zu sehen, löschen Sie eine einzelne Zelle in Excel und lesen Sie die Frage sorgfältig durch. –

+0

Es scheint, dass Sie Ihre eigene Frage beantwortet haben: "Normalerweise geben Sie eine Spalte an und entfernen oder verstecken ganze Zeilen." Ihnen fehlt einfach die Verwendung, wenn die Werte aus einem einzelnen Bereich/Spalte stammen: 'Filter ([A1: A100] .Wert," Werte()> 100 ")'. – michael

1

Zuerst ändern Sie die Funktion der folgenden ...

Public Function Filter(arr(), sValues As String, sCriteria As String) As Variant() 
    Dim Cnt As Long 
    arr_ = arr 
    Cnt = Application.Evaluate("SUMPRODUCT(--(" & sValues & sCriteria & "))") 
    If Cnt > 0 Then 
    Filter = Application.Evaluate("TRANSPOSE(INDEX(SMALL(IF(" & sValues & sCriteria & "," & _ 
     sValues & "),ROW(INDEX(A:A,1):INDEX(A:A," & Cnt & "))),0))") 
    Else 
    Filter = Array() 
    End If 
End Function 

Dann rufen Sie es wie folgt ...

+0

Danke, das funktioniert mit meinem Beispiel, aber 'SMALL' ist viel zu teuer (mehr als 20 Sekunden für 10k Zeilen) und es funktioniert nicht mit einer String-Auswertung (zB: =" a "). – michael

+0

Ja, ich hatte es nicht mit so vielen Zeilen getestet, aber ich habe erwartet, dass es ziemlich teuer ist. Und obwohl es so geändert werden kann, dass es sich mit String-Auswertungen befasst, kann ich davon ausgehen, dass es keinen Nutzen bringt, da es nicht praktisch ist. – Domenic

+0

Ja, es wäre nicht praktisch. Ich hatte gehofft, 'INDEX' mit einer inkrementierten Zeile zu verwenden, die auf dem Ergebnis der Auswertung basiert, aber das scheint mit einer Funktion nicht möglich zu sein. Sieht so aus, als müsste ich weiterhin eine Schleife benutzen. – michael

Verwandte Themen