Practice

Perl exercises

Make a change, predict what it will do, and compare the result.

  1. 1

    Perl Basics

    Declare a scalar and print it.

    Adapt the lesson example. Change one value or element, predict the result, and explain why it changes.

    Show starting example
    Perl
    use strict;
    use warnings;
    my $name = "Ada";
    print "Hello, $name\n";
  2. 2

    Perl Subroutines

    Define a sub that reads its arguments from @_.

    Predict average(1, 2).

    Reveal answer and reasoning

    3 / 2 is 1.5, so it prints 1.5.

    Show starting example
    Perl
    use strict;
    use warnings;
    
    sub average {
        my @values = @_;
        my $total = 0;
        $total += $_ for @values;
        return $total / @values;
    }
    
    print average(82, 91, 76, 95), "\n";
  3. 3

    Perl regular expressions and text processing

    Capture named groups, collect all matches, transliterate characters, and compute replacements with /e.

    What does "hello" =~ tr/a-z// return?

    Reveal answer and reasoning

    5, because tr with an empty replacement counts the characters that match the range and leaves the string unchanged.

    Show starting example
    Perl
    use strict;
    use warnings;
    
    my $line = "2024-03-15 ERROR disk full on /dev/sda1";
    if ($line =~ /^(?<date>\d{4}-\d{2}-\d{2})\s+(?<level>[A-Z]+)\s+(?<message>.*)$/) {
        print "$+{date} | $+{level} | $+{message}\n";
    }
    
    my $text = "cat bat rat hat";
    my @letters = $text =~ /\b(\w)at\b/g;
    print scalar(@letters), " matches: @letters\n";
    
    (my $copy = $text) =~ s/(\w)at/\U$1\Eat/g;
    print "$copy\n";
    
    (my $dna = "AACGT") =~ tr/ACGT/TGCA/;
    my $count = ($text =~ tr/a//);
    print "$dna $count\n";
    
    my $prices = "apple=3, pear=12, fig=7";
    (my $doubled = $prices) =~ s/(\d+)/$1 * 2/ge;
    print "$doubled\n";
    
    print join("|", split /\s*,\s*/, "a , b,c ,d"), "\n";
  4. 4

    Perl references and nested data structures

    Build hashes of arrays, add to them, and loop over them with references.

    How do you read the first score of Ada?

    Reveal answer and reasoning

    $scores{Ada}[0] or $scores{Ada}->[0]; the arrow is optional between subscripts.

    Show starting example
    Perl
    use strict;
    use warnings;
    use List::Util qw(sum);
    
    my %scores = (Ada => [90, 85], Bo => [70], Cy => []);
    push @{ $scores{Bo} }, 95;
    $scores{Di}[0] = 60;
    
    for my $name (sort keys %scores) {
        my @s = @{ $scores{$name} };
        my $average = @s ? sprintf("%.1f", sum(@s) / @s) : "n/a";
        print "$name: ", scalar(@s), " scores, average $average\n";
    }
    
    print join(" ", ref(\1), ref([]), ref({}), ref(sub { 1 }), ref(\\1)), "\n";
    
    my %copy = %scores;
    delete $copy{Cy};
    print exists $scores{Cy} ? "Cy still in the original\n" : "Cy gone\n";
    print join(",", sort keys %copy), "\n";
    my @both = @scores{qw(Ada Bo)};
    print scalar(@{ $both[0] }) + scalar(@{ $both[1] }), " scores for Ada and Bo\n";
  5. 5

    Perl file handles on strings and formatted output

    Read lines from a string as if from a file, validate each line, and write formatted output to a string.

    How do you read from a real file instead?

    Reveal answer and reasoning

    Replace \$data with the file name: open(my $in, '<', 'data.txt') or die ...

    Show starting example
    Perl
    use strict;
    use warnings;
    
    my $data = "alpha 3\nbeta 5\ngamma x\n";
    open(my $in, '<', \$data) or die "cannot open: $!";
    my $total = 0;
    while (my $line = <$in>) {
        chomp $line;
        my ($name, $value) = split ' ', $line;
        if ($value =~ /^\d+$/) {
            print "$name=$value\n";
            $total += $value;
        } else {
            print "bad line: $line\n";
        }
    }
    close $in;
    print "total $total\n";
    
    my $report = '';
    open(my $out, '>', \$report) or die "cannot open: $!";
    printf $out "%-6s|%3d\n", "ab", 7;
    print {$out} "done\n";
    close $out;
    print $report;
  6. 6

    Perl subroutines, context, and closures

    Return different results in list and scalar context, build closures, and write a recursive anonymous function.

    What does print scalar(minmax(1, 2)) print?

    Reveal answer and reasoning

    1..2: scalar() forces scalar context, so minmax returns the string.

    Show starting example
    Perl
    use strict;
    use warnings;
    
    sub minmax {
        my @sorted = sort { $a <=> $b } @_;
        return wantarray ? ($sorted[0], $sorted[-1]) : "$sorted[0]..$sorted[-1]";
    }
    my ($min, $max) = minmax(4, 9, 2, 7);
    my $range = minmax(4, 9, 2, 7);
    print "$min $max $range\n";
    
    sub counter {
        my $n = shift // 0;
        return { inc => sub { return ++$n }, get => sub { return $n } };
    }
    my $c1 = counter();
    my $c2 = counter(10);
    $c1->{inc}->() for 1 .. 3;
    $c2->{inc}->();
    print $c1->{get}->(), " ", $c2->{get}->(), "\n";
    
    my $factorial;
    $factorial = sub {
        my $n = shift;
        return $n <= 1 ? 1 : $n * $factorial->($n - 1);
    };
    print $factorial->(5), "\n";
    
    my @list = (3, 1, 2);
    my $count = @list;
    my ($first) = @list;
    print "$count $first\n";
  7. 7

    Perl packages, bless, and inheritance

    Create objects as blessed hashes, override methods in a subclass, and call the parent version.

    How would you add a Cat class?

    Reveal answer and reasoning

    Add package Cat; use parent -norequire, 'Animal'; sub sound { return 'Meow' }. The existing speak method works unchanged.

    Show starting example
    Perl
    use strict;
    use warnings;
    
    package Animal;
    
    sub new {
        my ($class, %args) = @_;
        my $self = { name => $args{name} };
        return bless $self, $class;
    }
    
    sub name { return $_[0]{name} }
    sub sound { return 'generic noise' }
    
    sub speak {
        my $self = shift;
        return $self->name . ' says ' . $self->sound;
    }
    
    package Dog;
    use parent -norequire, 'Animal';
    
    sub sound { return 'Woof' }
    
    sub speak {
        my $self = shift;
        return $self->SUPER::speak() . '!';
    }
    
    package main;
    
    my @animals = (Animal->new(name => 'Generic'), Dog->new(name => 'Rex'));
    print $_->speak, "\n" for @animals;
    print $animals[1]->isa('Animal') ? "a Dog is an Animal\n" : "not an Animal\n";
    print Dog->can('speak') ? "Dog can speak\n" : "Dog cannot speak\n";
    print ref($animals[1]), "\n";
  8. 8

    Perl Arrays

    Store a list in an @array and loop over it.

    Print the first element.

    Reveal answer and reasoning

    print $scores[0]; uses $ because a single element is a scalar. It prints 82.

    Show starting example
    Perl
    use strict;
    use warnings;
    
    my @scores = (82, 91, 76);
    push @scores, 95;
    my $total = 0;
    $total += $_ for @scores;
    print scalar(@scores), " ", $total, "\n";
  9. 9

    Perl Hashes

    Map keys to values and print them in a stable order.

    Remove Grace before the loop.

    Reveal answer and reasoning

    Use delete $scores{Grace}; only Ada and Linus are printed.

    Show starting example
    Perl
    use strict;
    use warnings;
    
    my %scores = (Ada => 91, Grace => 88);
    $scores{Linus} = 79;
    for my $name (sort keys %scores) {
        print "$name: $scores{$name}\n";
    }
  10. 10

    Perl sort blocks and the Schwartzian transform

    Sort numbers, hash keys by value, and records by computed keys without recomputing the keys.

    How would you sort the words in reverse alphabetical order?

    Reveal answer and reasoning

    sort { $b cmp $a } @words, or reverse sort @words.

    Show starting example
    Perl
    use strict;
    use warnings;
    
    my @nums = (10, 9, 1, 25);
    print join(" ", sort @nums), "\n";
    print join(" ", sort { $a <=> $b } @nums), "\n";
    print join(" ", reverse sort { $a <=> $b } @nums), "\n";
    
    my %age = (Ada => 30, Bo => 25, Cy => 30, Di => 25);
    my @by_age = sort { $age{$b} <=> $age{$a} or $a cmp $b } keys %age;
    print "@by_age\n";
    
    my @words = qw(pear fig apple kiwi);
    my @by_length =
        map  { $_->[1] }
        sort { $a->[0] <=> $b->[0] or $a->[1] cmp $b->[1] }
        map  { [length($_), $_] } @words;
    print "@by_length\n";
  11. 11

    Perl stacks, queues, and word frequencies

    Use push, pop, shift, and unshift as stack and queue operations, and count items with a hash.

    How would you count only words longer than three letters?

    Reveal answer and reasoning

    Add a condition: $freq{$_}++ for grep { length($_) > 3 } split ' ', $text;

    Show starting example
    Perl
    use strict;
    use warnings;
    
    my @stack;
    push @stack, 1, 2, 3;
    my $top = pop @stack;
    print "$top @stack\n";
    
    my @queue = (1, 2, 3);
    my $front = shift @queue;
    push @queue, 4;
    print "$front @queue\n";
    unshift @queue, 0;
    print "@queue\n";
    
    my $text = "the cat and the hat and the bat";
    my %freq;
    $freq{$_}++ for split ' ', $text;
    print join(" ", map { "$_=$freq{$_}" } sort { $freq{$b} <=> $freq{$a} or $a cmp $b } keys %freq), "\n";
    
    my @even = grep { $_ % 2 == 0 } 1 .. 10;
    my @squares = map { $_ ** 2 } @even;
    print "@even | @squares\n";
    
    my %seen;
    my @unique = grep { !$seen{$_}++ } qw(a b a c b);
    print "@unique\n";
  12. 12

    Perl SHA-256 and HMAC with Digest::SHA

    Hash messages, authenticate them with a key, and compare tags without leaking timing.

    What does an HMAC prove that a plain hash does not?

    Reveal answer and reasoning

    It proves the sender knew the secret key, so the message is authentic as well as unmodified.

    Show starting example
    Perl
    use strict;
    use warnings;
    use Digest::SHA qw(sha256_hex hmac_sha256_hex);
    
    print sha256_hex("abc"), "\n";
    print sha256_hex("abd"), "\n";
    
    sub secure_equal {
        my ($x, $y) = @_;
        return 0 unless length($x) == length($y);
        my $diff = 0;
        $diff |= ord(substr($x, $_, 1)) ^ ord(substr($y, $_, 1)) for 0 .. length($x) - 1;
        return $diff == 0;
    }
    
    my $message = "what do ya want for nothing?";
    my $tag = hmac_sha256_hex($message, "Jefe");
    print "$tag\n";
    print "valid: ", (secure_equal($tag, hmac_sha256_hex($message, "Jefe")) ? "yes" : "no"), "\n";
    print "forged: ", (secure_equal($tag, hmac_sha256_hex("what do ya want for something?", "Jefe")) ? "yes" : "no"), "\n";