» Start & Installation » ASP-Grundlagen » VBScript » Datenbanken » Erweiterte Techniken » Fehlercode-Suche » FAQ » Forum für aspfehlercodes » ASP » JavaScript » ASCII & ANSI » HTTP-Statuscodes » Codepage » LCID » VBScript » JScript
|
Link: Wichtiger Hinweis in eigener Sache!
| Autor: Klaus Keller | Erstellt am: 2006-02-15 | Aufrufe: 9697 |
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!
|
|