Textdatei zeilenweise durchsuchen

  • Hallo zusammen,

    ich versuche mich schon seit zwei Tagen an einem AutoIT-Script um eine Textdatei zeilenweise einzulesen. Immer wenn ich denke ich habe es ist doch noch irgendwie ein Fehler drin.

    Folgende Ausgangssituation:
    Eine Textdatei ("Daten.txt" oder ".csv") ganz egal enthält mit Semikolon separierte Einträge:
    WertA1;WertB1;WertC1;WertD1;WertE1; ... WertK1
    WertA2;WertB2;WertC2;WertD2;WertE2; ... WertK2
    ...

    Bedingungen:

    • Nun möchte ich nach einem bestimmten Wert suchen und dafür die Datei zeilenweise einlesen.
    • Wird der Wert (z.B.: WertK23) gefunden sollen alle Werte der Zeile entsprechend ausgegeben werden. (WertA23=... ; WertB23=...; ...WertK23=...)
    • Wir der Wert nicht gefunden sollen das Script sich die nächste Zeile vornehmen bis EOF.
    • Auch wenn der Wert gefunden wird, soll dennoch die Datei bis EOF durchsucht werden, da ggf. ein zweiter, dritter, ... Eintrag vorhanden ist. Ebenso soll dann die ganze Zeile ausgegeben werden.


    Mein Lösungsansatz:

    [autoit]


    #include <File.au3>

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

    ; Basisdaten
    $daten = "c:\Daten.csv"
    $datensatzgroesse = _FileCountLines($Daten)

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

    ; Anzeige der Summe aller Datensätze
    MsgBox (4096, "Inhalt", "Es befinden sich " & $datensatzgroesse & " Datensätze in der Verwaltung.")

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

    ; Abfrage, was getan werden soll
    $suchsoll = InputBox ("Datensuche", _
    "Bitte geben Sie die Daten ein," & @CRLF & _
    "nach denen gesucht werden soll", "", "")
    If @error = 1 Then
    Call ("abbruch")
    Else
    Call ("suchen", $suchsoll)
    EndIf

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

    ; Funktionen
    Func suchen ($suchsoll)
    $file = FileOpen ($daten, 0)
    For $i = 1 To $datensatzgroesse
    $zeile = FileReadLine($daten, $i)
    $durchlauf = 0
    Do
    $durchlauf = $durchlauf + 1
    If StringInStr ($zeile, $suchsoll) Then
    $datensatz = StringSplit ($zeile,';')
    MsgBox (4096, "Datenfund " & $suchsoll, _
    "Wert1: " & @TAB & $datensatz[1] & @CRLF & _
    "Wert2: " & @TAB & $datensatz[2] & @CRLF & _
    "Wert3: " & @TAB & $datensatz[3] & @CRLF & _
    "Wert4: " & @TAB & $datensatz[4] & @CRLF & _
    "Wert5: " & @TAB & $datensatz[5] & @CRLF & _
    "Wert6: " & @TAB & $datensatz[6] & @CRLF & _
    "Wert7: " & @TAB & $datensatz[7] & @CRLF & _
    "Wert8: " & @TAB & $datensatz[8] & @CRLF & _
    "Wert9: " & @TAB & $datensatz[9] & @CRLF & _
    "Wert10: " & @TAB & $datensatz[10] & @CRLF & _
    "Wert11: " & @TAB & $datensatz[11])
    ExitLoop 1
    Else
    MsgBox (48, "Meldung", "Der gesuchte Datensatzinhalt " & $suchsoll & " wurde nicht gefunden!")
    ExitLoop 2
    EndIf
    Until $durchlauf > $datensatzgroesse
    Next
    $file = FileClose ($daten)
    EndFunc

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

    Func abbruch ()
    MsgBox (16, "Abbruch", "Sie haben Abbrechen/ Cancel gedrückt." & @crlf & _
    "Auf wiedersehen.")
    EndFunc

    [/autoit]

    Weitere Planung:

    • Über eine anfängliche GUI die Option besitzen suchen, ändern, löschen, anlegen von Daten in diese Datei
    • Werte der einzelne Zeile exportieren und entsprechend neues Textfile schreiben

    Vielleicht hilft euch dies den Hintergrund etwas zu verstehen.

    Ich möchte um Gottes Willen von euch kein komplettes Script haben, das will ich schon selbst hinbekommen. Nur wie bereits erwähnt tippel ich schon seit ca. 2 Tagen auf der Stelle, weil ich irgendwie nicht den Durchlauf hinbekomme.
    Ich erhoffe mir von euch den entscheidenden Hinweis/ Denkanstoss sodass ich wieder weitermachen kann.
    Bei Fragen stehe ich euch zur Verfügung.

    ------------------------------------------------------------------------------------------
    Viele Grüße
    SaNiSo

    ------------------------------------------------------------------------------------------

    "Rechtschreibfehler sind lediglich Specialeffects meiner Tastatur."
    ------------------------------------------------------------------------------------------

    Einmal editiert, zuletzt von SaNiSo (24. April 2012 um 22:32)

  • Ok ein paar Tipps:

    • Bei FileReadLine sollte man nicht mit der Zeilennummer arbeiten sondern es selbst Zeile für Zeile durchgehen lassen. Sonst wird bei jedem Aufruf die Datei geöffnet und geschlossen. Es entfällt dann auch das vorherige Zählen der Zeilenanzahl.
    • Eine einfache Variable welche nur dann True wird wenn einmal etwas gefunden wird kann dir helfen am Ende die Entscheidung zu treffen ob etwas gefunden wurde.
    • Call() braucht man nicht.
    • Wir wissen nicht wie die Datei genau aussieht und können daher schlecht dafür anpassen. Eine kleine Beispieldatei zum Testen wäre schön gewesen.


    Bringt dich folgendes vielleicht etwas weiter?:

    Spoiler anzeigen
    [autoit]

    ; Basisdaten
    Global $s_FilePath = "c:\Daten.csv"
    Global $s_SearchString = InputBox("Datensuche", _
    "Bitte geben Sie die Daten ein," & @CRLF & _
    "nach denen gesucht werden soll", "", "")
    If @error Then abbruch()

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

    Global $h_File = FileOpen($s_FilePath, 0)
    If $h_File = -1 Then abbruch()

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

    Global $i_C = 1, $b_Found = False
    Do
    $s_Line = FileReadLine($h_File)
    If @error = -1 Then ExitLoop

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

    If StringInStr($s_Line, $s_SearchString) Then
    $b_Found = True
    $s_Out = ""
    $a_Split = StringSplit($s_Line, ';')
    For $i = 1 To $a_Split[0]
    $s_Out &= "Wert" & $i & @TAB & $a_Split[$i] & @CRLF
    Next
    MsgBox(4096, "Datenfund " & $s_SearchString, $s_Out)
    EndIf
    $i_C += 1
    Until 0

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

    FileClose($h_File)
    If Not $b_Found Then MsgBox(48, "Meldung", "Der gesuchte Datensatzinhalt " & $s_SearchString & " wurde nicht gefunden!")

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

    Func abbruch()
    MsgBox(16, "Abbruch", "Sie haben Abbrechen/ Cancel gedrückt." & @CRLF & _
    "Auf wiedersehen.")
    Exit
    EndFunc

    [/autoit]
  • Mit einer Schleife die Zeilen einzeln zu laden halte ich nicht für geeignet für diesen Zweck. Gerade wenn du größere "Datenbanken" analysieren willst, lade doch zu Anfang die gesamte Datei in einen Array, dann kannst du auch ganz perfekt Zeilenweise suchen. StringSPlit kannst du dann natürlich immernoch verwenden. Die Datei sollte nur nicht größer als der RAM sein ;)


    Edit: Natürlich wäre eine Testdatei auch hilfreich. 8o

    minx

  • Mit einer Schleife die Zeilen einzeln zu laden halte ich nicht für geeignet für diesen Zweck. Gerade wenn du größere "Datenbanken" analysieren willst, lade doch zu Anfang die gesamte Datei in einen Array, dann kannst du auch ganz perfekt Zeilenweise suchen. StringSPlit kannst du dann natürlich immernoch verwenden. Die Datei sollte nur nicht größer als der RAM sein ;)

    minx

    Was langsamer ist als Zeilenweise auslesen.

    Andy hat mir ein Schnitzel gebacken aber da war ein Raupi drauf und bevor Oscar das Bugfixen konnte kam Alina und gab mir ein AspirinJunkie.

  • lass mich raten du hast dir noch nie angeschaut wie FileReadToArray und ArraySearch arbeiteten. Sonst wüsstest du das die Kombo aus FileReadToArray + ArraySearch länger dauert als direkt FileReadLine + stringInStr.

    Andy hat mir ein Schnitzel gebacken aber da war ein Raupi drauf und bevor Oscar das Bugfixen konnte kam Alina und gab mir ein AspirinJunkie.

  • Ist ja gut. Wenn ich etwas in einer Datei suche, zeilenweise, geht es damit schneller, liegt vielleicht aber auch daran, dass ich unter Wine arbeite. Ich hab mit Filereadline-Schleifen Suchzeiten von fast 50 Sekunden, mit Array nur Bruchteile einer Sekunde. Liegt vielleicht auch an meinem Skript, ich kann nur aus Erfahrung sprechen.

  • Was langsamer ist als Zeilenweise auslesen.

    Kann man nicht ganz pauschalisieren.
    Wenn nur das erste Auftreten gefunden werden soll und der Wert am Anfang steht dann ist FileReadLine bedeutend schneller.
    Steht der Wert jedoch am Ende oder es sollen alle Auftreten gefunden werden sollte es langsamer sein.
    Kleines Testskript:

    Speedtest FileReadLine vs. _FileListToArray()
    [autoit]

    #include <Array.au3>
    #include <File.au3>

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

    Global Const $N = 1000

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

    ; Anpassen:
    Global Const $s_File = "D:\Kal.ics"
    Global Const $s_Search = "UID:{b6d5a87b-16a9-7c44-af40-3b8c107b5684}"

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

    $i_T = TimerInit()
    For $i = 1 To $N
    Global $a_File = 0
    $a_File = _FileReadToArray($s_File, $a_File)
    _ArraySearch($a_File, $s_Search)
    Next
    $i_T = TimerDiff($i_T) / 1000
    ConsoleWrite("_ArraySearch(): " & Round($i_T,2) & " ms" & @CRLF)

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

    $i_T = TimerInit()
    For $i = 1 To $N
    $h_File = FileOpen($s_File,0)
    Do
    $s_Line = FileReadLine($h_File)
    If @error = -1 Then ExitLoop
    If $s_Line = $s_Search Then ExitLoop
    Until 0
    FileClose($h_File)
    Next
    $i_T = TimerDiff($i_T) / 1000
    ConsoleWrite("FileReadLine(): " & Round($i_T,2) & " ms" & @CRLF)

    [/autoit]

    Wenn du mit FileReadToArray alles einliest kannst du mit ArraySearch deutlich schneller suchen.

    Wie soll das für diesen Fall hier funktionieren?
    ArraySearch sucht nur nach kompletter Übereinstimmung eines Array-Elementes mit dem Vergleichswert.
    SaNiSo sucht aber nach einzelnen Werten die innerhalb einer Zeile stehen und nicht nach kompletten Zeilen.


    Ich hab mit Filereadline-Schleifen Suchzeiten von fast 50 Sekunden, mit Array nur Bruchteile einer Sekunde

    Trägst du jeweils die Zeilennummer ein oder lässt du FileReadLine automatisch bis EOF durchlaufen?`Zeig am besten mal ein kleines Beispielskript wo dieser Effekt auftritt.

  • Zuerst einmal möchte ich euch allen für die schnelle und hilfreiche Unterstützung danken.

    Jedoch lag es mir hier fern einen Wettkampf in Punkto schnelleres Ein-/ Auslesen einer Textdatei via FileReadLine oder FileListtoArray() zu entfachen oder durch meinen Post die "Gemeinde" in zwei Lager zu spalten.

    Ich habe letztendlich euer aller Tipps zu Herzen genommen und das Codebeispiel von AspirinJunkie ein wenig angepasst und siehe da "die Sch... geht". :D

    Für die Zukunft nehme ich mir auf jeden Fall mit, dass mir umso detaillierter und auch schneller geholfen werden kann je mehr Informationen, Dokumentationen, Anlagen etc. ich hinzufüge.

    Also nochmals euch allen meinen rechtherzlichen Dank.

    ------------------------------------------------------------------------------------------
    Viele Grüße
    SaNiSo

    ------------------------------------------------------------------------------------------

    "Rechtschreibfehler sind lediglich Specialeffects meiner Tastatur."
    ------------------------------------------------------------------------------------------


  • ArraySearch sucht nur nach kompletter Übereinstimmung eines Array-Elementes mit dem Vergleichswert.

    Nein das stimmt nicht. Schau die den sechsten Parameter von ArraySearch an also "iCompare" (in der letzten Version hieß der Parameter noch "iPartial"). Dadurch wird innerhalb von ArraySearch ein StringinStr ausgeführt.


    AspirinJunkie keine Ahnung was dein Testscript beweißen soll.

    Hier mal ein Beispiel welches "echte" Ergebnisse liefert.

    HIer einmal mit _ArraySearch dazu die Testdatei "ende.txt" nehmen bei der die zu Suchende Zeile ganz am Ende steht. Das ganze macht bei _ArraySearch() ein Suchzeit von 0.56 ms und bei FileReadLine macht es 0.74 ms.

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    #include <File.au3>

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

    Dim $filequelle = @ScriptDir & "\ende.txt"
    Dim $search = "ganzamende"
    Dim $array

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

    $t = TimerInit()

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

    _FileReadToArray($filequelle, $array)
    _ArraySearch($array, $search)

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

    $t = TimerDiff($t) / 1000
    ConsoleWrite("_ArraySearch(): " & Round($t, 2) & " ms" & @CRLF)

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

    $t = TimerInit()

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

    $file = FileOpen($filequelle, 0)
    While 1
    $line = FileReadLine($file)
    If $line = $search Then
    EndIf
    If @error = -1 Then ExitLoop
    WEnd
    FileClose($file)
    $t= TimerDiff($t) / 1000
    ConsoleWrite("FileReadLine(): " & Round($t, 2) & " ms" & @CRLF)

    [/autoit]

    Und hier ein Beispiel bei der das gesuchte Wort mehrmals vorkommt, der einfachheithalber hab ich _ArrayFindAll genommen, da es intern ganz einfach nur _ArraySearch verwendet. Dazu passend die Testdatei "mehrere.txt". Das Ergebiss _ArraySearch braucht 1.11 ms und FileReadLine nur 0.71ms.

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    #include <File.au3>

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

    Dim $filequelle = @ScriptDir & "\mehrere.txt"
    Dim $search = "dazwischen"
    Dim $array

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

    $t = TimerInit()

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

    _FileReadToArray($filequelle, $array)
    _ArrayFindAll ($array, $search)

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

    $t = TimerDiff($t) / 1000
    ConsoleWrite("_ArraySearch(): " & Round($t, 2) & " ms" & @CRLF)

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

    $t = TimerInit()

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

    $file = FileOpen($filequelle, 0)
    While 1
    $line = FileReadLine($file)
    If $line = $search Then
    EndIf
    If @error = -1 Then ExitLoop
    WEnd
    FileClose($file)
    $t= TimerDiff($t) / 1000
    ConsoleWrite("FileReadLine(): " & Round($t, 2) & " ms" & @CRLF)

    [/autoit]
  • Nein das stimmt nicht. Schau die den sechsten Parameter von ArraySearch an also "iCompare"

    Oh dankeschön - wusste ich nicht. Daher minx: Sorry

    AspirinJunkie keine Ahnung was dein Testscript beweißen soll.
    Hier mal ein Beispiel welches "echte" Ergebnisse liefert.

    Das Script soll ein Testskript darstellen mit dem man selbst feststellen kann ob und wann die Methode mit FileReadLine() schneller ist als die mit _ArraySearch().
    Im Gegensatz zu deinem wird aber nicht nur 1 Durchlauf jeweils durchgeführt sondern mehrere und diese dann gemittelt um verlässlichere Ergebnisse zu erzielen und zum anderen stimmt bei mir die angegebene Zeiteinheit ;)
    1000 Durchläufe wäre für deine Testdateien aber natürlich zu lange - aber das kann man ja mit der Variablen $N steuern. Wobei mir auffällt das ich beim Mitteln fest 1000 eingetragen habe statt $N - das müsste man dann natürlich auch noch ändern.

    Prinzipiell bestätigen deine Ergebnisse ja auch die Grundaussage die bei mir auch herauskommen sollte. Nämlich dass man nicht pauschal sagen kann das die FileReadLine-Methode schneller ist als die _ArraySearch-Methode.

  • Sie wiederspricht aber deiner Aussage das FileReadLine beim auffinden aller Ergebnisse in der Textdatei langsamer ist hehe.

    Andy hat mir ein Schnitzel gebacken aber da war ein Raupi drauf und bevor Oscar das Bugfixen konnte kam Alina und gab mir ein AspirinJunkie.

    • Offizieller Beitrag

    He, da tobt ja ein Grundsatzfragen-Krieg. ;)
    Meiner Erfahrung nach sind die Laufzeiten wirklich nicht pauschalisierbar. Mit einer Datei geht die eine Variante, mit der nächsten die andere etwas besser.
    Sollte man allerdings in Minuten-Laufzeiten kommen und man braucht aber eine schnelle Lösung ist vermutlich die Grenze von AutoIt erreicht. - Dann bleibt nur noch der Blick über'n Gartenzaun zu anderen (Skript-) Sprachen.

  • Nun ich schrieb "sollte". ;)
    Deine Ergebnisse zeigen nun ja allerdings dass auch das nicht pauschalisierbar ist.
    _ArrayFindAll() ist zur Einfachheit zwar o.k. aber in deinem Fall wo 9600 Auftreten gefunden werden müssen führt _ArrayFindAll() 9600x ein ReDim durch während du bei der FileReadLine-Methode gar kein Array erstellen lässt.
    Ein repräsentativerer Vergleich wäre daher bei beiden ein Array zu erstellen und bei beiden mit der gleichen Methode.
    Daher wäre folgendes abgewandeltes Skript wohl etwas repräsentativer:

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    #include <File.au3>

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

    Global Const $filequelle = @ScriptDir & "\mehrere.txt"
    Global Const $search = "dazwischen"

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

    #region FileReadLine
    $t = TimerInit()
    $file = FileOpen($filequelle, 0)
    Global $avResult = "", $i_C = 0
    While 1
    $i_C += 1
    $line = FileReadLine($file)
    If @error = -1 Then ExitLoop
    If $line = $search Then $avResult &= $i_C & "#"
    WEnd
    $a_T = StringSplit(StringTrimRight($avResult, 1), "#")
    FileClose($file)
    $t= TimerDiff($t)
    ConsoleWrite("FileReadLine(): " & Round($t) & " ms" & @CRLF)
    #endregion FileReadLine

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

    #region ArraySearch
    $t = TimerInit()
    Global $array
    _FileReadToArray($filequelle, $array)
    $a_T = _MyArrayFindAll ($array, $search)
    $t = TimerDiff($t)
    ConsoleWrite("_ArraySearch(): " & Round($t) & " ms" & @CRLF)
    #endregion ArraySearch

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

    Func _MyArrayFindAll(Const ByRef $avArray, Const $vValue, $iStart = 0, Const $iEnd = 0, Const $iCase = 0, Const $iCompare = 0, Const $iSubItem = 0)
    $iStart = _ArraySearch($avArray, $vValue, $iStart, $iEnd, $iCase, $iCompare, 1, $iSubItem)
    If @error Then Return SetError(@error, 0, -1)

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

    Local $avResult = ""
    Do
    $avResult &= $iStart & "#"
    $iStart = _ArraySearch($avArray, $vValue, $iStart + 1, $iEnd, $iCase, $iCompare, 1, $iSubItem)
    Until @error
    Return StringSplit(StringTrimRight($avResult, 1), "#")
    EndFunc ;==>_ArrayFindAll

    [/autoit]


    Bei mir bleibt es dann dabei dass FileReadLine schneller ist - aber längst nicht mehr so krass.
    Wenn man allerdings heran geht und in die Datei zufällig 3 "udo" einfügt und nach denen sucht (also weniger als die 9600 Elemente im ersten Test) dreht sich das Bild wieder vollkommen - dann ist die Methode mit _ArraySearch() wieder schneller.
    Es bleibt also dabei: In keinem Fall kann man pauschal sagen dass eine der beiden Methoden langsamer oder schneller ist.

    Ich persönlich nutze meist immer die FileReadLine-Methode und ich denke dass man damit in der Summe auch besser fährt als erst die komplette Datei in ein Array zu schreiben.