Zahlenwerte aus Liste vergleichen

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.
Antworten
Cornelius
User
Beiträge: 1
Registriert: Dienstag 23. Juli 2013, 08:42

Hallo!

Ich bin ein absoluter Python Newbie und habe im Endeffekt nur das Skript eines Kollegen als Vorlage genommen.
Ich habe folgendes Problem:

Ich habe txt files die ungefaehr so aufgebaut sind:
----- Begin chunk -----

blablabla

Image filename: /path/filename

blablabla

----- Begin Cube
Cell parameters 12.89394 12.88293 12.90142 nm, 90.00000 90.00000 90.00000 deg
astar = +0.0012237 -0.0648252 +0.0437038 nm^-1
bstar = -0.0024479 -0.0436801 -0.0666936 nm^-1
cstar = +0.1851175 -0.0054972 -0.0088617 nm^-1

blablabla

----- End Cube
----- End Chunk -----
Im Endeffekt ist eine lange Liste dieser 'Chunks' und 'Cubes' am Stueck aufgereiht.

Nun will ich im Endeffekt 'Cubes' mit aehnlichen Groessen clustern - d.h. die Zahlen 'astar bstar cstar' aus einem chunk lesen und mit allen anderen chunks vergleichen.
D.h. ich will zum Schluss eine Liste mit den 'Image filenames' und dahinter eine Cluster Nummer, wonach sortiert wird.

Dazu habe ich bisher folgendes script, wobei ich lediglich astar bstar und cstar einlese:

Code: Alles auswählen

#!/usr/bin/python
FileName1='cubes.stream'

##### Opening files #####
import os
from numpy import *
print 'ok'

g=open('%s'%FileName1,'r')
line1=g.readlines()
g.close()

ax=[]; ay=[]; az=[]; bx=[]; by=[]; bz=[]; cx=[]; cy=[]; cz=[]; 
index_list=[]; filename=[];

for i in range(0,len(line1)):
    if line1[i]=='Image filename':
        filename+=[float(line1[i].split()[3])]
    if line1[i][0:5]=='Cell ':
        ax+=[float(line1[i+1].split()[2])]
        ay+=[float(line1[i+1].split()[3])]
        az+=[float(line1[i+1].split()[4])]
        bx+=[float(line1[i+2].split()[2])]
        by+=[float(line1[i+2].split()[3])]
        bz+=[float(line1[i+2].split()[4])]
        cx+=[float(line1[i+3].split()[2])]
        cy+=[float(line1[i+3].split()[3])]
        cz+=[float(line1[i+3].split()[4])]

##### Writing results #####
g=open('%s_Results.txt'%FileName1[:-7],'w')

g.write('ax: %f', %ax)
g.write('ay: %f', %ay)
g.write('az: %f', %az)

g.close

Bisher spuckt er mir nur die Zahlenwerte aus, ich will aber nun von chunk zu chunk vergleichen. Auch ist die Zeile 'filename+=[float(line1.split()[3])]' falsch, da dort kein float hingehoert.

Ich weiss, ihr seid nicht dazu da meine Aufgaben zu loesen, ich haette nur gerne eine 'Starthilfe' in welche Richtung ich gehen sollte. Bzw. wie man am besten diese Zahlen mit Toleranz vergleicht.


Vielen Dank schonmal im Voraus


Cornelius
BlackJack

@Cornelius: Wenn der `float()`-Aufruf da nicht hingehört, dann lass ihn doch einfach weg.

Der Quelltext ist ziemlich „unpythonisch”. Man kann über Elemente einer Liste, oder sogar Zeilen eines zum lesen geöffneten Dateiobjekts direkt iterieren, ohne über den Umweg eines Index gehen zu müssen. Ein ``for i in range(len(sequence)):`` um dann mit `i` auf Elemente der `sequence` zuzugreifen ist in 99,9% der Fälle unnötig umständlich. Falls man den Index *zusätzlich* zum Element benötigt, gibt es die `enumerate()`-Funktion. Ist hier aber nicht notwendig wenn man sich an den Zeileninhalten orientiert und nicht relativ zu einer Zeile „addressiert”. Die Zeilen mit den Werten haben ja auch charakteristische Präfixe an denen man sie erkennen kann.

Dieses ``+= [irgendwas]`` ist eine sehr umständliche Art `append()` zu umschreiben. ``+=`` ist hier insgesamt ungewöhnlich, denn die `extend()`-Methode macht das gleiche, wird aber öfter benutzt.

Ich denke Du solltest erst einmal ein Grundlagentutorial durcharbeiten, bevor Du konkrete Probleme löst. In der Python-Dokumentation ist eines enthalten und für absolute Programmieranfänger wird Learn Python The Hard Way oft empfohlen.

