asphelper.de - Tipps & Skripts - Doppelte Elemente aus einem Array entfernen

Link: Wichtiger Hinweis in eigener Sache!


 Autor: Klaus Keller Erstellt am: 2006-02-15 Aufrufe: 9697 


 << Vorheriger Tipp Nächster Tipp >>



Tipp 90: Doppelte Elemente aus einem Array entfernen



In diesem Tipp möchte ich Ihnen aufzeigen, wie Sie doppelte Elemente aus einem Array entfernen können. Aber es soll nicht nur ein Skript präsentiert werden, sondern gleich mehrere, inklusive der Vor- und Nachteile und die Performance des jeweiligen Skripts. Zuerst einmal das erste Skript, das dient zunächst einmal als Beispiel und Ausgangspunkt für verschiedene Verbesserungen:
1   
2
3
4
5
6
7
8
9
10
11
12
13
14
Public Function RemoveDuplicates(ByVal theArray)
    dim newArray(), tmpVal, objDict, index
    set objDict = Server.CreateObject("Scripting.Dictionary")
    for index = 0 to uBound(theArray)
        tmpVal = theArray(index)
        if not objDict.exists(tmpVal) then
            call objDict.add(tmpVal, empty)
            redim preserve newArray(objDict.count - 1)
            newArray(uBound(newArray)) = tmpVal
        end if
    next
    set objDict = nothing
    RemoveDuplicates = newArray
End Function


Die Verbesserung haben 2 Richtungen bezüglich der Optimierung, erstens die Geschwindigkeit und zweitens der Speicherbedarf. Beginnen wir mit der ersten Zeile, scheinbar alles ok? Nun, zuerst einmal wird das Array per ByVal übergeben. Damit wird das Array als Value, also als Wert übergeben. Ändert man dies auf ByRef, erfolgt die Übergabe per Referenz. Bei einer Übergabe per ByVal wird jedoch mehr Speicher benötigt da das Array innerhalb der Prozedur quasi neu angelegt wird.

Wann sollte eine Variable per ByVal übergeben werden? Wenn innerhalb einer Prozedur verändernd auf eine Variable zugegriffen wird und diese Änderungen sich nicht auf die an die Prozedur übertragene Variable übertragen werden darf. Aber im obigen Skrpt ist erkennbar, daß die Varialbe theArray in der Funktion nicht verändert wird. Somit kann das Array per ByRef übergeben werden, damit wird auch weniger Speicher benötigt.

Die nächste Optimierungsmöglichkeit ist in Zeile 7. Bei jedem Wert der nicht bereits vorhanden ist wird das Array neu dimensioniert. Genau diese Neudimensionierung mit ReDim jedoch ist Performance-Intensiv. Daher wird das ganze geändert.

Zuerst einmal erhält das Ergebnis-Array genau die gleiche Menge an Elementen wie das Array theArray. Dann wird die ReDim-Anweisung in der Schleife gelöscht. Die Redimensionerung wird erst nach der Schleife durchgeführt. Allerdings muss noch festgestellt werden, wieviele Datensätze überhaupt im Ergebnisarray vorhanden sind. Die Funktion sieht nun so aus:
1   
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Public Function RemoveDuplicates(ByRef theArray)
    Dim NewArray(), index, index2, objDict, tmpVal
    Redim newArray(UBound(theArray))
    set objDict = Server.CreateObject("Scripting.Dictionary")
    index2 = 0
        
    for index = 0 to UBound(theArray)
        tmpVal = theArray(index)
        if not objDict.exists(tmpVal) then
            call objDict.add(tmpVal, empty)
            NewArray(index2) = tmpVal
            index2 = index2 + 1
        end if
    next
    set objDict = nothing
    redim preserve NewArray(index2 - 1)
        
    RemoveDuplicates = newArray
End Function


