Bestimmte Zeile (Zeilennummer) in Textdatei lesen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Sirius3 hat geschrieben: Mittwoch 30. September 2020, 19:47 Ich kann kaum glauben, dass das einzige was Du von den Dateien brauchst, eine einzige Zeile ist.
Wenn es Dir um Geschwindigkeit geht, nimm pandas.read_csv.
Okey versuche es morgen mal mit pandas. Natürlich brauche ich nicht nur eine einzige Zeile, sondern wie ich schon sagte jede Sekunde eine Zeile meist eine andere aus der selben Datei. Dabei kann es vorkommen, dass ich eine andere Datei brauche, welche ich dann bei Bedarf einlese um mit dieser zu arbeiten. Alle 30 Dateien bei Start einlesen halte ich etwas für unsinnig, da ich ja meist nur 3-4 brauche. Dies meine Überlegung wegen Speicherresourcen oder wäre das Nullproblemo? Habe mich mit Speichergrösse, -platz nie befasst. (eine Datei hat 118MB) Die werden in den Arbeitspeicher gelanden - oder? Und jenachdem wieviel Arbeitsspeicher ich halt habe, klappt es oder nicht?
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

drei bis vier Dateien im Speicher zu halten, ist heutzutage kein Problem mehr. Wenn die x und y-Werte so regelmäßig sind, dann braucht man die gar nicht. Damit brauchen alle 30 Dateien nicht mal ein Gigabyte.
Zudem speichert man die Daten binär, dann ist es völlig egal ob die Dateien als Memory-mapped Files in den Speicher passen, oder nicht.
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Die Dateien in der Grösse selbst zu optimieren habe ich auch schon gedacht.
Ich bekomme diese von extern und habe keinen Einfluss darauf wie sie erstellt werden. Darum wollte ich sie im ersten Schritt mal unverändert lassen.
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

Kannst du mal deine Aufgabe konkret beschreiben? Warum brauchst du von den vielen Dateien nur wenige und warum von den wenigen jede Sekunde nur einen Wert? Wer gibt dir vor, welchen Wert du brauchst?
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Zu detailliert möchte ich das nicht beschreiben. Die "Suchwerte" jede Sekunde liefert ein GNSS, in den Dateien stehen Topodaten.
...und fragt mich jetzt nicht, warum ich das nicht näher beschreiben will :-)
heyJo
User
Beiträge: 23
Registriert: Mittwoch 24. Januar 2018, 20:49
Wohnort: Köln

Hallo zusammen,

ich finde das Thema gerade spannend und habe mal vier Wege getestet, wie man eine bestimme Zeile in der Koordinaten-Datei auslesen kann und habe auch dazu die Zeiten gestoppt.

Das Ergebnis ist:
- Mit pandas dauert es am längsten.
- Die Suchdauer hängt wesentlich ab von
-- der Position, an der sich die gesuchte Zeilennummer befindet
-- ob pandas oder itertools verwendet werden

Bsp.:
Pandas vs. itertools
Datei: 8.000.000 Zeilen, letzte Zeile suchen: 12 sec vs. 1,08 sec
Datei: 8.000.000 Zeilen, 4.000.000. Zeile suchen: 12 sec vs. 0,55 sec

Vor dem Programmlauf habe ich jeweils den Kernel neu gestartet.

Natürlich kommt es immer darauf an, was man später mit den Daten machen will. Aber ein DataFrame mit pandas zu erzeugen, wenn man „nur“ eine Zeile braucht, scheint mir mit Kanonen auf Sparten zu schießen, oder?

Die in den Programmen angegebenen Zeiten (im Kommentar) beziehen sich auf die Suche der letzten Zeile in einer 4.000.000 Zeilen großen Datei.

Code: Alles auswählen

with open ("Koordinaten.txt","w") as outputfile:
    for i in range (0,4000000):
        print("2624000.25 1133999.75 1121.99",file=outputfile)

Code: Alles auswählen

from time import time
import pandas as pd
# pandas Version 0.23.4
t1 = time()

df = pd.read_csv('Koordinaten.txt',delimiter=" ",header=None)
print (df.iloc[[399999],:])
print (time()-t1)
# t=6.094815254211426

Code: Alles auswählen

from itertools import islice
from time import time

t1 = time()

with open ("Koordinaten.txt","r") as file:
    print (list(islice(file,3999998,3999999,)))
    #print (str(l))
file.close()
print (time()-t1)
#t=0.556002140045166

Code: Alles auswählen

from time import time

t1 = time()

with open ("Koordinaten.txt","r") as file:
    n=0
    for line in file:
        n +=1
        if n == 3999999:
            print(line,end="")
file.close()
print (time()-t1)
#t=0.8531761169433594

Code: Alles auswählen

from time import time
t1 = time()
with open("Koordinaten.txt","r") as file:
    for n, value in enumerate(file):
        if n == 3999999:
            print (value, end="")
file.close()
print (time()-t1)
#t=0.8580334186553955
Benutzeravatar
sparrow
User
Beiträge: 4245
Registriert: Freitag 17. April 2009, 10:28

