Array durchsuchen

  • Guten Abend :)

    ich hab versucht eine Funktion zu schreiben, die ein übergebenes Array durchsucht. Bei mehrfach vorhandenen Werten sollen alle bis auf eines gelöscht werden, hinter dem Objekt soll dann stehen wie oft es vorhanden war. Ich hab mir das so vorgestellt das ich das übergebene Array erstmal in eine 2D-Array kopiere und dann in der 2. Ebene schreibe, wie oft es vorhanden ist. Das hat schon ganz schön Hirnschmalz bei mir abverlangt  :wacko: 


    Zuletzt kam das dabei heraus:

    [autoit]

    Func searchArray($Array)
    Local $Zwischenspeicher[UBound($Array) - 1][UBound($Array) -1 ]
    Local $Name = True ;Variable dient dazu, ein Objekt bei mehrfach-Objekten zu behalten
    Local $countItems ;Anzahl der mehrach vorhandenen Objekten
    For $counter = 0 To UBound($Array) - 1
    $Zwischenspeicher[$counter][0] = $Array[$counter] ;Array wird in 2D-Array kopiert
    For $zaehler = 0 To UBound($Zwischenspeicher) - 1
    If $Zwischenspeicher[$zaehler][0] == $Array[$counter] Then ;Bis her zwischengespeicherte Werte werden mit dem aktuellen Wert verglichen (ob mehrfach vorhanden)
    If $Name == True Then ;Falls der Objektname noch nicht im Zwischenspeicher ist (damit ein Objekt von den mehreren vorhanden ist)
    $Zwischenspeicher[$zaehler][0] = $Array[$counter] ;Wert wird in Zwischenspeicher geschrieben
    $countItems = $countItems + 1 ;Zähler wird hochgezählt
    $Name = False ;Name wird auf false gesetzt da der Objektname nun gespeichert wurde
    Else
    $Zwischenspeicher[$zaehler][0] = "" ;bei mehrfach vorhandenen Objekten wird der Objektname durch 'nichts' ersetzt
    $countItems = $countItems + 1 ;der Zähler wird hier ebenfalls hochgezählt
    EndIf
    Else
    $Zwischenspeicher[0][$zaehler] = $countItems ;der Zähler wird in die 2. Ebene geschrieben
    $countItems = 0 ;Items werden wieder auf 0 gesetzt (da das Objekt nicht mehr in der aktuell durchsuchten Liste vorkam
    $Name = True ;Name wird wieder auf True gesetzt
    EndIf
    Next
    Next
    Return $Zwischenspeicher
    EndFunc

    [/autoit]


    Leider kommt folgende Fehlermeldung:

    [autoit]

    (6) : ==> Array variable has incorrect number of subscripts or subscript dimension range exceeded.:

    [/autoit]


    Habt ihr ne Idee?

    5 Mal editiert, zuletzt von CrazyAutoIT (23. Juni 2013 um 08:25)

  • Auch wenn man fast nix lesen kann, so denke ich das du UBound falsch verwendest.

    Du musst immer UBound($Array) - 1 nehmen, da die Arrays 0 basierend sind.

    Um Missverständnisse zu vermeiden, mein Name rührt vom Sternenbild und nicht vom Shop her :D


    Rainbow Dash :rock:

    "Das, wobei unsere Berechnungen versagen, nennen wir Zufall." (Albert Einstein)

  • Unter Quellcode und nicht Editor posten

    Um Missverständnisse zu vermeiden, mein Name rührt vom Sternenbild und nicht vom Shop her :D


    Rainbow Dash :rock:

    "Das, wobei unsere Berechnungen versagen, nennen wir Zufall." (Albert Einstein)

  • Ah danke, hab jetzt mal überall ne '-1' davor gemacht, der Fehler kommt trotzdem noch.

  • Du benutzt UBound auf jeden fall immer noch falsch.
    Lies dir mal die Hilfe dazu durch, wenn du keinen Parameter angibst gibt er dir die Dimension zurück in deinen Beispiel 2

    Zitat

    [optional] Die Dimension des Arrays dessen Größe ermittelt werden soll. Der Standard ist 1, dies ist die Erste Dimension. Falls dieser Parameter 0 ist, wird die Anzahl der Dimensionen des Arrays zurückgegeben.

    Und der errror besagt, das du ein Element im Array aufrufst das gar nicht existiert.

    //Edit: Und kann es sein, dass du

    [autoit]

    _ArrayUnique

    [/autoit]

    suchst? :D

    Um Missverständnisse zu vermeiden, mein Name rührt vom Sternenbild und nicht vom Shop her :D


    Rainbow Dash :rock:

    "Das, wobei unsere Berechnungen versagen, nennen wir Zufall." (Albert Einstein)

  • Hm, _ArrayUnique liefert ja nur ein Array zurück in der jedes Element nur ein mal vorkommt, aber nicht wie oft das Element in dem übergebenen Array vorkam.

    Kann man ein 2D-Array nicht mit einem 1D-Array vergleichen? Hab den Code insoweit abgeändert das nun die 1. Dimension genommen wird:

    [autoit]


    Func searchArray($Array)
    Local $Zwischenspeicher[UBound($Array)][UBound($Array)]
    Local $Name = True ;Variable dient dazu, ein Objekt bei mehrfach-Objekten zu behalten
    Local $countItems ;Anzahl der mehrach vorhandenen Objekten
    For $counter = 0 To UBound($Array) - 1
    $Zwischenspeicher[$counter][0] = $Array[$counter] ;Array wird in 2D-Array kopiert
    For $zaehler = 0 To UBound($Zwischenspeicher, 1)
    If $Zwischenspeicher[$zaehler][0] == $Array[$counter] Then ;Bis her zwischengespeicherte Werte werden mit dem aktuellen Wert verglichen (ob mehrfach vorhanden)
    If $Name == True Then ;Falls der Objektname noch nicht im Zwischenspeicher ist (damit ein Objekt von den mehreren vorhanden ist)
    $Zwischenspeicher[$zaehler][0] = $Array[$counter] ;Wert wird in Zwischenspeicher geschrieben
    $countItems = $countItems + 1 ;Zähler wird hochgezählt
    $Name = False ;Name wird auf false gesetzt da der Objektname nun gespeichert wurde
    Else
    $Zwischenspeicher[$zaehler][0] = "" ;bei mehrfach vorhandenen Objekten wird der Objektname durch 'nichts' ersetzt
    $countItems = $countItems + 1 ;der Zähler wird hier ebenfalls hochgezählt
    EndIf
    Else
    $Zwischenspeicher[0][$zaehler] = $countItems ;der Zähler wird in die 2. Ebene geschrieben
    $countItems = 0 ;Items werden wieder auf 0 gesetzt (da das Objekt nicht mehr in der aktuell durchsuchten Liste vorkam
    $Name = True ;Name wird wieder auf True gesetzt
    EndIf
    Next
    Next
    Return $Zwischenspeicher
    EndFunc

    [/autoit][autoit]

    (6) : ==> Array variable has incorrect number of subscripts or subscript dimension range exceeded.:

    [/autoit]
  • Ich hab sowas vor einiger Zeit mal für ISI360 und sein ISN Autoit Studio geschrieben. Dort muss ein 1D Array mit Variablennamen durchsucht und Doubletten entfernt werden. Bei Doubletten soll die Anzahl der doppelten Einträge vermerkt werden, also genau das was du auch vorhast soweit ich dich verstanden habe.

    Wie funktioniert mein Code? Ich nehme das Input Array und prüfe für jeden Eintrag des Arrays ob er bereits in einen temporären String kopiert wurde (das geht schneller als ständig zwei Arrays miteinander zu vergleichen). Ist der Eintrag des Arrays noch nicht in diesem String, dann wird er hinzugefügt (Stringverkettung mit eindeutigem Trennzeichen $sepChar welches in keinem der Arrayeinträge vorkommen darf) und außerdem in ein neues 2D Array (Ergebnis) kopiert. Ist der Eintrag bereits im String und somit auch im Ergebnis Array vorhanden, dann wird die Stelle im Ergebnisarray gesucht und in der 2. spalte des Ergebnisarrays vermerkt, dass dieser Eintrag einmal mehr ($aNew[$k][1]+=1) gefunden wurde als bislang.

    Nachdem man das für alle Arrayzeilen gemacht hat muss man noch das Ergebnisarray kleiner machen (redim), da das neue Array ja sehr sicher weniger Zeilen haben wird als das Input Array. Damit man die richtige Größe kennt muss man sich bei jedem neuen Eintrag über eine Zählervariable merken wieviele einmalige Einträge man hinzugefügt hat (bei mir ist das die Zählervariable $j)

    Meine Funktion erlaubt es dann noch das 2D Ergebnisarray wieder in ein 1D Array zurückzuwandeln und dabei dann die Anzahl der doppelten Treffer nicht in einer 2. Spalte sondern in geschweiften Klammern zu vermerken.

    Ich gebe zu das ganze ist etwas komplizierter und vermutlich auf den ersten Blick nicht sofort verständlich, aber es funktioniert und es dürfte so ziemlich die schnellste und effizienteste Lösung für das Problem sein.


    Hier der Quellcode:

    Spoiler anzeigen
    [autoit]


    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global $avArray[11] = ["WM_KILLFOCUS", "WM_KILLFOCUS", "WM_ENABLE", "WM_SETREDRAW", "UnMountMOM", "MountMOM", "WM_CLOSE", "WM_PAINT", "WM_SETREDRAW", "WM_SETREDRAW", "Mountmom"]

    [/autoit] [autoit][/autoit] [autoit]

    $timer=TimerInit()
    $aUniqueCount = _arrayUniqueCount($avArray,1) ; Mode 1 für 1D-Rückgabe
    ConsoleWrite(TimerDiff($timer) & @CRLF)
    If $aUniqueCount = -1 Then
    ConsoleWrite("Fehler: Es wurde kein Array übergeben!" & @CRLF)
    Else
    _ArrayDisplay($aUniqueCount)
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    Func _arrayUniqueCount(ByRef $aData, $mode=2, $sepChar=",") ; mode: 1 = 1D Rückgabe , 2 = 2D Rückgabe

    [/autoit] [autoit][/autoit] [autoit]

    If Not IsArray($aData) Then Return -1 ; wichtig: bitte Rückgabe dieser Funktion prüfen bevor weiter gearbeitet wird...

    [/autoit] [autoit][/autoit] [autoit]

    Local $aNew[ubound($aData)][2] ; für 2D Rückgabe // geändert: array wird am schluß neu dimensioniert!
    Local $aNew1D[1] ; für 1D Rückgabe
    Local $sTemp=$sepChar ; string verkettung des ergebnis arrays für schnelle unique prüfung
    Local $j=0 ; zahl der unique treffer

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; Anmerkung: Der Vergleich sollt vielleicht doch nicht case sensitiv sein, da ansonsten gleichnamige Variablen mit anderer Schreibweise nicht erkannt werden, daher wieder auf not casesensitiv geändert, ist aber ein wenig langsamer...
    For $i=0 To UBound($aData)-1
    If StringInStr($sTemp,$sepChar & $aData[$i] & $sepChar,0,-1) Then ; stringprüfung arbeitet schneller als große arrays zu durchlaufen und "viele" strings zu vergleichen // geändert: sepchar muss nun ganze Variable umschließen
    For $k=0 To UBound($aNew)-1 ; bei bereits vorhandenen einträgen müssen wir zwangsweise die bisherigen suchergebnisse durchlaufen um den korrekten index zu finden
    If $aNew[$k][0]=$aData[$i] Then
    $aNew[$k][1]+=1
    ExitLoop
    EndIf
    Next
    Else
    $aNew[$j][0] = $aData[$i]
    $aNew[$j][1] = 1
    $sTemp &= $aData[$i] & $sepChar
    $j += 1
    ;ReDim $aNew[$j+1][2] ; geändert: wäre lahm, wurde im thread denke ich damals auch erwähnt...
    EndIf
    Next
    ReDim $aNew[$j][2] ; geändert: array wird erst zum schluß auf richtige Größe gebracht, performanter....

    [/autoit] [autoit][/autoit] [autoit]

    ;If UBound($aNew) > 1 Then ReDim $aNew[UBound($aNew)-1][2]

    [/autoit] [autoit][/autoit] [autoit]

    ; sofern zwingend ein 1D Array als Ausgabe benötigt wird muss das neue Array noch einmal durchlaufen werden:
    if $mode = 1 Then
    ReDim $aNew1D[UBound($aNew)]
    For $i=0 To UBound($aNew)-1
    If $aNew[$i][1] > 1 Then
    $aNew1D[$i]=$aNew[$i][0] & " {" & $aNew[$i][1] & "x}"
    Else
    $aNew1D[$i]=$aNew[$i][0]
    EndIf
    Next
    $aNew=$aNew1D
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    Return $aNew
    EndFunc

    [/autoit]

    3 Mal editiert, zuletzt von misterspeed (22. Juni 2013 um 22:14)

  • Genau das hab ich gesucht, ich bin aber anscheinend zu blöd die Funktion aufzurufen, wie mach ich das? Das Ergebnis kommt als Array, wie ruf ich da die einzelnen Werte auf?
    Das:

    [autoit]


    arrayUniqueCount($TestArray, 1, ",")[$Feld]

    [/autoit]

    funktioniert leider nicht.

    [autoit]


    Local $TestArray[10]

    [/autoit][autoit][/autoit][autoit]

    For $counter = 0 To UBound($TestArray)-1
    $TestArray[$counter] = 1
    Next

    [/autoit][autoit][/autoit][autoit]

    MsgBox(64, "Count", arrayUniqueCount($TestArray, 1, ","))

    [/autoit]
  • In meinem Script ist doch ein Beispiel zur Anwendung. Arrays kann man nicht in einer msgbox anzeigen, höchstens einzelne Werte des Arrays. Eine Funktion kann auch niemals mit eckigen Klammern versehen werden um den Rückgabewert der Funktion als Array anzusprechen. Du musst das Rückgabe Array schon vorher in eine Variable packen.

    EDIT: Ich empfehle dir einen Blick in das Array Tutorial von Bugfix http://www.bug-fix.info/array_tut.htm

    [autoit]


    #include <array.au3> ; wird für arraydisplay() gebraucht

    [/autoit][autoit][/autoit][autoit]

    ; zuerst natürlich $testarray mit daten füllen...
    dim $testarray[5]
    $testArray[0] = "abc"
    $testArray[1] = "123"
    $testArray[2] = "def"
    $testArray[3] = "123"
    $testArray[4] = "abc"

    [/autoit][autoit][/autoit][autoit]

    ; meine Funktion nutzen um neues Array zu erstellen
    $aUnique1D = _arrayUniqueCount($TestArray, 1, ",") ; 1D Variante mit geschweiften Klammern
    $aUnique2D = _arrayUniqueCount($TestArray, 2, ",") ; 2D Variante

    [/autoit][autoit][/autoit][autoit]

    ; $aUnique?D ist dein neues Array, welches von meiner Funktion erzeugt und zurückgegeben wurde
    ; das kann wie jedes Array verwendet werden, also z.B. mit

    [/autoit][autoit][/autoit][autoit]

    _arraydisplay($aUnique1D,"1D Variante mit geschweiften Klammern")
    _arraydisplay($aUnique2D,"2D Variante")

    [/autoit][autoit][/autoit][autoit]

    ; komplett anzeigen
    ; oder einzelne Felder per Index auslesen

    [/autoit][autoit][/autoit][autoit]

    msgbox(0,"1D Variante mit geschweiften Klammern","Treffer Nummer 1: " & $aunique1D[0])
    msgbox(0,"1D Variante mit geschweiften Klammern","Treffer Nummer 2: " & $aunique1D[1])

    [/autoit][autoit][/autoit][autoit]

    msgbox(0,"2D Variante","Treffer Nummer 1: " & $aunique2D[0][0] & " Anzahl der doppelten Treffer: " & $aunique2D[0][1])
    msgbox(0,"2D Variante","Treffer Nummer 2: " & $aunique2D[1][0] & " Anzahl der doppelten Treffer: " & $aunique2D[1][1])

    [/autoit][autoit][/autoit][autoit]

    ;usw.

    [/autoit]

    3 Mal editiert, zuletzt von misterspeed (22. Juni 2013 um 23:50)

    • Offizieller Beitrag

    Eine andere Möglichkeit wäre das Scripting.Dictionary-Objekt zu verwenden:

    Spoiler anzeigen
    [autoit]


    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    ; Zufallswerte zum testen erstellen
    $n = 10000;Random(50, 99, 1)
    Dim $w[$n]
    For $i = 0 To $n - 1
    $w[$i] = Random(0, 9, 1)
    Next
    ; Zufallswerte zum testen erstellen
    _ArrayDisplay($w, $n & ' Zufallswerte zwischen 0 und 9') ; Zufallswerte anzeigen

    [/autoit] [autoit][/autoit] [autoit]

    $aModal = _ArrayModal($w)
    _ArrayDisplay($aModal, 'Häufigkeitsverteilung')

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _ArrayModal($w)
    Local Static $oDictionary = ObjCreate('Scripting.Dictionary')
    $oDictionary.CompareMode = 1
    For $i = 0 To UBound($w) - 1
    If $oDictionary.Exists($w[$i]) Then
    $oDictionary.Item($w[$i]) = $oDictionary.Item($w[$i]) + 1
    Else
    $oDictionary.Add($w[$i], 1)
    EndIf
    Next
    Local $aOut[$oDictionary.Count][2], $i = 0, $colKeys
    $colKeys = $oDictionary.Keys
    For $strKey In $colKeys
    $aOut[$i][0] = $strKey
    $aOut[$i][1] = $oDictionary.Item($strKey)
    $i += 1
    Next
    $oDictionary = ''
    _ArraySort($aOut, 1, 0, 0, 1)
    Return $aOut
    EndFunc ;==>_ArrayModal

    [/autoit]
  • Beeindruckend wieviel schneller die Scripting Dictionary Variante bei etwas größeren Testmengen ist, selbst bei deinen simplen Testdaten sind es schon 20%, reduziert man die Redundanz und nutzt zufällige 2 stellige Strings ist das scripting dictionary sogar bis zu 4000% schneller.