Подсчет элемента в подсчете вхождений - PullRequest
0 голосов
/ 29 августа 2018

Здесь я получил текстовый файл, содержащий некоторые тематические результаты. Как можно сделать таблицу подсчета, как показано ниже в Perl?

result.txt

Math Peter pass
English Peter pass
Music Peter fail
Science Peter fail
Art Mary fail
Music Mary fail
English Mary fail
Math Bob pass
English Bob fail
Art Bob pass
Music Bob fail
English Mike pass
Science Mike pass

выход

name    pass    fail
Peter   2   2
Mary    0   3
Bob 2   2
Mike    2   0

Я уже пробовал это и могу успешно напечатать дамп в ускоренной форме

#!/usr/bin/perl

use strict;

use Data::Dumper;

my $CurrentPath = "/tmp";

open(FILE, "/tmp/result.txt") or die("Cannot open file result.txt for reading: $!");
my @results = <FILE>;
s{^\s+|\s+$}{}g foreach @results;
close FILE;

my @data_split = ();

foreach my $result ( @results ) {
    push @data_split, [ split /\s+/, $result ];
}

print Dumper \@data_split;

1;

выход

$VAR1 = [
          [
            'Math',
            'Peter',
            'pass'
          ],
          [
            'Eng',
            'Peter',
            'pass'
          ],
          ...............

Ответы [ 2 ]

0 голосов
/ 29 августа 2018

Это очень простое решение, которое не проверяет входные данные. Он инициализирует каждое значение хеша { pass => 0, fail => 0 }, чтобы не было "пропущенных" значений, которые должны быть установлены по умолчанию

Обратите внимание, что хэши Perl неупорядочены , поэтому порядок вывода также не определен. Если вам нужно что-то конкретное, тогда вы должны сказать:

use strict;
use warnings 'all';
use feature 'say';

open my $fh, '<', 'results.txt' or die $!;

my %grades;

while ( <$fh> ) {

    my ($class, $name, $grade) = split;

    $grades{$name} //= { pass => 0, fail => 0 };
    ++$grades{$name}{$grade}
}

say "name\tpass\tfail";

for ( keys %grades ) {
    say join "\t", $_, @{ $grades{$_} }{qw/ pass fail /};
}

выход

name    pass    fail
Mary    0   3
Bob 2   2
Mike    2   0
Peter   2   2
0 голосов
/ 29 августа 2018

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

Он может идти по этим направлениям

use warnings;
use strict;
use feature 'say';   
use Data::Dump qw(dd);

my $file = shift @ARGV;
die "Usage: $0 filename\n" if not $file;

open my $fh, '<', $file or die "Can't open $file: $!";

my %results;

while (<$fh>) {
    next if not /\S/;  # skip empty lines

    my ($subj, $name, $grade) = split;

    if (not $subj or not $name or not defined $grade) {
       warn "Incomplete data, line: $_";
       next;
    }

    if ($grade eq 'pass') {
        $results{$name}->{pass}++;
    }  
    elsif ($grade eq 'fail') {
        $results{$name}->{fail}++;
    }  
    else { 
        warn "Unknown grade format for $name in $subj: $grade";
        next;
    }
}
dd \%results;

Имена без единого вхождения конкретного балла остаются без хеш-значения для этого балла. Если вам нужны эти записи, то постобработка %results для их добавления, например

foreach my $name (keys %results) {
    $results{$name}->{pass} = 0 if not exists $results{$name}->{pass};
    $results{$name}->{fail} = 0 if not exists $results{$name}->{fail};
}

Или добавьте оператор, чтобы инициализировать обе оценки для каждой записи (имени) в коде.

Обратите внимание, что мы можем расширить эту структуру данных, чтобы хранить больше информации (например, $subj) по мере необходимости, аккуратно и с небольшими изменениями кода. Это еще одно преимущество использования хэшей.

Несколько комментариев к опубликованному коду

  • Почему в начале нет use warnings;? Вы должны иметь это; напрямую полезно

  • Используйте лексические дескрипторы файлов, open my $fh, '<', $file ... вместо глобусов (FILE)

  • Обрабатывать файлы по очереди за раз, если нет определенной причины прочитать все сначала

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

    Конечно, если все файлы, которые эта программа когда-либо будет читать , были бы похожи на то, что вы показываете, всегда со всеми полями и только с проходной / неудачной оценкой, тогда не было бы необходимости проверять ввод

  • Образец /\s+/ в split почти всегда следует заменять на ' ', то же самое, но он также отбрасывает ведущие пробелы. Это также значение по умолчанию, наряду с $_ для строки, таким образом, просто split; выше (строка, которую она разделяет на ' ', равна $_)

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

Приращение соответствующего счетчика можно записать более кратко

while (<$fh>) {
    next if not /\S/;
    my ($subj, $name, $grade) = split;
    # check input ...

    if ($grade !~ /^(?:pass|fail)$/) {
        warn "Unknown grade format for $name in $subj: $grade";
        next;
    }   

    $results{$name}->{$grade}++;
}

Если вы предпочитаете, чтобы ваш код спокойно принимал что-нибудь в третьем поле и сохранял его в %results с его счетом, то снимите проверку с pass|fail.

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