Как анализировать файл, создавать записи и выполнять манипуляции с записями, включая частоту вычислений и расстояния - PullRequest
6 голосов
/ 04 декабря 2010

Я учусь на вводном курсе Perl и ищу предложения и отзывы о моем подходе к написанию небольшой (но хитрой) программы, которая анализирует данные об атомах.Мой профессор поощряет форумы.Я не знаком с сабвуферами или модулями Perl (включая Bioperl), поэтому, пожалуйста, ограничьте ответы соответствующим «начальным уровнем», чтобы я мог понять и извлечь уроки из ваших предложений и / или кода (также ограничьте «Магию», пожалуйста).

Требования программы следующие:

  1. Считать файл (содержащий данные об атомах) из командной строки и создать массив записей атомов(одна запись / атом на новую строку).Для каждой записи программе необходимо хранить:

    • Серийный номер атома (столбцы 7 - 11)
    • Трехбуквенное название аминокислоты, к которой он принадлежит (столбец 18- 20)
    • Три координаты атома (x, y, z) (столбцы 31 - 54)
    • Имя элемента из одной или двух букв (например, C, O, N, Na) (столбцы77-78)

  2. Запрашивать одну из трех команд: частота, длина, плотность d (d - некоторое число):

    • частота- сколько атомов каждого типа содержится в файле (например, азот, натрий и т. д. будут отображаться так: N: 918 S: 23
    • длина - расстояния между координатами
    • плотность d (где dявляется числом) - программа запросит имя файла для сохранения вычислений и будет содержать расстояние между этим атомом и каждым другим атомом. Если это расстояние меньше или равно числу d, оно увеличивает счетчикчисло атомов, которые находятся в пределах этого расстояния, если только это число не равно нулюo файл. Вывод будет выглядеть примерно так:
    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 

Ответы [ 2 ]

5 голосов
/ 04 декабря 2010

Похоже, что ваши навыки Perl развиваются хорошо - с помощью ссылок и сложных структур данных.Вот несколько советов и общих советов.

  • Включите предупреждения с помощью use warnings вместо $^W = 1.Первый способ самодокументируется и имеет преимущество, заключающееся в том, что он является локальным по отношению к вмещающему блоку, а не глобальным параметром.

  • Используйте переменные с четкими именами, которые помогут задокументировать поведение программы, а нечем полагаться на специальный Perl $_.Например:

    while (my $input_record = <DATA>){
    }
    
  • В сценариях пользовательского ввода бесконечный цикл позволяет избежать повторяющихся инструкций, таких как «Введите команду».См. Ниже.

  • Ваше регулярное выражение может быть упрощено, чтобы избежать необходимости повторных привязок.См. Ниже.

  • Как правило, позитивные тесты легче понять, чем негативные тесты.См. Ниже измененную структуру if-else.

  • Заключите каждую часть программы в свою собственную подпрограмму.Это хорошая общая практика по множеству причин, поэтому я бы просто начал эту привычку.

  • Соответствующая хорошая практика - минимизировать использование глобальных переменных.В качестве упражнения вы можете попытаться написать программу так, чтобы она вообще не использовала глобальные переменные.Вместо этого любая необходимая информация будет передаваться между подпрограммами.С небольшими программами не обязательно быть строгими во избежании глобальных изменений, но не стоит забывать об идеале.

  • Дайте вашей подпрограмме length другоеназвание.Это имя уже используется встроенной функцией length.

  • Что касается вашего вопроса о makeRecord, один из подходов - игнорировать проблему фильтрации внутри 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.
        }
    }
}

...
4 голосов
/ 04 декабря 2010

FM ответ довольно хороший. Я просто упомяну пару дополнительных вещей:

У вас уже есть хэш с действительными командами (что является хорошей идеей). Нет необходимости дублировать этот список в регулярном выражении. Я бы сделал что-то вроде этого:

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

Обратите внимание, что я также передаю @line подпрограмме, потому что она понадобится вам для команды плотности. Подпрограммы, которые не принимают аргументы, могут просто игнорировать их.

Вы также можете создать список допустимых команд для сообщения об ошибке, используя keys %command_table, но я оставлю это в качестве упражнения для вас.

Другое дело, что в описании входного файла упоминаются номера столбцов, что говорит о том, что это формат с фиксированной шириной. Это лучше разбирать с substr или unpack. Если поле всегда пустое или содержит пробел, то ваше разбиение не будет анализировать его правильно. (Если вы используете substr, помните, что он нумерует столбцы, начиная с 0, когда люди часто отмечают первый столбец 1.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...