Perl Exercises

in perl

A short list of exercises I use when teaching perl. Hope you'll find them useful. Feel free to add yours in the comments.

Using And Creating References

  1. Write a function called diffsum that takes 2 lists and prints the difference in their sum. For example:
my @x = (10, 20, 30);
my @y = (10, 20, 40);

# prints: -10
diffsum(\@x, \@y);
  1. Write a function called longer_than that takes a list and a scalar and prints all the strings in lists longer than scalar. Caveat: The scalar is passed in a hashref. Example:
# returns ('mountains')
longer_than('I', 'can', 'see', 'the', 'mountains', { minlength => 4 });
  1. Write a function that takes a list of words and a hash of weights, and prints the sum of the weights. For example:
# prints: 85
sum_weights('I', 'can', 'can', 'see', 'you', { 
  I   => 10,
  can => 30,
  you => 15,
});

Nested Data Structures

  1. Given the following data:
my $data = [
 { name => 'bill', age => 74 },
 { name => 'george', age => 60 },
 { name => 'emily', age => 80 },
 { name => 'maria', age => 21 },
]

print the details of the oldest person

  1. Given a series of .ini files of the following format:
// file: users.ini
name=ynon
web = www.tocode.co.il
likes = python and stuff

// file: teachers.ini
name = joe
email = joe@gmail.com
color = red

// file cities.ini
size=10
name=foo bar

Write a program that takes a list of ini file names and saves their data to as a nested hash: The key is the file name and the value is a hash of the data in that file.

Then write a function that takes this data structure and a list of keys and returns a new hash containing just the files that have those keys, and for each just the specified keys. For example using the above data:

filterData($data, 'name', 'email');

# Should return:
# {
#   users.ini =>    { name => 'ynon' },
#   teachers.ini => { name => 'joe', email => 'joe@gmail.com' }
#   cities.ini =>   { name => 'foo bar' },
# }
  1. Given an input file of the following format:
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
	options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
	inet 127.0.0.1 netmask 0xff000000 
	inet6 ::1 prefixlen 128 
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 
	nd6 options=201<PERFORMNUD,DAD>
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
	ether f4:0f:24:29:df:4d 
	inet6 fe80::1cb5:1689:1826:cc7b%en0 prefixlen 64 secured scopeid 0x4 
	inet 10.176.85.19 netmask 0xffffff00 broadcast 10.176.85.255
	nd6 options=201<PERFORMNUD,DAD>
	media: autoselect
	status: active
en1: flags=963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX> mtu 1500
	options=60<TSO4,TSO6>
	ether 06:00:58:62:a3:00 
	media: autoselect <full-duplex>
	status: inactive
p2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304
	ether 06:0f:24:29:df:4d 
	media: autoselect
	status: inactive

Write a perl script to parse the file into an array of hashes, each hash represents the properties of a single block. Then write a function that prints specific fields from the data, so one can use:

printFields($blocks, 'interface', 'inet', 'status');

And get the result:

lo0,127.0.0.1,
gif0,,
en0,10.176.85.19,active
en1,,inactive
p2p0,,inactive

Supported Field Names: interface, flags, ether, media, inet, status.

Subroutine References

  1. Write a function called groupby that takes a key-function and a list. The function should return a hash whose keys are the result of key-function and values are the values from the original list which produced these results. Example usage:
groupby(
    sub { substr($_[0], 0,1) }, 
    'hello', 'hi', 'help', 'bye', 'here'
)

# returns: 
# { 
#    h => ['hello', 'hi', 'help', 'here'], 
#    b => ['bye'] ,
# }
  1. Fill in the code for the memoize function in the snippet so the following code prints only 10 times:
use v5.18;

sub memoize {
# Fill code here
}

sub fib {
  my ($n) = @_;
  say "fib($n)";
  $n <= 2 ? 1 : fib($n-1) + fib($n-2)
}

*{main::fib} = memoize(\&fib);

say fib(10);
  1. Write a function called iter that takes a list and returns a new function ref, which every time is called returns a next element from the list. Example usage:
my @x = (10, 20, 30, 40);
my $i = iter(@x);

# prints 10 20 30 40
while (my $item = $i->()) {
  say $item;
}

Using Modules

  1. Given a passwords.md5 file that contains the following MD5 password digests:
