Einfaches Programm zum Zeilen Löschen

  • Hallo zusammen,

    Zunächst einmal muss ich sagen, dass ich absolut Null Ahnung habe, aber schnell eine Lösung brauche und daher mit dieser wie ich glaube recht simplen Anfrage um die Ecke komme.

    Es geht darum, dass ich aus Oberflächendaten des Mondes an meiner Uni einen 3D-Druck mittels Lasersintering erstellen soll.
    Diese Oberflächendaten liegen uns als txt Datei vor, welche zeilenweise Koordinaten enthalten. Jede der Zeilen entspricht also einem Punkt.
    Das Konvertieren der txt Datei in ein STL File und damit die Bearbeitung mittels CAD-Software funktioniert ohne Probleme. Das Problem liegt allerdings in der Größe der txt Datei.
    Diese ist 430MB groß und durch die Konvertierung in andere Files wird das ganze nochmals deutlich größer, wodurch kein PC an der Uni letztlich noch die Bearbeitung mittels CAD-Software gestemmt bekommt.

    Was ich also benötige ist ein einfaches Programm, dass mir (ohne dass ich die txt Datei öffnen muss) zB die Zeilen 2 bis 100, 102 bis 200 etc löscht und mir somit die txt Datei einfach verkleinert. Am besten wäre es natürlich wenn ich die Anzahl der zu löschenden Zeilen selber variieren kann um somit einen guten Kompromiss zwischen Genauigkeit und Dateigröße zu finden.

    Da ich so wenig Ahnung habe, dass ich nichtmal einschätzen kann wie aufwendig das ganze ist, würde ich euch bitten mir einfach zu sagen, was ihr dafür haben wollt.

    Zeitraum: eine Woche, ansonsten je schneller desto besser!

  • Hier mal eine simple Umsetzung der vorgeschlagenen Lösung:

    [autoit]


    $inputFile = @ScriptDir & "\bigmoon.txt"
    $outputFile = @ScriptDir & "\littlemoon.txt"

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

    ; create dummy input data
    $random = Random(10000,100000,1)
    $sData = ""
    For $i = 1 To $random
    $sData &= "Line " & $i & @tab & "blahblah...." & @CRLF
    Next
    FileWrite($inputFile,$sData)
    ; -----------------------------------------------------------

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

    $hInFile = FileOpen($inputFile,0)
    $hOutFile = FileOpen($outputFile,1)
    $interval = 100

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

    $i = 1
    while 1
    $sLine = FileReadLine($hInFile,$i)
    if @error = -1 Then ExitLoop
    FileWriteLine($hOutFile,$sLine)
    $i += $interval
    WEnd

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

    FileClose($hInFile)
    FileClose($hOutFile)

    [/autoit]
    • Offizieller Beitrag

    Aufgrund der Dateigröße, kann man die Datei ja nicht einlesen und in ein Array splitten. Also bleibt nur das zeilenweise lesen und dann selektieren zum neu schreiben.
    Hierbei ist es sinnvoll die Daten zu sammeln und in 50 MB-Blöcken zu schreiben. Eine Datei mit 600 Zeichen je Zeile kommt mal locker auf eine halbe Million Zeilen bei Dateigröße ~500 MB. Da ist es natürlich Quatsch jede Zeile einzeln in die neue Datei zu schreiben. Dann dauert das Erstellen gut 20 min, so sind es rund 20 sec. ;)

    misterspeed: Wenn jede Zeile gelesen wird, darfst du keinesfalls mit einem Zeilenzähler die gewünschte Zeile angeben. Dann wird jedesmal vom Anfang bis zu dieser Zeile gelesen. Gibst du nichts ein, gilt: Next Line, ohne dass das bisherige nochmal eingelesen wird. Der Pointer steht ja bereits am Ende der zuletzt gelesenen Zeile.

    [autoit]

    $sPath = @ScriptDir & '\TestBigFile.txt' ; == Datei zum Einlesen
    $sPathOut = @ScriptDir & '\TestBigFileOut.txt' ; == Datei für Ausgabe

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

    $iLineDeleteFirst = 100 ; == erste zu löschende Zeile
    $iLineDeleteLast = 200 ; == letzte zu löschende Zeile

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

    $fH = FileOpen($sPath)
    $fHOut = FileOpen($sPathOut, 1)

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

    $sWrite = ''
    $iLine = 0
    While True
    $iLine += 1
    $sLine = FileReadLine($fH)
    If @error Then
    If $sWrite <> '' Then FileWrite($fHOut, $sWrite)
    ExitLoop
    EndIf
    If $iLine < $iLineDeleteFirst Or $iLine > $iLineDeleteLast Then
    $sWrite &= $sLine & @CRLF
    If StringLen($sWrite) > 50*1024*1024 Then ; immer 50 MB sammeln zum Schreiben, dauert sonst zu lange
    FileWrite($fHOut, $sWrite)
    $sWrite = ''
    EndIf
    EndIf
    WEnd
    FileClose($fH)
    FileClose($fHOut)

    [/autoit]
  • Quick and Dirty - und zeilenweise, da ich denke die Datei ist zu groß zum einlesen in ein Array

    3 Parameter
    1. Parameter = Pfad zur Datei
    2. Parameter = Intervall ab dem du eine gewisse Zahl an Zeilen löschen magst
    3. Parameter = Anzahl der zeilen die nach dem Intervall gelöscht werden sollen

    [autoit]

    #include <File.au3>

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

    Local $sPathFile = $CmdLine[1]
    Local $iDelInterval = $CmdLine[2]
    Local $iDelRows = $CmdLine[3]
    Local $sLine = ''
    Local $x = 0
    Local $y = 0

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

    Local $hFile = FileOpen($sPathFile, 0); Check if file opened for reading OK
    If $hFile = -1 Then
    MsgBox(0, "Error1", "Unable to open file.")
    Exit
    EndIf

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

    Local $hFileEdited = FileOpen($sPathFile & ".edited", 2)
    If $hFileEdited = -1 Then
    MsgBox(0, "Error2", "Unable to open file.")
    Exit
    EndIf

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

    ; Read in lines of text until the EOF is reached
    While 1
    $x += 1

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

    $sLine = FileReadLine($hFile)
    If @error = -1 Then ExitLoop

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

    If $x < $iDelInterval Then
    FileWriteLine($hFileEdited, $sLine)
    Else
    $y += 1
    If $y >= $iDelRows Then
    $x = 0
    $y = 0
    EndIf
    EndIf
    WEnd

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

    FileClose($hFile)
    FileClose($hFileEdited)

    [/autoit]

    Beispiel Datei: tmp.txt
    Output: tmp.txt.edited (Hier umbenannt in tmp_edited.txt da das hochladen sonst nicht möglich ist)
    (Siehe Anhang)

  • misterspeed: Wenn jede Zeile gelesen wird, darfst du keinesfalls mit einem Zeilenzähler die gewünschte Zeile angeben. Dann wird jedesmal vom Anfang bis zu dieser Zeile gelesen. Gibst du nichts ein, gilt: Next Line, ohne dass das bisherige nochmal eingelesen wird. Der Pointer steht ja bereits am Ende der zuletzt gelesenen Zeile.

    Öhm ja, hilfe lesen soll helfen. Danke für den Hinweis, war bei meiner mikrigen Testdatei so nicht erkennbar.

    Hier mal eine gefixte Variante mit 100% Schreibcache, da wir bei einer 400mb Datei die um den Faktor 100 reduziert werden soll so wenig Ergebnisdaten haben, dass es kein Problem sein sollte diese komplett im RAM vorzuhalten und in einem rutsch zu schreiben. Bei mehr Daten macht ein auf XXX MB begrenzter cache aber sicherlich Sinn.

    [autoit]

    $inputFile = @ScriptDir & "\bigmoon.txt"
    $outputFile = @ScriptDir & "\littlemoon.txt"

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

    $hInFile = FileOpen($inputFile,0)
    $hOutFile = FileOpen($outputFile,1)
    $interval = 100
    $sOut = ""

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

    while 1
    $sLine = FileReadLine($hInFile)
    if @error = -1 Then ExitLoop
    $sOut &= $sLine & @CRLF

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

    For $i=1 to $interval-1
    FileReadLine($hInFile)
    if @error = -1 Then ExitLoop 2
    Next
    WEnd

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

    FileWrite($hOutFile,$sOut)

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

    FileClose($hInFile)
    FileClose($hOutFile)

    [/autoit]
    • Offizieller Beitrag

    dass es kein Problem sein sollte diese komplett im RAM vorzuhalten und in einem rutsch zu schreiben


    Nun, die Speicherverwaltung in Windows ist ja hervorragend gelöst und somit hatte ich beim Versuch die Daten kpl. zu cashen (800 MB - Datei) ein Memory Leak. :rofl: (ich habe ja auch nur 8 GB RAM verbaut :P)

  • Was sind denn schon 8Gb RAM ^^ Ich hab ein Windows Leistungs Indes von 7,6 Max 7,9

    Intel I7 Generation 2 8x 3,6 GH Taktbar auf 4,2 16GB GDDR5 RAM
    Saphir Graka und 6 Bildschirme xD

    4 RAID 0 SSD Platten und 4 TB Sata3 Extern

    Dazu noch 2 Linux Dedis mit jeweils 64Gb RAM und 2 x 3TB SATA3 RAID0

    Hoste VServer - Gameserver - Webspace - Voice Server zz LAufen Insgesamt 86 Server davon sind 43 HL/Source Engine und 14 Minecraft server wer sich auskennt die verbrauchen RAM und bin bei einer Gesamtserverlast von Gerademal 52%

    1Gbps Pro Server und Ip Anbindung Igesamt 10 Ips 10Gbps Anbindung

    Desweiteren hab ich noch 19 Bulletproof Offshore VPS mit jeweils 10Gbps anbindung wofür? Booter Hab ein DDoS Output von 800Gbps ( Shells + 19 Server )

    Ich weiß ist Off Topic aber mir war Langweilig und sein Problem wurde mehrmals gelöst^^

    Greetings Ich ^^

  • Also erstmal vielen Dank für die schnellen Hilfen. Hat wunderbar funktioniert!

    Da aber nach dem Lösen eines Problems meist das nächste zum Vorschein kommt habe ich nun schon die nächste Anfrage.

    Ich habe die Punktwolken in folgender Form vorliegen:
    -15392104 -21905404 171668908
    -15378372 -21292259 171748276
    -15356410 -20667213 171728997
    -15330879 -20037372 171662887
    -15318077 -19423875 171732509
    -15305227 -18809587 171794952
    -15292330 -18194569 171850209
    -15279255 -17578730 171896789

    Nun bräuchte ich ein Programm, das mir einfach nach den letzten 4 Ziffern ein Komma setzt und somit die Gesamtdimension einfach herunterskaliert.

    Das ganze soll dann in folgender Form ausgegeben werden:
    -1539,2104 -2190,5404 17166,8908
    -1537,8372 -2129,2259 17174,8276
    -1535,6410 -2066,7213 17172,8997
    -1533,0879 -2003,7372 17166,2887
    -1531,8077 -1942,3875 17173,2509
    -1530,5227 -1880,9587 17179,4952
    -1529,2330 -1819,4569 17185,0209
    -1527,9255 -1757,8730 17189,6789

    Weiter wäre es cool wenn zu jedem Punkt eine darauf folgende Zeile erstellt wird in der die gleichen Koordinaten gegeben sind, nur dass die Z-Koordinate (die dritte Zahl) durch 0 ersetzt wird.

    Randbedingungen sind die selben wie zuvor.

    Grüße

    Einmal editiert, zuletzt von Carcharoth (5. Mai 2014 um 13:47)

  • Als Student sollte man doch eigentlich in der Lage sein sich derart simple Dinge selbst in kürzester Zeit anzueignen, meinst du nicht? Wer macht den Rest deines Projektes? Datenaufbereitung und NC-Programmierung sind nunmal mit die Hauptaufgaben wenn es um 3D Druck geht. Du tust dir also nicht unbedingt einen gefallen damit wenn du das andere machen lässt.

    Hier als kleiner Denkanstoss die benötigten Funktionen um das gewünschte umzusetzen:

    [autoit]


    _filereadtoarray() ; sofern du inzwischen keine abartigen 400mb mehr hast, andernfalls siehe Lösungen oben zum Zeilenweise einlesen
    stringsplit
    stringright
    stringreplace
    _filewritefromarray()

    [/autoit]

    Ergänzend noch ne FOR Schleife zum Durchgehen des Arrays und das wars. Kann man auch als absoluter Anfänger innerhalb kürzester Zeit hinbekommen, insbesondere wenn man ein wenig was auf dem Kasten hat (was ich bei nem Student einfach mal vorraussetze).

  • Studenten sind faul :P

    Spoiler anzeigen
    [autoit]

    Global $hFileSource, $hFileDestination, $sFileSourceReadChar, $sFileSourceReadLine, $sNewLine
    Global Const $FILE_PATH_SOURCE = "C:\src\foo.bar"
    Global Const $FILE_PATH_DESTINATION = "C:\dst\foo.bar"

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

    $hFileSource = FileOpen($FILE_PATH_SOURCE)
    $hFileDestination = FileOpen($FILE_PATH_DESTINATION, 2)

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

    If Not $hFileSource Or Not $hFileDestination Then Exit MsgBox(16, "", "FileOpen Error")

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

    Do
    $sFileSourceReadChar = FileRead($hFileSource, 1)
    If @error Then ExitLoop

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

    If $sFileSourceReadChar = @LF Then
    $sNewLine = StringRegExpReplace($sFileSourceReadLine, "(\d{4})\b", ",\1") & @CRLF
    FileWrite($hFileDestination, $sNewLine)

    $sNewLine = StringRegExpReplace($sNewLine, "(\d+,\d+ \d+,\d+ )\d+,\d+", "\{1}0") & @CRLF
    FileWrite($hFileDestination, $sNewLine)

    $sFileSourceReadLine = ""
    Else
    $sFileSourceReadLine &= $sFileSourceReadChar
    EndIf

    Until False

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

    FileClose($hFileDestination)
    FileClose($hFileSource)

    [/autoit]

    Keine Garantie auf Lauffähigkeit, ist auf meinem Tablet entstanden :D

  • Ich wills ja lernen obwohl ich mich von dem "Studenten = faul" nicht unbedingt komplett freisprechen kann.

    Die Lösung von SEuBo funktioniert bei mir auch leider gar nicht.

    Nun hab ich aber soweit was hinbekommen, scheiter allerdings an einer kleinen Sache. Und zwar sind ja pro Zeile immer 3 Koordinaten gegeben und mit StringSplit bekomme ich sie auch aufgeteilt und klappen tut auch alles. Allerdings sind die Koordinaten jedesmal wenn kein Minus vor der Zahl steht nicht mit einem sondern mit zwei Leerzeichen getrennt. Das heißt manchmal habe ich als Trennzeichen ein Leerzeichen und manchmal zwei. Wie bekomme ich es nun hin, dass sowohl ein als auch zwei Leerzeichen als Trennzeichen erkennt werden?

    Danke schonmal im Voraus!

    Edit: Habs selbst geschafft :rock:
    Trotzdem vielen Dank an alle.

    Einmal editiert, zuletzt von Carcharoth (7. Mai 2014 um 11:26)

  • Edit: Habs selbst geschafft :rock:

    Glückwunsch. Wäre aber nett wenn du uns deine Lösung noch posten würdest, denn so haben andere hier im Forum zukünftig auch noch etwas von deinem Thread und wir können dir ggf. Schwachstellen an deine Lösung nennen oder Verbesserungsvorschläge einbringen.

  • Sehr gerne, auch wenn das wahrscheinlich jetzt nicht absolut korrekt ist, aber hauptsache ich habe eine Lösung.

    Also mein Ansatz war zunächst einmal natürlich der falsche. Anstatt 4 Ziffern vor dem Ende ein Komma zu setzen, ist es natürlich einfacher jede Zahl durch 10000 zu dividieren.
    Habe das Skript von YaeroxXO verwendet und dieses dann ein wenig umgeformt.

    [autoit]

    #include <File.au3>

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

    Local $sPathFile = "Dateipfad"
    Local $sLine = ''
    Local $x = 0
    Local $y = 0

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

    Local $hFile = FileOpen($sPathFile, 0); Check if file opened for reading OK
    If $hFile = -1 Then
    MsgBox(0, "Error1", "Unable to open file.")
    Exit
    EndIf

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

    Local $hFileEdited = FileOpen($sPathFile & ".edited", 2)
    If $hFileEdited = -1 Then
    MsgBox(0, "Error2", "Unable to open file.")
    Exit
    EndIf

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

    ; Read in lines of text until the EOF is reached
    While 1
    $x += 1

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

    $sLine = FileReadLine($hFile)
    If @error = -1 Then ExitLoop

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

    $sZeile = StringStripWS($sLine,4)
    $sZeile1 = StringStripWS($sZeile,1)
    $array = StringSplit ($sZeile1," ")
    $Zahl = $array[1] / 10000
    $Zahl1 = $array[2] / 10000
    $Zahl2 = $array[3] / 10000

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

    FileWriteLine($hFileEdited, $Zahl&" "&$Zahl1&" "&$Zahl2&@CRLF&$Zahl&" "&$Zahl1&" 0")
    WEnd

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

    FileClose($hFile)
    FileClose($hFileEdited)

    [/autoit]

    Da sind jetzt bestimmt noch ein paar Sachen unnötig bzw. man hätte es auch kürzer fassen können. Aber für das erste Mal bin ich doch sehr zufrieden :rolleyes:

    Das StringStripWS habe ich jeweils eingefügt um für den Fall, dass ich zwei Leerstellen zwischen zwei Zahlen hatte eine davon zu entfernen bzw. für den Fall, dass die erste Zahl mal positiv war und somit auch dort ein Leerzeichen und kein Minus stand. So dass auch das Leerzeichen dann jeweils wegfiel.

  • Kann man so lassen ;)

    Zwei Anmerkungen hätte ich aber noch:


    • Autoit verwendet den Datentyp Variant. Du kannst dich also nicht unbedingt darauf verlassen, dass deine Strings die du in den Variablen hast auch wirklich immer als Zahl behandelt werden wenn du deine Division durchführst. Um die korrekte Interpretation zu erzwingen sollte man die Strings zur Sicherheit vorher "casten"
      [autoit]


      $Zahl = number($array[1]) / 10000
      $Zahl1 = number($array[2]) / 10000
      $Zahl2 = number($array[3]) / 10000

      [/autoit]
    • Aus Performancesicht wäre es vermutlich besser alle Teilergebnisse zu verketten und erst am Ende am Stück in die Zieldatei zu schreiben. Das ist zumindestens bei kleineren Datenmengen zu empfehlen. Bei mehreren 100 MB könnte dir aber der RAM ausgehen, daher sollte man in diesem Fall die Daten alle xx MB "schubweise" schreiben.
      [autoit]


      ;...
      local $ergebnis = ""
      while 1
      ;...
      ;FileWriteLine($hFileEdited, $Zahl&" "&$Zahl1&" "&$Zahl2&@CRLF&$Zahl&" "&$Zahl1&" 0")
      $ergebnis &= $Zahl&" "&$Zahl1&" "&$Zahl2&@CRLF&$Zahl&" "&$Zahl1&" 0" & @CRLF
      WEnd

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

      FileWrite($hFileEdited, stringreplace($ergebnis,@CRLF,"",-1)) ; hier wird der letzte Zeilenumbruch noch vor dem Schreiben entfernt...

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

      FileClose($hFile)
      FileClose($hFileEdited)

      [/autoit]
  • Aus Performancesicht wäre es vermutlich besser alle Teilergebnisse zu verketten und erst am Ende am Stück in die Zieldatei zu schreiben. Das ist zumindestens bei kleineren Datenmengen zu empfehlen. Bei mehreren 100 MB könnte dir aber der RAM ausgehen, daher sollte man in diesem Fall die Daten alle xx MB "schubweise" schreiben.


    Siehe Post von BugFix ;)