@heyJo: Du vergleichst Äpfel mit Birnen. Das macht höchstens bei Obstsalat Sinn. Pandas tut viel mehr als nur Zeilenendenzeichen zu zählen und zu verwerfen, was vorher in der Datei steht. Pandas vorher zu sagen, was in den Spalten steht sollte schon gehörig Schub bringen, denn du lässt es ja hier seine Magie anwenden.
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

So schwierig ist die Beschreibung doch nun nicht: Du bekommst also jede Sekunde Koordinaten. Der erste Schritt ist es, aus den Koordinaten die Kachel zu berechnen, dann die Kachel zu laden und darin den Index zu berechnen, wo Du den z-Wert findest.

Um effektiv auf die Arrays zugreifen zu können, müssen sie im Arbeitsspeicher liegen. Da sich die Topologiedaten nicht sehr häufig ändern, macht es schon Sinn, die in ein passendes Format zu konvertieren, um effektiv damit arbeiten zu können.

Code: Alles auswählen

import numpy

TILE_FILENAME_CSV = "topologie_{:03d}.csv"
TILE_FILENAME_BIN = "topologie_{:03d}.bin"
        
def convert_tile(tile_number):
    with open(TILE_FILENAME_CSV.format(tile_number)) as lines:
        data = numpy.array([l.split() for l in lines], dtype=float)
    data.tofile(TILE_FILENAME_BIN.format(tile_number)
Dann kann man sie ohne weiteres komplett in den Speicher mappen:

Code: Alles auswählen

tiles = [
    numpy.memmap(TILE_FILENAME_BIN.format(tile_number), dtype=float).reshape(-1, 3)
    for tile_number in range(30)
]
Und beliebig darauf zugreifen:

Code: Alles auswählen

print(tiles[17, 3242])
heyJo
User
Beiträge: 23
Registriert: Mittwoch 24. Januar 2018, 20:49
Wohnort: Köln

@sparrow
Mir ist schon klar, dass pandas ein mächtiges Werkzeug ist. Wenn es allerdings "nur" darum geht eine einzelne Zeil/ Information zu extrahieren, finde ich es zu schwerfällig.
sparrow hat geschrieben: Donnerstag 1. Oktober 2020, 09:03 @heyJo: ... Pandas vorher zu sagen, was in den Spalten steht sollte schon gehörig Schub bringen, denn du lässt es ja hier seine Magie anwenden.
Mit meinem Wissen habe ich nur den Code hier hin bekommen; der ist aber noch langsamer ...

Code: Alles auswählen

from time import time
import pandas as pd
import numpy as np
# pandas Version 0.23.4
t1 = time()

df = pd.read_csv('Koordinaten.txt',delimiter=" ",header=None,names=("x","y","z"),converters={'x':np.float32,'y':np.float32,'z':np.float32})
print (df.iloc[[399999],:])
print (time()-t1)
# ohne np t=6.094815254211426
# mit np t=8.270103693008423
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich den die Antwort hier wären dr Aufgabe angemessene Indizes, wie sie in GIS Systemen wie zB PostGIS zum Einsatz kommen.
Benutzeravatar
MaximalMax
User
Beiträge: 18
Registriert: Sonntag 3. Mai 2020, 00:51

Die Lösung kann man ohne jegliche Imports lösen. Hier ist mein Code, der funktioniert.
Du öffnest erstmal die Datei mittels des With-Statements, dann wird abgefragt, welche Zeile du willst.
Dann wird das -1 gerechnet, da eine Liste immer bei 0 und nicht bei 1 anfängt.
Dann wird die For-Schleife genutzt um alle Werte, aller Zeilen zu speicher und die werden alle in die Liste eingetragen.
Dann gibt man in einem try die einzelne Zeile aus und wenn man eine zu große Zeilen-Nummer auswählt, dann wird im except einfach ein "Error" ausgegeben

Code: Alles auswählen

with open('test.txt') as file:
    zeile = int(input("Zeile: "))
    zeile = zeile - 1
    liste = []

    for line in file:
        liste += [line.strip()]
    
    try:
        print(liste[zeile])
    except:
        print("Diese Datei hat nicht so viele Zeilen")
Mit freundlichen, Max :D
PS: Viel Spaß beim weiteren Programmieren!
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Sirius3 hat geschrieben: Donnerstag 1. Oktober 2020, 09:17 So schwierig ist die Beschreibung doch nun nicht: Du bekommst also jede Sekunde Koordinaten. Der erste Schritt ist es, aus den Koordinaten die Kachel zu berechnen, dann die Kachel zu laden und darin den Index zu berechnen, wo Du den z-Wert findest.
Genau - da haben wir uns voll richtig verstanden :-)
Und beliebig darauf zugreifen:

Code: Alles auswählen

print(tiles[17, 3242])
Wenn ich das richtig verstehe, ist das aus der Dateinummer 17, die Zeilennummer 3242 - oder???
Aber müsste wohl so sein?

Code: Alles auswählen

print(tiles[17][3242])
Deins wirft die Fehlermeldung "TypeError: list indices must be integers or slices, not tuple"
Benutzeravatar
sparrow
User
Beiträge: 4245
Registriert: Freitag 17. April 2009, 10:28