Bei der Datenorganisation ist das Aufteilen von zusammengehörigen Daten auf viele Listen ungünstig. Daten die zusammen gehören sollte man auch in einem Objekt zusammen fassen. Dann muss man auch weniger leere Listen an Namen binden und nicht mehr als eine Anweisung in eine Zeile schreiben. Das ``;`` zum Trennen von Anweisungen wird in Programmen eher selten bis gar nicht verwendet. Was man hier eher machen würde ist die Daten von einem Abschnitt zu einem Objekt zusammen zu fassen. Einem Wörterbuch (`dict`) oder einem `collections.namedtuple` oder auch ein eigener Datentyp, also eine selbstgeschriebene Klasse. Dann hat man am Ende eine Liste mit diesen Objekten, die man sortieren kann.

Der Ausdruck ``'%s' % FileName1`` ist wenn `FileName1` eine Zeichenkette ist, unsinnig. Was denkst Du denn was der bewirkt?

Durchnummerieren von Namen ist ein „code smell”. Meistens ist es ein Zeichen, dass man die Werte eigentlich zusammen in eine geeignete Datenstruktur stecken möchte, oft eine Liste, hier ist es aber einfach nur sinnfrei. Vergib bessere Namen ohne die angehängte 1. Im Fall von `FileName1` wäre das zum Beispiel `filename` und bei `line1` wäre es `lines`, denn es ist ja nicht *eine* Zeile, sondern hinter dem Namen stecken *alle* Zeilen der Datei.

Das erste ``if`` in der Schleife wird nie ausgeführt, wenn die Dateien so aussehen wie im Beispiel. Da fehlt ein Doppelpunkt und das Zeilenende-Zeichen. Oder man arbeitet mit der `startswith()`-Methode auf Zeichenketten. Oder mit der `strip()`-Methode — dann braucht man nur noch den Doppelpunkt.

Auf ein und der selben Zeichenkette wiederholt `split()` Aufrufen um dann jeweils nur *ein* Element aus dem Ergebnis zu verwenden ist ineffizient.

Dateien die man öffnet, sollte man auch wieder schliessen. Entweder explizit mit `close()` wie Du das beim Lesen gemacht hast, oder man verwendet die ``with``-Anweisung, die in Ausnahmefällen sicherer ist. Beim Schreiben hast Du vergessen `close()` auch *aufzurufen*.

Der Dateiname für die Ergebnisdatei wird nicht besonders robust erzeugt. Diese magische -7 geht von einem bestimmten Wert als Dateinamen aus. Da sollte man besser mit `os.path.splitext()` arbeiten.

Bei der Ergebnisdatei versuchst Du Listen als Gleitkommazahlen mit `%f` zu formatieren. Das geht natürlich nicht, weil eine Liste keine Zahl ist. Zumal das sowieso kein Code sein kann, den Du tatsächlich mal ausprobiert hast, denn die `write()`-Methode nimmt keine *zwei* Argumente entgegen.
BlackJack

Mal so als Grungerüst, allerdings komplett ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import os
from collections import namedtuple

FILENAME_PREFIX = 'Image filename: '
STAR_PREFIXES = tuple(c + 'star = ' for c in 'abc')
LAST_PREFIX = STAR_PREFIXES[-1]


Point = namedtuple('Point', 'x y z')
Cube = namedtuple('Cube', 'filename ' + ' '.join(STAR_PREFIXES))


def load_cubes_stream(filename):
    # 
    # TODO Replace assertions with proper exceptions.
    # 
    with open(filename) as lines:
        cube = dict()
        for line in lines:
            if line.startswith(FILENAME_PREFIX):
                assert 'filename' not in cube
                cube['filename'] = line[len(FILENAME_PREFIX):].strip()
            elif line.startswith(STAR_PREFIXES):
                name, remainder = line.split('=', 1)
                name = name.strip()
                values = map(float, remainder.split()[:3])
                assert len(values) == 3
                assert name not in cube
                cube[name] = Point._make(values)
                if LAST_PREFIX.startswith(name):
                    yield Cube(**cube)
                    cube = dict()
        assert not cube, 'partial cube'


def get_cube_sort_key(cube):
    # 
    # Anstelle der `sum()`-Funktion müsste eine Art Ordnungszahl oder Gewicht
    # für den `cube` berechnet werden, nach dem sortiert werden kann.
    # 
    return (sum(cube.astar), cube.filename)


def save_cubes_as_json(filename, cubes, indent=None):
    with open(filename, 'w') as result_file:
        json.dump([c._asdict() for c in cubes], result_file, indent=indent)
    

def main():
    filename = 'cubes.stream'
    cubes = sorted(load_cubes_stream(filename), key=get_cube_sort_key)
    save_cubes_as_json(
        cubes, os.path.splitext(filename)[0] + '_results.json', 2
    )


if __name__ == '__main__':
    main()
Antworten