6

私はPerlのイントロクラスの学生で、原子に関するデータを分析する小さな(しかしトリッキーな)プログラムを作成するための私のアプローチに関する提案とフィードバックを探しています。私の教授はフォーラムを奨励しています。私はPerlのサブまたはモジュール(Bioperlを含む)に精通していないので、あなたの提案やコードから理解して学ぶことができるように、適切な「初心者レベル」に応答を制限してください(「マジック」も制限してください)。

プログラムの要件は次のとおりです。

  1. コマンドラインからファイル(Atomに関するデータを含む)を読み取り、アトムレコードの配列を作成します(改行ごとに1つのレコード/アトム)。各レコードについて、プログラムは以下を保存する必要があります。

    •原子のシリアル番号(列7〜11 )•原子
    が属するアミノ酸の3文字の名前(列18〜20)
    •原子の3つの座標(x、y、z)(列31〜54)
    •原子の1文字または2文字の要素名(例:C、O、N、Na)(列77-78)

  2. 次の3つのコマンドのいずれかを要求します:freq、length、density d(dはいくつかの数値です):

    •freq-ファイル内の各タイプの原子の数(例:窒素、ナトリウムなどは次のように表示されます:N:918 S:23
    •長さ-座標間の距離
    •密度d(dは数値) -プログラムは、計算を保存するファイルの名前を要求し、その原子と他のすべての原子との間の距離を含みます。その距離が数値d以下の場合、原子の数のカウントをインクリメントします。そのカウントがファイルにゼロでない限り、その距離内にあります。出力は次のようになります:
    1:5
    2:3
    3:6
    ...(非常に大きなファイル)そして終了すると閉じます。

以下のコードで私が書いた(そして書く必要がある)ものについてのフィードバックを探しています。私は特に私の潜水艦を書くことにアプローチする方法についてのフィードバックに感謝します。下部にサンプル入力データを含めました。

私が見ているプログラムの構造と関数の説明:

$^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 
4

2 に答える 2

5

参照と複雑なデータ構造を使用して、Perlのスキルが順調に進んでいるようです。ここにいくつかのヒントと一般的なアドバイスがあります。

  • use warningsではなくで警告を有効にします$^W = 1。前者は自己文書化であり、グローバル設定ではなく、囲んでいるブロックに対してローカルであるという利点があります。

  • Perlの特別なに頼るのではなく、プログラムの動作を文書化するのに役立つ名前の付いた変数を使用してください$_。例えば:

    while (my $input_record = <DATA>){
    }
    
  • ユーザー入力のシナリオでは、無限ループは「コマンドの入力」のような繰り返しの指示を回避する方法を提供します。下記参照。

  • 繰り返しアンカーを使用する必要がないように、正規表現を簡略化できます。下記参照。

  • 原則として、肯定的なテストは否定的なテストよりも理解しやすいです。以下の変更されたif-else構造を参照してください。

  • プログラムの各部分を独自のサブルーチンで囲みます。これは多くの理由から良い一般的な習慣なので、私は習慣を始めるだけです。

  • 関連するグッドプラクティスは、グローバル変数の使用を最小限に抑えることです。演習として、グローバル変数をまったく使用しないようにプログラムを作成してみることができます。代わりに、必要な情報がサブルーチン間で受け渡されます。小さなプログラムでは、グローバルの回避について必ずしも厳密である必要はありませんが、理想を念頭に置くことは悪い考えではありません。

  • サブルーチンlengthに別の名前を付けます。その名前は、組み込みlength関数によってすでに使用されています。

  • に関する質問に関してmakeRecord、1つのアプローチは、内部のフィルタリングの問題を無視することmakeRecordです。代わりにmakeRecord、追加のハッシュフィールドを含めることができ、フィルタリングロジックは別の場所にあります。例えば:

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

上記のいくつかのポイントの図:

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.
        }
    }
}

...
于 2010-12-04T01:00:16.543 に答える
4

FMの答えはかなり良いです。いくつかの追加事項について説明します。

有効なコマンドを含むハッシュがすでにあります(これは良い考えです)。そのリストを正規表現で複製する必要はありません。私はこのようなことをします:

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

@line密度コマンドで必要になるため、サブルーチンにも渡しています。引数を取らないサブルーチンは、それらを無視することができます。

を使用してエラーメッセージの有効なコマンドのリストを生成することもできますがkeys %command_table、これは演習として残しておきます。

もう1つのことは、入力ファイルの説明に列番号が記載されていることです。これは、固定幅形式であることを示しています。substrまたはで解析した方がよいでしょうunpack。フィールドが空白またはスペースを含む場合、分割はそれを正しく解析しません。(を使用する場合はsubstr、最初の列に1のラベルを付けることが多いため、0から始まる列に番号を付けることに注意してください。)

于 2010-12-04T01:27:56.127 に答える