3899dcbab79f92af727c2190bbd8abc5
5f4dcc3b5aa765d61d8327deb882cf99
827ccb0eea8a706c4c34a16891f84e7b
25d55ad283aa400af464c76d713c07ad
d8578edf8458ce06fbc5bb76a58c5ca4
9cdda062c5bd8e38e3a576c61208e1d4
25f9e794323b453885f5181f1b624d0b
81dc9bdb52d04dc20036dbd8313ed055
5d41402abc4b2a76b9719d911017c592
8621ffdbc5698829397d97767ac13db3
37b4e2d82900d5e94b8da524fbeb33c0
9aeaed51f2b0f6680c4ed4b07fb1a83c
d0763edaa9d9bd2a9516280e9044d885
0d107d09f5bbe40cade3de5c71e9e9b7
8c62573d0982c91aad72e634ed6903f2
e99a18c428cb38d5f260853678922e03
96e79218965eb72c92a549dd5a330112
96e25e3c6373262faae824dc5a16cce1
d5bb5c0168e2952b6806d6a976b3d98a

Use Digest::MD5 and Math::Cartesian::Product to find their plaintext sources.

  1. Use File::Find to find all *.pl files that don't start with a shebang line.

  2. Using List::Util, write a program that prints only the unique arguments it received from command line. Example usage:

# prints: ho bye
$ print_unique.pl ho ho ho bye bye ho
  1. Speed up ex. 1 in this chapter using threads pragma.

Error Handling

  1. Without using threads, write a perl function that tries to read a value from the user, but if the user didn't type anything within n seconds it should return undef. Example usage:
my $x = read_with_timeout(5);
# Now program stops and waits at most 5 seconds 
# for the user to type a number.
  1. Add error handling to the code below so the final message is always printed.
sub go {
    print "in go\n";
    die "hahaha";
}

go();
print "--- the end \n";
  1. Write a program that takes a file name and prints the number of lines in the file. In case of error print a proper error message. Use autodie.
use autodie;
my $filename = shift;

# fill code here
  1. Install Try::Tiny and use it to improve the above (Ex. 3) solution.

Writing Modules

  1. Write a module called MyStack to make the following code work:
use MyStack;

add_item(10);
add_item(20);
add_item(22, 33);

# prints 33
print pop_item();

# prints 22
print pop_item();

clear_stack();
# now prints nothing
print pop_item();
  1. Write a module called AddressBook to make the following code work:
my $home = AddressBook::new;

AddressBook::add($home, 'bill', 'bill@gmail.com');
AddressBook::add($home, 'jane', 'jane@gmail.com');
AddressBook::add($home, 'maria', 'maria@yahoo.com');

# prints: 
# 'bill, bill@gmail.com'
# 'jane, jane@gmail.com'
print AddressBook::find($home, '@gmail.com');
  1. Write a module for finding anagrams in a list of words. Two words are an anagram if they have the same letters, for example
    add and dad are an anagram. The module should provide a class named Anagramer with the methods:

    • init(wordsfile) takes a words file and parses it to an anagram repository
    • get_random_anagram that returns a random anagram from the repository
    • list_anagrams(word) that lists all anagrams for a given word
  2. Write a perl script for mass renaming music files according to labels. The script takes an existing format of files in
    current directory and an expected output format and prints a list of old -> new file name tuples.

Format can be any string that contains any number of the labels: <artist>, <album>, <track>, <year>.

Sample list of input files:

Bob Dylan - 01 You're No Good (1962).mp3
Bob Dylan - 02 Talkin' New York (1962).mp3
Bob Dylan - 03 In My Time of Dyin' (1962).mp3
Bob Dylan - 04 Man of Constant Sorrow (1962).mp3
Bob Dylan - 05 Fixin' to Die (1962).mp3
Bob Dylan - 06 Pretty Peggy-O (1962).mp3

Sample input format:

<album> - <track> <title> (<year>).mp3

Sample output format:

Bob Dylan/<year> <album>/<track> <title>.mp3

Expected output:

