• Ich bin gerade in Passau und schaue mir einige Vorlesungen an. Heute war eine über "Priority Queue" dabei:
    Priority Queues sind Datentyprn, die sehr schnell sind, wenn man nur auf das kleinste Element zugreifen und Elemente ergänzen will. Das ganze wird als binärer Baum realisiert und wird für prioritätsabhängige Echtzeitaufgaben verwendet, zB. scheduling. Die Rechenzeit einer Operation ist dabei O(log n). Für nähere Infos hilft sicher Wikipedia.

    Ich habe mich mal an einer AutoIt-Realisierung versucht, möglicherweise ist es hier gar nicht schneller als vergleichbare Operationen, aber es war eine hübsche Aufgabe. Auch können in meiner Version noch keine Daten angehängt werden, was für eine praktische Nutzbarkeit essentiell wäre. Das ergänze ich aber noch.

    PriorityQueue.au3
    [autoit]

    #include-once <Array.au3>
    ;Priority Queue
    Func _PQ_Add(ByRef $queue,$data)
    _ArrayAdd($queue,$data)
    $element=UBound($queue)-1
    Do
    If $queue[$element] < $queue[vater($element)] Then
    _PQ_swap($queue,$element,vater($element))
    $element=vater($element)
    Else
    ExitLoop
    EndIf
    Until $element=0
    EndFunc
    Func _PQ_extractMin(ByRef $queue)
    $return=$queue[0]
    $last=UBound($queue)-1
    $queue[0]=$queue[$last]
    _ArrayDelete($queue,$last)
    $element=0
    Do
    If (exists($queue,sohn($element,1)) And $queue[$element] > $queue[sohn($element,1)]) Or (exists($queue,sohn($element,2)) And $queue[$element] > $queue[sohn($element,2)]) Then
    _PQ_swap($queue,$element,sohn($element,_PQ_SmallestSon($queue,$element)))
    $element=sohn($element,1)
    Else
    ExitLoop
    EndIf
    Until False
    Return $return
    EndFunc
    Func _PQ_swap(ByRef $queue,$e1,$e2)
    $h=$queue[$e1]
    $queue[$e1]=$queue[$e2]
    $queue[$e2]=$h
    EndFunc
    Func vater($x)
    ;internal use only
    Return Floor(($x-1)/2)
    EndFunc
    Func sohn($x,$which=1)
    ;internal use only
    Return $x*2+$which
    EndFunc
    Func exists($queue,$element)
    ;internal use only
    If UBound($queue) > $element Then
    Return True
    Else
    Return False
    EndIf
    EndFunc
    Func _PQ_SmallestSon($queue,$element)
    ;internal use only
    If not exists($queue,sohn($element,2)) Or $queue[sohn($element,1)] < $queue[sohn($element,2)] Then Return 1
    Return 2
    EndFunc

    [/autoit]


    Jetzt mit Beispiel:

    Beispiel
    [autoit]


    #include "PriorityQueue.au3"
    Dim $queue[1]=[0]
    ;Q füllen
    _PQ_Add($queue,4)
    _PQ_Add($queue,3)
    _PQ_Add($queue,8)
    _PQ_Add($queue,2)
    _PQ_Add($queue,99)
    _PQ_Add($queue,1)
    _PQ_Add($queue,0)
    _PQ_Add($queue,4.4)
    ;Elemente geordnet ausgeben
    For $i=1 To 8
    MsgBox(64,$i & ". Element",_PQ_extractmin)
    Next

    [/autoit]

    Twitter: @L3viathan2142
    Benutze AutoIt persönlich nicht mehr, da ich keinen Windows-Rechner mehr besitze.

    5 Mal editiert, zuletzt von L3viathan (12. Juli 2010 um 11:28)

  • Zitat

    schaue mir einige Vorlesungen an

    Hmm. Hast du auch zugehört ^^ ?
    Ich denke das muss man in c++ per dll-call machen, wobei dll-call ja langsam ist :huh: .

    Nur keine Hektik - das Leben ist stressig genug

  • Na "blind" solltest du in Zukunft nicht mehr schreiben - wart lieber ab bis du wieder testen kannst.
    Programmieren ist halt Try & Error ;)

    Spoiler anzeigen


    Aber sowas finde ich echt interessant - egal ob es nun wirklich performanter wird oder nicht.
    Würd mich freuen wenn du noch ein funktionierendes Beispiel posten kannst - aber lass dir Zeit (Die Ehre die ganzen Bugs zu korrigieren wollte ich dir jetzt nicht wegnehmen... ;) )

  • Hi,
    habe dazu auch mal etwas gebastelt, sogar mit grafischer Darstellung^^
    Im Prinzip werden die Ereignisse in eine nach Prioritäten (0 bis 9, höchste Priorität ist 0) sortierte Liste eingetragen.
    Dann hatte ich 2 Ansätze, um diese Liste (welche immer wieder aufgefüllt wird) abzuarbeiten.

    1. Ansatz: Das Ereignis mit der höchsten Priorität wird auch als erstes abgearbeitet. Ist keins vorhanden, dann das Ereignis mit der nächst niedrigeren Priorität. Ist zwischenzeitlich wieder ein Ereignis mit höherer Priorität eingetroffen, wird das zuerst abgearbeitet.
    Vorteil: Ereignisse mit hoher Priorität werden sehr schnell abgearbeitet, da sie "ganz oben" in der Liste stehen, Die Liste wird in jedem durchlauf von oben nach unten abgearbeitet.
    Nachteil: Das letzte Ereignis mit der niedrigsten Priorität braucht sehr lange, bis es abgearbeitet ist.

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>

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

    ;stures abarbeiten der Liste, Ereignisse mit höherer Priorität werden zuerst abgearbeitet

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

    global $debug=0 ;debug=0 , script läuft ohne Msgboxen und arraydisplays
    ;priority queue
    ;tabelle mit Ereignissen einer bestimmten Priorität
    ;
    ;Es gibt Prioritäten von 0 bis 9
    ; 0 hat die höchste Priorität
    Dim $queue[10][1000] ;maximal 1000 Ereignisse pro Priorität
    ;
    ;[priorität] [0]=Anzahl der ereignisse [1 bis n]=Ereignisse
    ; 0 [0]=3 [1]=Ereignis_33 [2]=ereignis_855 [3]=ereignis_1456
    ; 1 [0]=4 [1]=Ereignis_345 [2]=ereignis_98 [3]=ereignis_5 [4]=Ereignis_44
    ; 2 [0]=2 [1]=Ereignis_45 [2]=ereignis_686
    ; 3 [0]=5 [1]=Ereignis_45 [2]=ereignis_686 [3]=Ereignis_745 [4]=ereignis_798 [5]=ereignis_8055
    ;uswusf
    ;

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

    Dim $progress[10] ;10x progress-control
    Local $ereignis ;ereigniszähler

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

    $hgui = GUICreate("Priority queue")
    For $i = 0 To 9 ;progressbars erstellen
    $progress[$i] = GUICtrlCreateProgress(20, $i * 30 + 30, 300, 25)
    Next
    GUISetState()

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

    ;liste mit zufälligen ereignissen füllen
    For $zufall = 1 To 200 ;Zufällige Anzahl der kommenden Ereignisse
    $ereignis += 1 ;es kommt ein Ereignis...
    $prio = Random(0, 9, 1) ;mit einer priorität....
    _fillqueue($prio, $ereignis) ;ereignis in liste eintragen
    Next

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

    if $debug then _arraydisplay($queue)

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

    $lastereignis = 0 ;letzte ereignisnummer
    $random = 3
    $schnitt=0 ;Durchschnittliche Anzahl der Ereignisse, bis ein Ereignis höchster Priorität (0) abgearbeitet wurde
    $lastitem = $queue[9][$queue[9][0]] ;letztes Ereignis in der gesamten liste
    ;MsgBox(262144,'Debug line ~' & @ScriptLineNumber,'Selection:' & @lf & '$lastitem' & @lf & @lf & 'Return:' & @lf & $lastitem) ;### Debug MSGBOX

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

    Do ;prioritätenliste abarbeiten

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

    ;~ For $zufall = 1 To Random(1, $random, 1) ;Zufällige Anzahl der kommenden Ereignisse
    $ereignis += 1 ;es kommt ein Ereignis...
    $prio = Random(0, 9, 1) ;mit einer priorität....
    _fillqueue($prio, $ereignis) ;ereignis in liste eintragen
    ;~ Next
    ;_ArrayDisplay($queue)

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

    ;~ _readqueue(0) ;das dringenste Ereignis auslesen
    ; _readqueue(Random(5, 9, 1)) ;auch die weniger dringenden Ereignisse müssen irgendwann abgearbeitet werden
    winsettitle($hgui,"",$queue[9][1]&" "&$lastitem)
    ;ab hier nur Statistik-Geplänkel, wann erreicht das letzte item der liste die erste position?
    If $ereignis > 100000 Then
    $ereignis = 0
    $lastereignis = 0
    $schnitt=0
    EndIf
    if stringright($queue[0][1],2)="_0" then $schnitt+=1
    If $queue[9][1] = $lastitem Or $queue[1][1] = $lastitem Then ;das letzte Item aus der Liste hat es bis zum löschen geschafft
    MsgBox(0, "priority queue","Anzahl der Durchläufe, bis das letzte Ereigniss der niedrigsten Priorität (9) aus der Liste gelöscht wurde: "& $ereignis - $lastereignis & @crlf & _
    "Durchschnittliche Anzahl der Ereignisse, bis ein Ereignis höchster Priorität (0) abgearbeitet wurde : "&(($ereignis - $lastereignis)/$schnitt) & @crlf & _
    "Anzahl der Ereignisse mit der höchsten Priorität gesamt: "&$schnitt)
    $schnitt=0
    $lastereignis = $ereignis
    For $i = 9 To 0 Step -1
    If $queue[$i][0] = 0 Then ContinueLoop
    $lastitem = $queue[$i][$queue[$i][0]] ;letztes Item aus der liste holen
    ExitLoop
    Next
    ;~ MsgBox(262144,'Debug line ~' & @ScriptLineNumber,'Selection:' & @lf & '$lastitem' & @lf & @lf & 'Return:' & @lf & $lastitem) ;### Debug MSGBOX
    ;~ _arraydisplay($queue)
    ;_ArrayDisplay($queue)
    EndIf
    _readqueue(0) ;das dringenste Ereignis auslesen
    ;Until guigetmsg()*Sleep(10) = -3
    Until guigetmsg() = -3

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

    Func _fillqueue($prio, $ereignis) ;queue mit ereignissen einer bestimmten priorität füllen
    $queue[$prio][0] += 1 ;Anzahl der Ereignisse in der bestimmten Priorität um eins erhöhen
    $queue[$prio][$queue[$prio][0]] = "Ereignis_" & $ereignis &"_prio_"&$prio;Ereignis an die letzte Position in der Kette schreiben
    GUICtrlSetData($progress[$prio], $queue[$prio][0])
    EndFunc ;==>_fillqueue

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

    Func _readqueue($prio) ;ereignis einer bestimmten Priorität aus der Liste holen
    ; $time=timerinit()
    Local $ereignis
    For $i = $prio To UBound($queue, 1) - 1 ;prioritäten und alle weiteren mit geringerer priorität
    ;~ If $queue[0][0] = 0 Then ;die erste prioritätenreihe ist leer, alle anderen Prioritäten eins nach "unten" rutschen
    ;~ if $debug then Msgbox(0,"priority queue","in der folgenden liste sieht man, wie die prioritäten eine zeile weiter nach unten sortiert wurden",3)
    ;~ ;_ArrayDisplay($queue)
    ;~ For $t = 0 To 8 ;alle prioritäten eins nach unten, aus [1] wird [0] aus [2] wird [1] usw
    ;~ For $f = 0 To $queue[$t + 1][0]
    ;~ $queue[$t][$f] = $queue[$t + 1][$f]
    ;~ Next
    ;~ If $t < 8 Then ;die ereignisse mitsortieren
    ;~ For $f = $queue[$t + 2][0] To $queue[$t + 1][0]
    ;~ $queue[$t + 1][$f] = 0
    ;~ Next
    ;~ EndIf

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

    ;~ Next
    ;~ For $f = $queue[9][0] To 0 Step -1 ;letzte reihe löschen
    ;~ $queue[9][$f] = 0
    ;~ Next
    ;~ if $debug then _ArrayDisplay($queue)
    ;~ EndIf

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

    if $debug then _ArrayDisplay($queue)
    If $queue[$i][0] = 0 Then ContinueLoop ;es sind keine ereignisse vorhanden, nächste Priorität

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

    $ereignis = $queue[$i][1] ;erstes ereignis einer priiorität aus der liste holen
    For $anz = 2 To $queue[$i][0] + 1 ;ereignisse der priorität ein feld weiter nach unten schieben
    $queue[$i][$anz - 1] = $queue[$i][$anz]
    Next
    $queue[$i][0] -= 1 ;anzahl der ereignisse in der liste um eins verkleinern
    GUICtrlSetData($progress[$i], $queue[$i][0])
    ExitLoop ;ereignis gefunden
    Next
    ;~ $l=timerdiff($time)
    ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $l = ' & $l & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    Return $ereignis

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

    EndFunc ;==>_readqueue

    [/autoit]

    2. Ansatz: Es wird immer nur das Ereignis mit der höchsten Priorität abgearbeitet!
    Gibt es nun kein Ereignis mit der höchsten Priorität mehr, dann werden alle Ereignisse in ihrer Priorität eine Stufe erhöht d.h. aus 1 wird 0 , aus 2 wird 1 usw
    Vorteil: Das letzte Element mit der niedrigsten Priorität wird sehr schnell erreicht! D.h. auch Niedrige Prioritäten werden schnell berücksichtigt.
    Nachteil: Neue Elemente mit höchster Priorität müssen im schlechtesten Fall etwas warten, bis die "aufgerückten" Elemente abgearbeitet sind

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    global $debug=0 ;debug=0 , script läuft ohne Msgboxen und arraydisplays, debug=1 für info´s
    $speed=1 ;zum beschleunigen speed=0

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

    ;priority queue
    ;tabelle mit Ereignissen einer bestimmten Priorität
    ;
    ;Es gibt Prioritäten von 0 bis 9
    ; 0 hat die höchste Priorität
    Dim $queue[10][1000] ;maximal 1000 Ereignisse pro Priorität
    ;
    ;[priorität] [0]=Anzahl der ereignisse [1 bis n]=Ereignisse
    ; 0 [0]=3 [1]=Ereignis_33 [2]=ereignis_855 [3]=ereignis_1456
    ; 1 [0]=4 [1]=Ereignis_345 [2]=ereignis_98 [3]=ereignis_5 [4]=Ereignis_44
    ; 2 [0]=2 [1]=Ereignis_45 [2]=ereignis_686
    ; 3 [0]=5 [1]=Ereignis_45 [2]=ereignis_686 [3]=Ereignis_745 [4]=ereignis_798 [5]=ereignis_8055
    ;uswusf
    ;

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

    Dim $progress[10] ;10x progress-control
    Local $ereignis ;ereigniszähler

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

    $hgui = GUICreate("Priority queue")
    For $i = 0 To 9 ;progressbars erstellen
    $progress[$i] = GUICtrlCreateProgress(20, $i * 30 + 30, 300, 25)
    Next
    GUISetState()

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

    ;liste mit zufälligen ereignissen füllen
    For $zufall = 1 To 200 ;Zufällige Anzahl der kommenden Ereignisse
    $ereignis += 1 ;es kommt ein Ereignis...
    $prio = Random(0, 9, 1) ;mit einer priorität....
    _fillqueue($prio, $ereignis) ;ereignis in liste eintragen
    Next

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

    if $debug then _arraydisplay($queue)

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

    $lastereignis = 0 ;letzte ereignisnummer
    $random = 3
    $schnitt=0 ;Durchschnittliche Anzahl der Ereignisse, bis ein Ereignis höchster Priorität (0) abgearbeitet wurde
    $lastitem = $queue[9][$queue[9][0]] ;letztes Ereignis in der gesamten liste
    ;MsgBox(262144,'Debug line ~' & @ScriptLineNumber,'Selection:' & @lf & '$lastitem' & @lf & @lf & 'Return:' & @lf & $lastitem) ;### Debug MSGBOX

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

    Do ;prioritätenliste abarbeiten

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

    ;~ For $zufall = 1 To Random(1, $random, 1) ;Zufällige Anzahl der kommenden Ereignisse
    $ereignis += 1 ;es kommt ein Ereignis...
    $prio = Random(0, 9, 1) ;mit einer priorität....
    _fillqueue($prio, $ereignis) ;ereignis in liste eintragen
    ;~ Next
    ;_ArrayDisplay($queue)

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

    _readqueue(0) ;das dringenste Ereignis auslesen
    ; _readqueue(Random(5, 9, 1)) ;auch die weniger dringenden Ereignisse müssen irgendwann abgearbeitet werden

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

    ;ab hier nur Statistik-Geplänkel, wann erreicht das letzte item der liste die erste position?
    If $ereignis > 100000 Then
    $ereignis = 0
    $lastereignis = 0
    $schnitt=0
    EndIf
    if stringright($queue[0][1],2)="_0" then $schnitt+=1
    If $queue[0][1] = $lastitem Or $queue[1][1] = $lastitem Then ;das letzte Item aus der Liste hat es bis zum löschen geschafft
    MsgBox(0, "priority queue","Anzahl der Durchläufe, bis das letzte Ereigniss der niedrigsten Priorität (9) aus der Liste gelöscht wurde: "& $ereignis - $lastereignis & @crlf & _
    "Durchschnittliche Anzahl der Ereignisse, bis ein Ereignis höchster Priorität (0) abgearbeitet wurde : "&(($ereignis - $lastereignis)/$schnitt) & @crlf & _
    "Anzahl der Ereignisse mit der höchsten Priorität gesamt: "&$schnitt)
    $schnitt=0
    $lastereignis = $ereignis
    For $i = 9 To 0 Step -1
    If $queue[$i][0] = 0 Then ContinueLoop
    $lastitem = $queue[$i][$queue[$i][0]] ;letztes Item aus der liste holen
    ExitLoop
    Next
    ;~ MsgBox(262144,'Debug line ~' & @ScriptLineNumber,'Selection:' & @lf & '$lastitem' & @lf & @lf & 'Return:' & @lf & $lastitem) ;### Debug MSGBOX
    ;~ _arraydisplay($queue)
    ;_ArrayDisplay($queue)
    EndIf
    if $speed then sleep(30)
    Until guigetmsg() = -3

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

    Func _fillqueue($prio, $ereignis) ;queue mit ereignissen einer bestimmten priorität füllen
    $queue[$prio][0] += 1 ;Anzahl der Ereignisse in der bestimmten Priorität um eins erhöhen
    $queue[$prio][$queue[$prio][0]] = "Ereignis_" & $ereignis &"_prio_"&$prio;Ereignis an die letzte Position in der Kette schreiben
    GUICtrlSetData($progress[$prio], $queue[$prio][0])
    EndFunc ;==>_fillqueue

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

    Func _readqueue($prio) ;ereignis einer bestimmten Priorität aus der Liste holen
    ; $time=timerinit()
    Local $ereignis
    For $i = $prio To UBound($queue, 1) - 1 ;prioritäten und alle weiteren mit geringerer priorität
    If $queue[0][0] = 0 Then ;die erste prioritätenreihe ist leer, alle anderen Prioritäten eins nach "unten" rutschen
    if $debug then Msgbox(0,"priority queue","in der folgenden liste sieht man, wie die prioritäten eine zeile weiter nach unten sortiert wurden",3)
    ;_ArrayDisplay($queue)
    For $t = 0 To 8 ;alle prioritäten eins nach unten, aus [1] wird [0] aus [2] wird [1] usw
    For $f = 0 To $queue[$t + 1][0]
    $queue[$t][$f] = $queue[$t + 1][$f]
    Next
    If $t < 8 Then ;die ereignisse mitsortieren
    For $f = $queue[$t + 2][0] To $queue[$t + 1][0]
    $queue[$t + 1][$f] = 0
    Next
    EndIf

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

    Next
    For $f = $queue[9][0] To 0 Step -1 ;letzte reihe löschen
    $queue[9][$f] = 0
    Next
    if $debug then _ArrayDisplay($queue)
    EndIf

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

    If $queue[$i][0] = 0 Then ContinueLoop ;es sind keine ereignisse vorhanden, nächste Priorität

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

    $ereignis = $queue[$i][1] ;erstes ereignis einer priiorität aus der liste holen
    For $anz = 2 To $queue[$i][0] + 1 ;ereignisse der priorität ein feld weiter nach unten schieben
    $queue[$i][$anz - 1] = $queue[$i][$anz]
    Next
    $queue[$i][0] -= 1 ;anzahl der ereignisse in der liste um eins verkleinern
    GUICtrlSetData($progress[$i], $queue[$i][0])
    ExitLoop ;ereignis gefunden
    Next
    ;~ $l=timerdiff($time)
    ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $l = ' & $l & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    Return $ereignis

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

    EndFunc ;==>_readqueue

    [/autoit]

    Um die "Laufzeiten" der Ereignisse mit niedrigster Priorität zu veranschaulichen, habe ich einen debug-mode eingebaut. Dort wird in jedem Durchlauf die Liste angezeigt.
    Für kleine Bildschirme sollte man die Startliste auf 100 Elemente begrenzen oder noch weniger...dem Prinzip ist das egal, da pro Durchlauf immer ein Ereignis hinzugefügt, und ein Ereignis herausgenommen wird

  • So, jetzt bin ich wieder in Weimar und habe alles behoben: Das Hauptproblem war, dass ich nicht mit ByRef gearbeitet habe.

    Der Code in Post #1 ist jetzt jedenfalls lauffähig.

    Edit: Außerdem verhältnismäßig schnell, finde ich: Ohne Idealbedingungen (surfen im Hintergrund) braucht das Skript zum Einbauen und geordneten Ausgeben von 10000 Zufallszahlen 63789 Millisekunden.

    Twitter: @L3viathan2142
    Benutze AutoIt persönlich nicht mehr, da ich keinen Windows-Rechner mehr besitze.

    Einmal editiert, zuletzt von L3viathan (12. Juli 2010 um 11:34)