Zeilen tauschen und hinzufügen

  • Hallo,

    Kurzfassung :) Ich suche eine Lösung für:

    Finde eine Zeile die mit X beginnt, prüfe dann, ob die nächste Zeile mit Y beginnt und wenn ja, dann tausche beide Zeilen und füge eine weitere Zeile Z hinzu - für alle Zeilen in einer Textdatei.

    Langfassung:

    ich möchte in einer Textdatei ( *.txt ) Zeilen tauschen und hinzufügen, ohne dass ich die Datei als array einlese, zeilenweise abarbeite und in eine neue Datei schreiben muss.

    Sollte das jedoch die beste Möglichkeit sein, folgendes Problem zu lösen, freue ich mich über Anregungen.

    Vielleicht hat jemand so ein Skript bereits in AutoIt geschrieben.

    PS: wie formuliert man das Problem auf Englisch – im englischen Forum fand ich nichts unter „swap lines“?

    Vereinfacht ausgedrückt möchte ich folgendes:

    Ich suche nach einer Zeile die mit folgendem Text beginnt „# 2“

    $suche = "# 2"

    If StringLeft ( $aktuelle_Zeile, 3) = $suche Then

    EndIf

    Die darauffolgende Zeile muss mit "# 1" beginnen.

    Also in der ersten IF Abfrage eine zweite

    $suche = "# 1"

    If StringLeft ( $nächste_Zeile, 3) = $suche Then

    EndIf

    Meine Probleme beginnen bereits an dieser Stelle:

    Wenn ich den Text in einem Array habe und das Array zeilenweise durchgehe, und ich treffe auf eine Fundstelle ("# 2" gefunden), wie kann in der zweiten IF-Abfrage (suche nach "# 1") bereits in der nächste Zeile ( "# 1" ) suchen, obwohl ich noch im array in der vorigen Zeile ( " # 2" ) bin? ( siehe Beispiel )

    Gibt es eine vereinfachte Lösung in AutoIt, die so lautet:

    Daher

    Finde eine Zeile die mit X beginnt, prüfe dann, ob die nächste Zeile mit Y beginnt und wenn ja, dann tausche beide Zeilen und füge eine weitere Zeile Z hinzu.

    ( klingt einfach, aber irgendwie beiße ich mir daran die Zähne aus )

    Beispiel

    Quelle - Die Textdatei enthält einen Text mit folgender Struktur:

    # 2 beliebiger Text

    # 1 juhu ein Sandkorn

    ... viele Zeilen

    # 2 beliebiger Text

    # 1 juhu ein Korn

    ... viele Zeilen

    Nach der Bearbeitung mit dem AutoIt-Skript soll die Datei wie folgt aussehen:

    # 1 juhu ein Sandkorn

    # 2 beliebiger Text

    # 3 juhu ein Sandkorn

    ... viele Zeilen

    # 1 juhu ein Sandkorn

    # 2 beliebiger Text

    # 3 juhu ein Korn

    Vielen Dank

    2 Mal editiert, zuletzt von AutoMit (21. September 2018 um 14:11)

  • Wenn ich den Text in einem Array habe und das Array zeilenweise durchgehe, und ich treffe auf eine Fundstelle ("# 2" gefunden), wie kann in der zweiten IF-Abfrage (suche nach "# 1") bereits in der nächste Zeile ( "# 1" ) suchen, obwohl ich noch im array in der vorigen Zeile ( " # 2" ) bin?

    Ehm... einfach den nächsten Arrayindex ansprechen? Wenn du grad in [n] bist und #2 findest, dann schaust du einfach ob in [n + 1] #1 steht.

  • Also genau genommen möchtest du nicht Zeilen tauschen, sondern etwas, das deinen Text sortiert?

    Ich gehe mal davon aus, das auch folgendes vorkommen kann:

    #3 blablabla

    #1 bla

    #4 blablablabla

    #2 blabla

    ...welches dann so aussehen soll:

    #1 bla

    #2 blabla

    #3 blablabla

    #4 blablablabla

    Oder bleibt es bei deinem Beispiel, dass immer nur die Werte gedreht (der letzte = der erste,...) werden müssen?

  • So wie ich das verstanden habe, suchst du eine Zahl in einer Textdatei.

    Wenn du die Zahl gefunden hast, willst du quasi einen Range Scan

    machen, du gehst also solange weiter (+1), bis keine Zahlen mehr kommen

    und das selbe umgekehrt (-1).

    Die ganzen Werte kannst du dann mit den _Array Funktionen sortieren.

    Bin mir nicht sicher, aber _ArraySort() könnte glaube ich weiterhelfen. :)

    Falls ich was falsch verstanden habe, korrigiert mich nochmal. :P

  • Also ich hätte es ähnlich wie xSunLighTx3 geschrieben hat, gemacht.

    Wenn die Zahlen immer um 180° gedreht sind (unterste = erste, oberste = letzte), hätte ich es so gemacht:

    1. Die Zeilen der Textdatei von der letzten Zeile an nach dem ersten "# 1" suchen -> Zeilennummer in Variable

    2. Die Zeilen Weiter suchen bis er kein "#" am Anfang mehr findet -> Zeilennummer in 2. Variable

    3. Einen Array erstellen, der der Anzahl der gefundenen Werte entspricht (Variable 1 - Variable 2) -> 3. Variable für Schleifen Durchlauf & Arraygrößenbestimmung

    4. die gefunden Werte anhand ihres Wertes (Variable 1 - Zeilennummer) in den Array an Position N schreiben

    5. Die Werte aus dem Array sollten dadurch schon sortiert sein und können der Reihenfolge wieder eingefügt werden

    Oder Wenn die Werte wild sind in Punkt 3 werte wild in Array einfügen und mit _ArraySort() sortieren (2D-Array)

  • Gibt es eine vereinfachte Lösung in AutoIt, die so lautet:


    Daher

    Finde eine Zeile die mit X beginnt, prüfe dann, ob die nächste Zeile mit Y beginnt und wenn ja, dann tausche beide Zeilen und füge eine weitere Zeile Z hinzu.

    ( klingt einfach, aber irgendwie beiße ich mir daran die Zähne aus )

    So in etwa...

  • Hallo AutoMit

    Code
    For $i = 0 To UBound($aFile) -1 Step 1
        If $i < UBound($aFile) -1 And StringLeft($aFile[$i], 3) = '# 2' And StringLeft($aFile[$i + 1], 3) = '# 1' Then

    Ich möchte nur ergänzen, dass du den Code nicht ganz so übernehmen solltest. Ich habe den Code nicht getestet, bin aber sicher, dass er tut, was er soll. Jedoch sollte Ubound nicht in einer Schleife aufgerufen werden. Das kostet unnötig Zeit (abhängig von der Anzahl der Zeilen). Außerdem meine ich, die Prüfung in der Schleife ist gar nicht notwendig. Stattdessen sollte es auch so funktionieren:

    Code
    For $i = 0 To UBound($aFile) -2
        If StringLeft($aFile[$i], 3) = '# 2' And StringLeft($aFile[$i + 1], 3) = '# 1' Then

    Grüße autoiter

  • Jedoch sollte Ubound nicht in einer Schleife aufgerufen werden. Das kostet unnötig Zeit (abhängig von der Anzahl der Zeilen)

    Das stimmt zwar aber das ist (wenn man sich jetzt komplexere Anwendungsfälle ansieht, vernachlässigbar wenig.

    Ich hab eben ein kleines Skript gebastelt und ein 2000x1000x5 Array erstellt und es mit zufälligen Zahlen gefüttert. (260 MB RAM Verbrauch etwa).

    Und innerhalb einer Sekunde habe ich 612.341 also ~ 610.000 UBound Abfragen für die 1., 2. UND 3. Dimension erhalten.

    Führe ich den selben Test kleineren Zahlen aus (2, 1, 5) kriege ich identische Werte, macht also praktisch überhaupt keinen Unterschied.

    Ich würde lieber die Variante nehmen, die semantisch schöner aussieht, funktional hat man nichts davon aber wenn sich der Code besser lesen lässt, warum nicht.

    In diesem Beispiel steht es allerdings außer Frage. UBound() - 2 wäre hier so oder so der way-to-go.

  • Jedoch sollte Ubound nicht in einer Schleife aufgerufen werden. Das kostet unnötig Zeit (abhängig von der Anzahl der Zeilen).

    Nope... denn der Wert für UBound wird nur einmalig ermittelt... egal wie viele Zeilen das Array hat - leicht nachvollziehbar daran, dass die letzten drei Zeilen, die mit _ArrayAdd beim ersten Durchlauf hinzugefügt werden, nicht sortiert werden!

    Außerdem meine ich, die Prüfung in der Schleife ist gar nicht notwendig. Stattdessen sollte es auch so funktionieren:

    Sicher funktioniert das auch ohne Prüfung... das kann aber dazu führen, dass das Script abstürzt, wenn die Datei fehlerhaft ist.

    Einmal editiert, zuletzt von Bitnugger (21. September 2018 um 19:15)

  • Hallo Bitnugger

    Nope... denn der Wert für UBound wird nur einmalig ermittelt... egal wie viele Zeilen das Array hat

    Das stimmt nur für die Zeile For $i = 0 To UBound($aFile) -1 Step 1 (auch wenn ich das Step 1 weglassen würde. Aber du bist eigentlich immer genauer als ich :D).

    Innerhalb der Schleife wird das wenigstens in AutoIt wohl erneut ermittelt.

    Spoiler anzeigen

    Hierzu:

    Sicher funktioniert das auch ohne Prüfung... das kann aber dazu führen, dass das Script abstürzt, wenn die Datei fehlerhaft ist.

    Du hast die Größe doch vorher ermittelt (mit Ubound). Wenn du die For-Schleife nicht beim letzten Element, sondern beim Vorletzten Element abbrichst, brauchst du doch nicht zu prüfen, ob du gerade im letzten Array-Element bist.

    Grüße autoiter

  • Hi zusammen?

    Wieso Array? Ich dachte es geht um einen Text?!

    Komplette Datei einlesen, die beiden Zeilen mit den entsprechenden "Anfängen" finden, tauschen und (mit der 3.Zeile ergänzt) stringreplacen.

    Um Texte zu bearbeiten verwendet man entsprechende Textbearbeitungsfunktionen, so sehe ich das jedenfalls. Arrays braucht dabei kein Mensch! Ein Regex macht das geforderte in einem Einzeiler.....und diesmal in einem "richtigen" Einzeiler^^

  • Das stimmt nur für die Zeile For $i = 0 To UBound($aFile) -1 Step 1 (auch wenn ich das Step 1 weglassen würde.

    Ja, sicher... genau davon reden wir doch... und ja, Step 1 kann man weglassen, weil es Default ist... und was innerhalb der Schleife passiert, ist wieder eine völlig andere Sache!

    Die in deinem Test-Script ermittelten Zeiten machen keinen Sinn, wenn der Code innerhalb der beiden Schleifen unterschiedlich ist... in der ersten For...Next prüfst bei jedem Durchgang UBound... das kostet zusätzliche Zeit... und ja, je größer das Array, umso länger braucht UBound, um die Anzahl der Zeilen im Array zu ermitteln - aber... entweder ist es ein Array mit fixer Größe, oder an irgendeiner Stelle muss die Anzahl der Zeilen im Array mit UBound ermitteln werden... und dann ist es egal, wo dies getan wird.

    Fehlt da nicht noch was? ;)

    If 999 Then

  • ....immer noch Array...;(

    Naja gut, hier meine Anleitung zum Copypaste für den Einzeiler

  • Wieso Array? Ich dachte es geht um einen Text?!

    Komplette Datei einlesen, die beiden Zeilen mit den entsprechenden "Anfängen" finden, tauschen und (mit der 3.Zeile ergänzt) stringreplacen.

    Das geht natürlich auch... :rofl:

    AutoIt
    Local $sFile = _
        '# 2 beliebiger Text' & @CRLF & _
        '# 1 juhu ein Sandkorn' & @CRLF & _
        '... viele Zeilen' & @CRLF & _
        '# 2 beliebiger Text' & @CRLF & _
        '# 1 juhu ein Korn' & @CRLF & _
        '... viele Zeilen' & @CRLF
    ConsoleWrite('> vorher: ' & @CRLF & $sFile & @CRLF)
    $sFile = StringRegExpReplace($sFile, '(# 2.+\R)(# 1)(.+\R)', '\2\3\1\# 3\3')
    ConsoleWrite('! nachher: ' & @CRLF & $sFile & @CRLF)

    Einmal editiert, zuletzt von Bitnugger (22. September 2018 um 07:25)

  • Das geht natürlich auch...:rofl:

    Ich bin nicht der große regexer, aber so etwas einfaches bekomme ich dank https://regex101.com auch hin :o)

    Du hast das \R verwendet :thumbup:, ich hab´s aufgedröselt...hätt ich mal in die AutoIt-Hilfe geschaut, da steht´s drin.:klatschen:

    '... viele Zeilen' & @CRLF

    hach, wie profan....pah!:rock:

    Spass beiseite, so simpel geht das alles bei regex101.com

    Zwischenablage01.jpg

    Im rechten Feld wird jede Eingabe erklärt, echt klasse, darunter werden die "Treffer" angezeigt, und darunter in der Reference....naja....da sucht man sich zusammen, was man an den ganzen kryptischen Zeichen so braucht. Ich muss da auch immer reingucken, selbst für so simples Zeug wie den Unterschied von .* und .+ reicht meine Gehirnkapazität nicht (mehr) aus. Aber Wissen ist wissen, wo es steht!

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    2 Mal editiert, zuletzt von Andy (21. September 2018 um 21:05)

  • Hey Leute,

    die Regex-Lösung von Andy finde ich klasse(, hätte den Einzeiler aber nicht hinbekommen). :)

    Hierzu:

    Ja, sicher... genau davon reden wir doch... und ja, Step 1 kann man weglassen, weil es Default ist... und was innerhalb der Schleife passiert, ist wieder eine völlig andere Sache!

    Die in deinem Test-Script ermittelten Zeiten machen keinen Sinn, wenn der Code innerhalb der beiden Schleifen unterschiedlich ist... in der ersten For...Next prüfst bei jedem Durchgang UBound... das kostet zusätzliche Zeit... und ja, je größer das Array, umso länger braucht UBound, um die Anzahl der Zeilen im Array zu ermitteln - aber... entweder ist es ein Array mit fixer Größe, oder an irgendeiner Stelle muss die Anzahl der Zeilen im Array mit UBound ermitteln werden... und dann ist es egal, wo dies getan wird.

    Fehlt da nicht noch was? ;)

    If 999 Then

    Bei If 999 Then fehlt nichts. Schau dir mal die Schleife mit Ubound an. Da wird in meinem sinnlosen Beispiel dasselbe gemacht.

    Sorry, du hast einfach nicht verstanden.

    Du redest von der For $i = 0 To UBound($aFile) -1 Step 1 

    Du schreibst selbst, dass man Ubound dann innerhalb der Schleife nicht mehr nutzen sollte. Allerdings war meine ursprüngliche Aussage ja genau auf diesen Fehler in deinem ersten Skript bezogen!

    AutoIt
    For $i = 0 To UBound($aFile) -1 Step 1
        If $i < UBound($aFile) -1 And StringLeft($aFile[$i], 3) = '# 2' And StringLeft($aFile[$i + 1], 3) = '# 1' Then

    Innerhalb der Schleife prüfst du bei jedem Durchlauf, ob schon das letzte Element erreicht ist. Davon habe ich abgeraten. Du hast das zitiert und "Nope".. gesagt. Daraufhin habe ich dir das Beispiel mit unterschiedlich großen Arrays und Schleifen gegeben, in denen nichts anderes passiert, als einmal eine statische Größe zu prüfen und eine immer neu ermittelte Größe zu prüfen...

    Aber richtig ist auch die Antwort von alpines , dass es meist vernachlässigbar ist. Eigentlich hatte ich mir das Skript geschrieben, um seine Aussagen nachvollziehen zu können.

    Grüße autoiter

  • die Regex-Lösung von Andy finde ich klasse(, hätte den Einzeiler aber nicht hinbekommen).

    Code
    filewrite("ergebnis.txt",StringRegExpReplace(fileread("testdatei.txt"),"(?m)(^bla.*$)\r\n(blub.*$)", "$2" & @CRLF & "$1" & @CRLF & "ZZZZZZZZ"))

    krank, aber geht^^

    Wieso krank? Weil jegliche Möglichkeiten des Debugging ausfallen. Definitiv sind 99% der User solch einer von einem "Programmierer" erstellten Konstruktion nicht in der Lage, bei Fehlern auch nur den Ansatz für eine Lösungsmöglichkeit zu finden.

    Ähnliches auch in den o.g. Scripten.

    1. For $i = 0 To UBound($aFile) -1 Step 1
    2. If $i < UBound($aFile) -1 And StringLeft($aFile[$i], 3) = '# 2' And StringLeft($aFile[$i + 1], 3) = '# 1' Then

    Mal abgesehen davon, dass in jedem Schleifendurchlauf auf Ubound() geprüft werden muss, wird genau dieses Ubound() auch noch mehrfach innerhalb der Schleife verwendet. Das gehört VOR der Schleife einmal berechnet und dann in eine Variable. Diese Variable ist dann per Tastendruck zu debuggen...

    Gleiches gilt für die StringLeft(). Die werden auch jedes mal neu berechnet:Face:

    Der Einzeiler für´s Regex ist natürlich schwer zu toppen, aber ich hatte ja zur Bearbeitung von Texten (Strings) die Text(String)funktionen empfohlen.

    Warum? Diese Stringfunktionen existieren seit Anbeginn aller Zeiten in jeder Programmiersprache. Es existieren nur eine Handvoll dieser Funktionen, und alle sind EINFACH!

    Wenn man ein Mal verstanden hat, dass ein String (das muss keinesfalls nur eine Textdatei sein!!!) nur eine lange Schlange aufeinanderfolgender Zeichen ist, dann behaupte ich jetzt mit dieser Handvoll Stringfunktionen den größten Teil aller Programmierprobleme lösen zu können. Beispiele für ein superschnelles Pixelsearch, also Suchen von Bytefolgen innerhalb eines Bildes, sind mit einer Zeile Code, bzw. mit einem simplen StringInStr() machbar. In AutoIt sowieso, da dessen Stringfunktionen als in C++ kompilierte und optimierte Funktionen aufgerufen werden, und nicht durch den Interpreter abgenudelt werden!

    Den Stringfunktionen sind die "Zeichen" egal, es können ALLE Zeichen bearbeitet werden. Also auch Binärdateien....

    Mit der Darstellung (bspw. als "Text" auf dem Bildschirm bzw. im Editor) sieht das anders aus, da funkt das Windows dazwischen, welches bspw. ein NUL-Zeichen (chr(0)) als Textende interpretiert....aber dazu :rtfm:

    Ein Beispiel, wie das Problem mit den extrem schnellen Stringfunktionen gelöst werden könnte:

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    Einmal editiert, zuletzt von Andy (22. September 2018 um 12:17)

  • Mal abgesehen davon, dass in jedem Schleifendurchlauf auf Ubound() geprüft werden muss, wird genau dieses Ubound() auch noch mehrfach innerhalb der Schleife verwendet. Das gehört VOR der Schleife einmal berechnet und dann in eine Variable.

    Wie Bitnugger bereits geschrieben hat sollte sich da nicht viel nehmen. Es wird einfach im Interpreter das Array gesucht und der Wert geholt, ich denke nicht, dass da eine Variable wirklich schneller ist.

    UBound berechnet die Größe ja nicht, es ruft nur die bereits Abgespeicherte ab.

  • Hallo alpines

    UBound berechnet die Größe ja nicht, es ruft nur die bereits Abgespeicherte ab.

    Ubound ist bei meinen Tests immer mindestens doppelt so langsam, wie ein in einer Variable gespeicherter Wert. Ich habe dir zugestimmt, dass es schon sehr große Arrays sein müssen, bevor das auffällt. Aber man sollte es wissen, damit man sich in so einem Fall nicht wundert.

    Grüße autoiter