Bob Dylan - 01 You're No Good (1962).mp3 -> Bob Dylan/1962 Bob Dylan/01 You're No Good.mp3
Bob Dylan - 02 Talkin' New York (1962).mp3 -> Bob Dylan/1962 Bob Dylan/02 Talkin' New York.mp3
Bob Dylan - 03 In My Time of Dyin' (1962).mp3 -> Bob Dylan/1962 Bob Dylan/03 In My Time of Dyin'.mp3
Bob Dylan - 04 Man of Constant Sorrow (1962).mp3 -> Bob Dylan/1962 Bob Dylan/04 Man of Constant Sorrow.mp3
Bob Dylan - 05 Fixin' to Die (1962).mp3 -> Bob Dylan/1962 Bob Dylan/05 Fixin' to Die.mp3
Bob Dylan - 06 Pretty Peggy-O (1962).mp3 -> Bob Dylan/1962 Bob Dylan/06 Pretty Peggy-O.mp3

Bonus: also rename the files and create required directories along the way.

Moose Object System

  1. The following code assumes a class named Widget which represent a thing that needs to be built. Building a widget
    should automatically trigger a build on all its dependencies. Implement Widget so the following code works:
my $luke    = Widget->new("Luke");
my $hansolo = Widget->new("Han Solo");
my $leia    = Widget->new("Leia");
my $yoda    = Widget->new("Yoda");
my $padme   = Widget->new("Padme Amidala");
my $anakin  = Widget->new("Anakin Skywalker");
my $obi     = Widget->new("Obi-Wan");
my $darth   = Widget->new("Darth Vader");
my $_all    = Widget->new("All");


$luke->add_dependency($hansolo, $leia, $yoda);
$leia->add_dependency($padme, $anakin);
$obi->add_dependency($yoda);
$darth->add_dependency($anakin);

$_all->add_dependency($luke, $hansolo, $leia, $yoda, $padme, $anakin, $obi, $darth);
$_all->build;
# code should print: Han Solo, Padme Amidala, Anakin Skywalker, Leia, Yoda, Luke, Obi-Wan, Darth Vader
# (can print with newlines in between modules)
  1. Provided a text file with information about artists and songs:
Joy Division - Love Will Tear Us Apart
Joy Division - New Dawn Fades
Pixies - Where Is My Mind
Pixies - Hey
Genesis - Mama

Write the required classes so the following code works:

my $music = MusicFile->new('/Users/ynonperek/music.txt')
print($music->artist('Joy Division')->songs);
  1. Write a class named Notebook that saves notes and allows quick lookup by tags:
my $notes = Notebook->new
$notes->add('fix stuff', tags => ['TODO']);
$notes->add('then add stuff', tags => ['TODO', 'perl']);
$notes->add('show stuff to friends', tags => ['perl']);

# prints:
# fix stuff
# then add stuff
print $notes->find('TODO');
  1. Write a Document class that represents text in a file. Provide get($line), set($line, $content), save().
    Then Write a PersistentDocument class that saves the file after each change.
    The following snippet uses both classes:
my $d = Document->new('file1.txt');
my $p = PersistentDocument->new('file2.txt');

# This one does not change file1.txt
$d->set_line(2, 'This is the new line 2');

# This one changes file2.txt
$p->set_line(2, 'This is the new line 2');

# Finally, this one changes file1.txt
$d->save;

Classic Object Oriented Perl

  1. Using only standard modules, implement a Singleton object so that the following code works:
my $s = Counter->instance;
my $t = Counter->instance;

# print 1
$s->tick();

# print 2
$s->tick();

# print 3
$t->tick();
  1. Using only standard modules, implement the classes Car and Race so that the following code works:
my $c1 = Car->new(color => 'red', speed => 20);
my $c2 = Car->new(color => 'blue', speed => 50);
my $c3 = Car->new(color => 'green', speed => 30);

my $r = Race->new($c1, $c2, $c3);

# prints: "The blue car won"
print $r->winner;
  1. Find and fix the bug in the following code:
use v5.18;

package Critter {
  my %critters;
  use Scalar::Util qw/refaddr/;
  sub new {
    my ($cls, $name) = @_;
    my $self = {};
    bless $self, $cls;
    my $id = refaddr $self;
    $critters{$id} = $name;

    $self;
  }

  sub name {
    my ($self, $val) = @_;
    my $id = refaddr $self;
    if (defined($val)) {
      $critters{$id} = $val;
    }
    $critters{$id};
  }
}

Additional Resources

Not sure how to solve some of them? Or just interested in writing better perl code? Gabor's Perl
tutorial
is a great place to start.

Comments