@MaximalMax: Nackte Excepts sind keine gute Idee, weil sie _alle_ Ausnahmen schlucken, ohne eine Hinweis darauf zu geben, was schief gelaufen ist. Vertippst du dich innerhalb des try-Blocks, wirst du den Fehler ewig suchen, weil du nicht darauf hingewiesen wirst. Dein Programm geht dann immer davon aus, dass es nicht genug Zeilen waren. Deshalb gilt: Nur die Exceptions fangen, die man auch erwartet.
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

@MaximalMax: es ist sehr umständlich, um ein Element an eine Liste anzufügen, dafür extra eine neue Liste mit einem Element zu erzeugen. Statt dessen benutzt man append.
Du erwartest, wenn die Zeile nicht existiert einen IndexError.
Benutzeravatar
MaximalMax
User
Beiträge: 18
Registriert: Sonntag 3. Mai 2020, 00:51

MaximalMax hat geschrieben: Donnerstag 1. Oktober 2020, 12:44 Die Lösung kann man ohne jegliche Imports lösen. Hier ist mein Code, der funktioniert.
Du öffnest erstmal die Datei mittels des With-Statements, dann wird abgefragt, welche Zeile du willst.
Dann wird das -1 gerechnet, da eine Liste immer bei 0 und nicht bei 1 anfängt.
Dann wird die For-Schleife genutzt um alle Werte, aller Zeilen zu speicher und die werden alle in die Liste eingetragen.
Dann gibt man in einem try die einzelne Zeile aus und wenn man eine zu große Zeilen-Nummer auswählt, dann wird im except einfach ein "Error" ausgegeben

Code: Alles auswählen

with open('test.txt') as file:
    zeile = int(input("Zeile: "))
    zeile = zeile - 1
    liste = []

    for line in file:
        liste += [line.strip()]
    
    try:
        print(liste[zeile])
    except:
        print("Diese Datei hat nicht so viele Zeilen")
Mit freundlichen, Max :D
PS: Viel Spaß beim weiteren Programmieren!
Hoffe das hilft :D
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Hallo zusammen

Ich habe das Ganze nun umgesetzt - noch nicht ganz fertig (GPS funktioniert, Topodaten lesen funktioniert - nun nur noch beides verheiraten und testen.
Vielen Dank für eure Inputs und Ideen!
Ich habe mich schlussendlich für die Variante von Sirius3 entschieden viewtopic.php?f=1&t=49740&start=15#p373772
Alle 30 Binärdateien laden und eine der letzen Wert (Zeilen) suchen in weniger als 20ms, danach kanns nur noch schneller werden weil ja die Daten im Speicher sind.

Vielleicht hat noch jemand Lust mir die folgende Zeile zu erklären. Verstehe ich nicht. Dok nachgelesen --> trotzdem nicht verstanden. Ev. kanns jemand kurz und einfach erklären.
Was macht "reshape(-1, 3)" genau und warum -1, 3

Code: Alles auswählen

numpy.memmap(TILE_FILENAME_BIN.format(tile_number), dtype=float).reshape(-1, 3)
Danke nochmals!
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

Zuerst hast du nur einen großen Speicherbereich, mit vielen Zahlen. Du willst aber immer drei Zahlen als eine Zeile in der Matrix haben. Daher wird die Form (shape) der Matrix mit beliebig viele Zeilen (-1) und drei Spalten angegeben.
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Hallo

Meine Anwendung läuft. Aber.... Probleme mit Speicher:
Auf eine Latte-Panda mit WIN10 läuft die Anwendung mit 30 Tiles (diese haben ca. 2.7 GB) --> soweit so gut.
Nun möchte ich die selbe Anwendung auch auf dem Raspberry mit "Raspberry Pi OS" laufen lassen.
Ich habe einen 8GB RPi4. Bei 30 Tiles kommt unten stehende Fehlermeldung. Bei 15 klappt es noch.
Wenn ich im Taskmanager schaue, nachdem die 15 Tiles geladen sind, sind "nur" etwa 225MB belegt und etwa 7875 frei, also genügend Arbeitsspeicher frei ?!?!?!?!
Werden die Tiles nicht in diesen Arbeitsspeicher geladen?

Jemand eine Idee woran das liegt?

Code: Alles auswählen

self.tiles = [numpy.memmap(TILE_FILEPATH_BIN + self.bin_filenames[file_nr], dtype=float, mode='r').reshape(-1, 3) for file_nr in range(len(self.bin_filenames))]
  File "/usr/lib/python3/dist-packages/numpy/core/memmap.py", line 264, in __new__
      mm = mmap.mmap(fid.fileno(), bytes, access=acc, offset=start)
OSError: [Errno 12] Nicht genügend Hauptspeicher verfügbar
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Benutzt du 64 Bit Linux? Wenn nicht, ist der Speicher pro Anwendung auf 3GB limitiert.
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Bin mir gerade nicht sicher, schaue später.
Kann ich nicht davon ausgehen, wenn im Taskmanager 8GB angezeigt wird, diese auch wirklich genutzt werden???
Antworten