Nun stellen Sie sich wahrscheinlich die Frage: Was ist besser? Ich möchte Ihnen eine Performance-Messung aufzeigen. Zunächst einmal die erste Funktion, verwendet wurden rund 1.200 Datensätze in der jeder Datensatz 3 mal vorkam, das Ergebnis:
0,0140 Sekunden
Nun aber die optimierte Version:
0,0099 Sekunden
Das sieht doch schon besser aus! Gibt es noch weitere Optimierungsmöglichkeiten? Ja, vor allem was der Speicherbedarf angeht. Im zweiten Skript wird ein Dictionary-Objekt verwendet, damit wird geprüft ob ein Wert bereits vorhanden ist. Jeder Wert wird in das Dictionary geschrieben, aber genauso in das Ergebnis-Array. Ist das wirklich notwendig? Nein! Denn damit wird ja jeder Wert 2mal in die Variablen geschrieben, es reicht wenn das ganze nur einmal verwendet wird. So sieht das Skript jetzt aus:
1   
2
3
4
5
6
7
8
9
10
11
12
13
14
Public Function RemoveDuplicates(ByRef theArray)
    dim objDict
    set objDict = Server.CreateObject("Scripting.Dictionary")
    objDict.add theArray(1), empty
    
    for index = 1 to uBound(theArray)
        if not objDict.exists(theArray(index)) then
            objDict.add theArray(index), empty
        end if
    next

    RemoveDuplicates = objDict.Keys
    set objDict = nothing
End Function


Zuerst einmal die Performance: 0,00099 Sekunden, noch einmal deutlich besser als die zweite Lösung und rund damit mehr als 14 mal so schnell wie das erste Beispiel. Auch im Speicherbedarf wird sich die Optimierung bei grösseren Datenmengen deutlich bemerkbar machen. Mit diesen Optimierungen haben Sie damit eine hocheffiziente Möglichkeit doppelte Einträge aus einem Array zu entfernen, Empfehlenswert und der Tipp!

Nur könnten Sie ja noch auf die Idee kommen, und weitere Versionen vorschlagen, die ja sogar noch besser sein könnten. Daher stelle ich Ihnen noch weitere Möglickeiten vor, zuerst eine die vollständig auf das Dictionary-Objekt verzichtet. Dafür werden 2 Schleifen und 2 Arrays eingesetzt. Würde es sich um eine Kompilierte Sprache handeln durchaus eine Alternative, aber zuerst einmal das Skript:
1   
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Public Function RemoveDuplicates(ByRef myArray)
    Dim index, index2, index3, blnCheck, NewArray
    ReDim newArray(UBound(myArray))
    index3 = 0
    
    for index = 0 To Ubound(myArray)
        blnCheck = false
        for index2 = 0 To index3
            if myArray(index) = newArray(index2) then
                blnCheck = true
                exit for
            end if
        next
        if blnCheck = false then
            newArray(index3) = myArray(index)
            index3 = index3 + 1
        end if
    next
    ReDim Preserve newArray(index3 - 1)
    RemoveDuplicates = NewArray
End Function


Aber, wie sieht die Performance aus? 0,1322 Sekunden. Oops! Aber warum so langsam? Ganz einfach: Bei VBScript handelt es sich nun mal um eine Interpretierte Sprache, das Dictionary-Objekt ist nun mal ein Objekt das in VBScript bereits vorhanden ist und somit auch kompiliert ist. Sieg für das Dictionary-Objekt!

Das Array hat noch eine Methode namens "Filter", damit kann man innerhalb eines Array nach Werten suchen, so könnte ein Skript damit aussehen:
1   
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Public Function RemoveDuplicates(ByRef myArray)
    Dim newArray(), arrErg, index2
    
    index2 = 0
    ReDim newArray(UBound(myArray))
    
    For index = 0 To UBound(myArray)
        If UBound(Filter(newArray, myArray(index))) < 0 Then
            newArray(index2) = myArray(index)
            index2 = index2 + 1
        End If
    Next
    ReDim Preserve newArray(index2 - 1)
    RemoveDuplicates = newArray
End Function


Und die Geschwindigkeit? 0,0646 Sekunden, leider auch nicht gerade der Renner. Auch hierbei muss man sagen: Das Dictionary ist die erste Wahl!



 << Zum vorherigen Tipp 89:
Ist Javascript im Browser aktiviert?

 Zum nächsten Tipp 91 >>
'Bitte warten Sie' oder ein Fortschrittsbalken einblenden

top top
© Copyright By Klaus Keller, 2001-2008 - Alle Rechte vorbehalten.