6 Stimmen

Analysieren einer Datei, Erstellen von Datensätzen und Durchführen von Manipulationen an Datensätzen, einschließlich Häufigkeit von Begriffen und Abstandsberechnungen

Ich bin Student in einem Einführungskurs in Perl und suche nach Vorschlägen und Feedback zu meinem Ansatz, ein kleines (aber kniffliges) Programm zu schreiben, das Daten über Atome analysiert. Mein Professor ermutigt zu Foren. Ich habe keine Erfahrung mit Perl-Subs oder -Modulen (einschließlich Bioperl), also beschränken Sie Ihre Antworten bitte auf ein angemessenes "Anfängerniveau", damit ich Ihre Vorschläge und/oder Ihren Code verstehen und daraus lernen kann (bitte beschränken Sie auch "Magic").

Die Anforderungen des Programms sind wie folgt:

  1. Liest eine Datei (mit Daten über Atome) aus der Befehlszeile und erstellt ein Array von Atomdatensätzen (ein Datensatz/Atom pro Zeilenumbruch). Für jeden Datensatz muss das Programm speichern:

    - Die Seriennummer des Atoms (Spalten 7 - 11)
    - Der aus drei Buchstaben bestehende Name der Aminosäure, zu der sie gehört (Spalten 18 - 20)
    - Die drei Koordinaten des Atoms (x, y, z) (Spalten 31 - 54)
    - Die ein- oder zweibuchstabige Elementbezeichnung des Atoms (z. B. C, O, N, Na) (Spalten 77-78)

  2. Aufforderung zur Eingabe eines von drei Befehlen: freq, length, density d (d ist eine Zahl):

    - freq - wie viele Atome jeder Art in der Datei enthalten sind (z. B. Stickstoff, Natrium usw. würden wie folgt angezeigt werden: N: 918 S: 23
    - Länge - Die Abstände zwischen den Koordinaten
    - Dichte d (wobei d eine Zahl ist) - das Programm fragt nach dem Namen einer Datei, in der die Berechnungen gespeichert werden sollen und die den Abstand zwischen diesem Atom und jedem anderen Atom enthält. Wenn dieser Abstand kleiner oder gleich der Zahl d ist, wird die Anzahl der Atome, die sich innerhalb dieses Abstands befinden, in der Datei erhöht, es sei denn, diese Anzahl ist Null. Die Ausgabe sieht dann etwa so aus:
    1: 5
    2: 3
    3: 6
    ... (sehr große Datei) und wird geschlossen, wenn sie fertig ist.

Ich bin auf der Suche nach Feedback zu dem, was ich im untenstehenden Code geschrieben habe (und schreiben muss). Ich freue mich besonders über jede Rückmeldung, wie ich beim Schreiben meiner Unterprogramme vorgehen soll. Ich habe unten ein Beispiel für die Eingabedaten angefügt.

Die Programmstruktur und Funktionsbeschreibungen, wie ich sie sehe:

$^W = 1; # turn on warnings
use strict; # behave!

my @fields;
my @recs;

while ( <DATA> ) {
 chomp;
 @fields = split(/\s+/);
 push @recs, makeRecord(@fields);
}

for (my $i = 0; $i < @recs; $i++) {
 printRec( $recs[$i] );
}
    my %command_table = (
 freq => \&freq,
 length => \&length,
 density => \&density,
 help => \&help, 
 quit => \&quit
 );

print "Enter a command: ";
while ( <STDIN> ) {
 chomp; 
 my @line = split( /\s+/);
 my $command = shift @line;
 if ($command !~ /^freq$|^density$|length|^help$|^quit$/ ) {
    print "Command must be: freq, length, density or quit\n";
    }
  else {
    $command_table{$command}->();
    }
 print "Enter a command: ";
 }

sub makeRecord 
    # Read the entire line and make records from the lines that contain the 
    # word ATOM or HETATM in the first column. Not sure how to do this:
{
 my %record = 
 (
 serialnumber => shift,
 aminoacid => shift,
 coordinates => shift,
 element  => [ @_ ]
 );
 return\%record;
}

sub freq
    # take an array of atom records, return a hash whose keys are 
    # distinct atom names and whose values are the frequences of
    # these atoms in the array.  

sub length
    # take an array of atom records and return the max distance 
    # between all pairs of atoms in that array. My instructor
    # advised this would be constructed as a for loop inside a for loop. 

sub density
    # take an array of atom records and a number d and will return a
    # hash whose keys are atom serial numbers and whose values are 
    # the number of atoms within that distance from the atom with that
    # serial number. 

sub help
{
    print "To use this program, type either\n",
          "freq\n",
          "length\n",
          "density followed by a number, d,\n",
          "help\n",
          "quit\n";
}

sub quit
{
 exit 0;
}

# truncating for testing purposes. Actual data is aprox. 100 columns 
# and starts with ATOM or HETATM.
__DATA__
ATOM   4743  CG  GLN A 704      19.896  32.017  54.717  1.00 66.44           C  
ATOM   4744  CD  GLN A 704      19.589  30.757  55.525  1.00 73.28           C  
ATOM   4745  OE1 GLN A 704      18.801  29.892  55.098  1.00 75.91           O

5voto

FMc Punkte 40706

Es sieht so aus, als ob Ihre Perl-Kenntnisse gute Fortschritte machen - Sie verwenden Referenzen und komplexe Datenstrukturen. Hier sind ein paar Tipps und allgemeine Ratschläge.

  • Aktivieren Sie Warnungen mit use warnings statt $^W = 1 . Ersteres ist selbstdokumentierend und hat den Vorteil, dass es sich um eine lokale Einstellung für den umschließenden Block handelt und nicht um eine globale Einstellung.

  • Verwenden Sie gut benannte Variablen, die helfen, das Verhalten des Programms zu dokumentieren, anstatt sich auf Perls spezielle $_ . Zum Beispiel:

    while (my $input_record = <DATA>){
    }
  • In Szenarien mit Benutzereingaben bietet eine Endlosschleife eine Möglichkeit, wiederholte Anweisungen wie "Geben Sie einen Befehl ein" zu vermeiden. Siehe unten.

  • Ihre Regex kann vereinfacht werden, um die Notwendigkeit wiederholter Anker zu vermeiden. Siehe unten.

  • In der Regel sind positive Tests leichter zu verstehen als negative Tests. Siehe die modifizierte if-else Struktur unten.

  • Schließen Sie jeden Programmteil in eine eigene Unterroutine ein. Dies ist eine gute allgemeine Praxis aus einer Reihe von Gründen, so würde ich nur beginnen, die Gewohnheit.

  • Eine damit zusammenhängende gute Praxis ist die Minimierung der Verwendung von globalen Variablen. Als Übung könnten Sie versuchen, das Programm so zu schreiben, dass es überhaupt keine globalen Variablen verwendet. Stattdessen würden alle benötigten Informationen zwischen den Unterprogrammen weitergereicht werden. Bei kleinen Programmen muss man nicht unbedingt rigide sein, was die Vermeidung globaler Variablen angeht, aber es ist keine schlechte Idee, sich das Ideal vor Augen zu halten.

  • Geben Sie Ihr length Unterroutine einen anderen Namen geben. Dieser Name wird bereits von der eingebauten length Funktion.

  • Bezüglich Ihrer Frage nach makeRecord Ein Ansatz besteht darin, das Problem der Filterung im Inneren zu ignorieren. makeRecord . Stattdessen, makeRecord könnte ein zusätzliches Hash-Feld enthalten, und die Filterlogik würde an anderer Stelle angesiedelt. Zum Beispiel:

    my $record = makeRecord(@fields);
    push @recs, $record if $record->{type} =~ /^(ATOM|HETATM)$/;

Eine Veranschaulichung einiger der oben genannten Punkte:

use strict;
use warnings;

run();

sub run {
    my $atom_data = load_atom_data();
    print_records($atom_data);
    interact_with_user($atom_data);
}

...

sub interact_with_user {
    my $atom_data = shift;
    my %command_table = (...);

    while (1){
        print "Enter a command: ";
        chomp(my $reply = <STDIN>);

        my ($command, @line) = split /\s+/, $reply;

        if ( $command =~ /^(freq|density|length|help|quit)$/ ) {
            # Run the command.
        }
        else {
            # Print usage message for user.
        }
    }
}

...

4voto

cjm Punkte 60581

Die Antwort von FM ist ziemlich gut. Ich werde nur ein paar zusätzliche Dinge erwähnen:

Sie haben bereits einen Hash mit den gültigen Befehlen (was eine gute Idee ist). Es besteht keine Notwendigkeit, diese Liste in einer Regex zu duplizieren. Ich würde etwas wie dieses tun:

if (my $routine = $command_table{$command}) {
  $routine->(@line);
} else {
  print "Command must be: freq, length, density or quit\n";
}

Beachten Sie, dass ich auch die @line in das Unterprogramm einfügen, da Sie es für den Dichtebefehl benötigen. Unterprogramme, die keine Argumente akzeptieren, können diese einfach ignorieren.

Sie können die Liste der gültigen Befehle für die Fehlermeldung auch mit folgendem Befehl erstellen keys %command_table aber ich überlasse es Ihnen, das zu üben.

Außerdem werden in der Beschreibung der Eingabedatei Spaltennummern erwähnt, was darauf schließen lässt, dass es sich um ein Format mit fester Breite handelt. Das wird besser geparst mit substr o unpack . Wenn ein Feld leer ist oder ein Leerzeichen enthält, wird es von Ihrem Split nicht korrekt analysiert. (Wenn Sie substr Beachten Sie, dass die Spalten mit 0 beginnen, während die erste Spalte oft mit 1 bezeichnet wird).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X