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.
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: 4231
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: 17797
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: 4231
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: 17797
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: 17797
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???
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mit 32 Bit kann man nur 4 GB adressieren. Punkt aus. Es gibt Mechanismen im Kernel, die den gesamtspeicher dann auf mehrere Anwendungen verteilen. Aber EINE Anwendung kann nie mehr als 3GB, weil in den gleichen addressraum auch noch der Kernel abgebildet werden muss.
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

@ _deets_
Super!!! vielen Dank für den Tip mit 64bit OS, hatte tatsächlich eine 32bit Version installiert - das offizielle "Raspberry Pi OS" (ex. Raspian) halt für Pi4 aber 32bit.
Für 64bit gibt es "nur" eine beta Version, die man schon etwas suchen muss.

Es funktioniert so - trotzdem die Frage: Habt ihr ein besseres 64-bit-OS für RaspberryPi4? Habe auch mal das Ubuntu MATE noch gesehen. Probiere ich vielleicht mal...
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Was heißt denn “besser”? Ist ein Auto mit mehr PS besser, oder eines mit mehr Zuladung, oder eines mit mehr Plätzen, oder eines das in die kleine Garage passt, oder das schwimmen kann?
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Dumme Frage - gescheite Antwort :lol:
Antworten