in reply to Design a Perl training day

Well, the Perl Training Day finally happened. We covered about half the material I prepared, but it was a great success. I had aimed for the day to be fun, for each person to finish the day feeling they had learned worthwhile stuff, to introduce interesting and important corners of Perl and to get people comfortable with using a variety of Perl resources. So far as I can tell I succeeded on each point despite a large disparity in the backgrounds and skills of the participants.

So, how did I structure it? Well, the fun element was the lollipop element (think node rep and XP). The material was structured as a series of exercises where the participant did some guided research, then a coding exercise, then a group discussion that examined the code generated by individuals in the group and some reference code that I provided. This structure worked very well with those quicker on the uptake or with a more appropriate background getting the core task completed first, but then going on to play with their solution, or helping others in the group.

The following is the day plan we followed, although time slipped and not all the material was covered:

Introduction

(5 minutes): 9:00

During this training day I hope to give an overview of what Perl is about, what it’s good at, and touch on some of the common Perl idioms. We’ll also touch on a few traps for young players.

Sessions generally consist of cycles of research, code and discuss phases. Times are approximate and don't need to be strictly adhered to, but material at the end of a session will be skipped if it runs into a break period.

Perl resources

Note that the mantra:

use strict; use warnings; use 5.010;

is presumed to start each of your scripts.

Training day guidelines and rules

  1. If you are done with a task and someone else seems to need help, help them.
  2. Lollipops may be traded for help.
  3. Failures often teach as much as successes.
  4. There is no rule 4.
  5. Not all group lollipops need be awarded and unawarded lollipops jackpot.
  6. Group discussion is allowed during research.
  7. Lollipops have no intrinsic value.
  8. If a task seems too simple find a different way to do it (TIMTOWTDI).
  9. Lollipops may actually be some other form of confectionary (TIMTOWTDI).
  10. Questions may be asked by participants at any time.
  11. Task times are mutable, break times are not.

Session 1 – The basics

(85 minutes): 9:05

Research: The strictures, according to Seuss

(3 minutes – 9:08)

Use PerlMonk’s “Super Search” to find, and then read a Seussish take on strict and warnings.

Note that all scripts you write today (except command line “one liners”) are expected to run correctly with strictures!

Research: Run a Perl script

(2 minutes – 9:10)

Find out how to run a Perl script from the command line. You may find one of the following resources helpful:

  • Perldoc perlrun
  • A workmate
  • Try “the obvious thing”
  • You don’t need to actually run a script at this point. But you do need to know how to run a script for the code phase.

    Code: Hello World x 10

    (4 minutes – 9:14)

    Work individually to each write a Perl script that prints “Hello World” to the console ten times. Run the script to check that it works.

    For two lollipops write a command line “one liner” version.

    Discuss: How do your “Hello World” scripts compare?

    (5 minutes – 9:19)

    TIMTOWTDI – did everyone write the same script? What were the interesting differences?

    The group gains two lollipops to award as it deems appropriate.

    Research: variables, $, % @ and context

    (5 minutes – 9:24)

    Read the “Variable names” and “Context” sections of perldata (resource: http://perldoc.perl.org or “perldoc perldata”).

    Code: Reverse stuff

    (5 minutes – 9:29)

    Write a script that creates a list of the numbers 1 to 20. Print the list reversed.

    Write a script that initialises a variable with a string containing all the letters of the English alphabet. Print the reversed string.

    The reverse function may be of interest for these tasks.

    One lollipop awarded for each task completed as a “one liner”.

    Discuss: How did your reversey scripts compare

    (5 minutes – 9:34)

    What were the interesting differences between scripts? Did you notice that reverse is context sensitive? What is the reverse of a single element list? What context does the print function provide?

    The group gains two lollipops to award as it deems appropriate.

    Research: Slicing and dicing

    (5 minutes – 9:39)

    Read the “Slices” section in perldata.

    Code and discuss: Reverse less stuff

    (5 minutes – 9:44)

    Write a script that creates a list of the numbers 1 to 20. Print the list with elements 5 to 10 reversed.

    One lollipop awarded for completing the task as a “one liner”.

    The group gains two lollipops to award as it deems appropriate.

    Research: String manipulation

    (8 minutes – 9:52)

    Read up about the Perl functions: index, join, split and substr

    Code and discuss: Reverse less stuff – string version

    (5 minutes – 9:57)

    Write a script that initialises a variable with a string containing all the letters of the English alphabet. Reverse the substring containing letters ‘g’ to ‘m’ and print the result.

    The group gains two lollipops to award as it deems appropriate.

    Research: Perl Hash

    (3 minutes – 10:00)

    Read the Hashes subsection in the “Perl variable types” section of perlintro.

    Code and discuss: Hashy uniqueness

    (10 minutes – 10:10)

    Write a script that finds the unique letters in your name and print the result.

    Now do it case insensitive but case preserving (for the first case of each letter found).

    The group gains three lollipops to award as it deems appropriate.

    Research and discuss: map and grep

    (5 minutes 10:15)

    You may have noticed map and grep in the previous code samples. Use perldoc -f map and perldoc -f grep to learn about two of Perl's list processing powerhouses.

    Compare the code you wrote for Hashy uniqueness to the sample code and discuss the advantages and disadvantages of using these bits of syntactic sugar.

    Research: Boolean or operator(s)

    (5 minutes – 10:20)

    Perl lifted C’s operators wholesale, then added a few of its own. Even those that came from C have acquired new behaviour. Use http://perldoc.perl.org to find the various flavours of or operator provided by Perl.

    Code and discuss: sub – optimal defaults

    (10 minutes – 10:30)

    Write a sub that accepts three parameters where the first parameter is required and the following two are optional.

    The first parameter is a string to be printed.

    The second parameter determines how many lines of the repeated string to print (default 1). If this parameter is 0 nothing is printed.

    The third parameter determines how many times to repeat the string on a line (default 1). This parameter is set to 1 if 0 is provided.

    Call the sub with several different sets of parameters to demonstrate that it works correctly. For example, the call repeter('Peter ', 2, 3); should print:

    Peter Peter Peter Peter Peter Peter

    For one lollipop sensibly use both || and //.

    The group gains one lollipop to award as it deems appropriate.

    Morning tea break – 10:30

    (20 minutes – 10:50)

     

    Session 2 –Loops, references and data structures – 10:50

    (90 minutes)

    Research: Go loopy

    (5 minutes – 10:55)

    Read through the “Loop Control” section of perlsyn and take note of the various loop control commands.

    Code and discuss: Process some input

    (20 minutes – 11:15)

    Write a script that plays a number guessing game. The script generates a number from 0 to 10 (the function rand may help here). If the number is 0 the script prints the number of numbers guessed and the message “I’m sick of playing now” and the number of numbers guessed then exits.

    If the player enters 0 the script prints the number of numbers guessed then exits.

    If the player enters the correct number the script prints “Got it” then generates a new number.

    Otherwise the script indicates whether the entered number was high or low.

    Note that <> is used to read a line from STDIN.

    One lollipop awarded for sensibly using next. One awarded lollipop for sensibly using last. One lollipop awarded for sensibly using redo. Two lollipops awarded for sensibly using continue.

    The group gains three lollipops to award as it deems appropriate.

    Research: perlreftut

    (10 minutes – 11:25)

    Skim down to the Making References section. Read through to the end of the “Using References” section. Read the “Arrow Rule” section. Read the “The Rest” section.

    Code and Discuss: An odd hash

    (10 minutes – 11:35)

    Write a script that generates an array of 20 random numbers in the range 0 to 100. From the array build a hash keyed by ‘odd’ and ‘even’ of the odd and even values. Print the even values on the first line and the odd values on the second line.

    Research: perlsub

    (5 minutes – 11:40)

    Skim the Synopsis section. Read the Description at least down to the first example block. There is a lot of material in this section! For now make sure you (at least think you) understand how to pass parameters.

    Code and Discuss: Passing Arrays

    (5 minutes – 11:45)

    Modify your odd hash code to call a sub that takes a list of values and returns the sorted list. Use the sub (in two calls) to sort the odd and even numbers for printing. You may need to read the documentation for the sort function.

    Code and Discuss: Passing Arrays by reference

    (5 minutes – 11:50)

    Proceed as for the Passing Arrays exercise above, but now pass and return the arrays by reference.

    Discuss: What the hash!

    (5 minutes – 11:55)

    Without running it, what does the following code print and why, or why can’t you tell?

    my %hash = (1 .. 20); say join ' ', theSub(%hash); sub theSub {return @_;}

    When you agree to a good story about the code, run it to see what you actually get.

    The group gains three lollipops to award as it deems appropriate.

    Code and Discuss: Hash sorted

    (10 minutes – 12:05)

    Modify the sample code above so that theSub returns the list sorted by the key values in the hash.

    The group gains two lollipops to award as it deems appropriate.

    Research: push, pop, shift, unshift and splice

    (5 minutes – 12:10)

    Use perldoc –f to read the Perl documentation for the common array manipulation functions.

    Code and Discuss: Reversing old school

    (5 minutes – 12:15)

    Revisit reversing a list of numbers. This time use an array and reverse the list without using reverse.

    The group gains two lollipops to award as it deems appropriate.

    Lunch: The Asian

    (80 minutes): 12:20

    We are off to The Asian for lunch.

    Session 3 – Regular expressions and CPAN

    (80 minutes): 13:40

    Research: perlrequick

    (5 minutes – 13:45)

    Much of Perl’s text processing power comes from regular expression processing. At almost 500 lines even the quick start documentation for regular expressions is not short! Read down to the “Using Character Classes” section then skip to the “Search and replace” section and read it.

    Code and discuss: Normalise a file path

    (5 minutes – 13:50)

    Write a regular expression replacement statement to normalise all the Linux ('/') or Windows ('\') path separators in a string to Linux ('/') separators. Note that the switch character 'g' can be used to match as many times as possible: s/x/y/g would replace all 'x' characters with 'y' characters for example.

    One lollipop awarded for using a character other than / for the regular expression delimiters.

    The group gains one lollipop to award as it deems appropriate.

    Research: more perlrequick

    (5 minutes – 13:55)

    Skim through the “Using character classes” paying attention to “abbreviations for common character classes”. Carry on down to the end of the “Matching repetitions” section.

    Code and discuss: More normalise a file path

    (5 minutes – 14:00)

    Write a regular expression replacement statement to normalise all the Mac (':'), Linux ('/') or Windows ('\') path separators in a string to Linux ('/') separators.

    One lollipop awarded for using a character other than / for the regular expression delimiters.

    The group gains one lollipop to award as it deems appropriate.

    Code and discuss: lines of numbers

    (5 minutes – 14:05)

    Use the code fragment:

    my @lines = split '\n', `perldoc perlrequick`;

    to create an array of all the lines in the perlrequick documentation. Print all the lines containing a number (and no other lines smart arse).

    The group gains one lollipop to award as it deems appropriate.

    Research, code and discuss: perlrequick, this or that

    (5 minutes – 14:10)

    Read the "Matching this or that" section and the following section of perlrequick, then use the fragment from the previous example and print all the lines where the word 'more' is preceded by 'or' or 'not'.

    The group gains one lollipop to award as it deems appropriate.

    Research, code and discuss: just this or that

    (5 minutes – 14:15)

    Read the "Extracting matches" section then modify your last script to just print out the word matched before more.

    The group gains one lollipop to award as it deems appropriate.

    Code and discuss: play it again Sam

    (5 minutes – 14:20)

    Find and print all the lines containing repeated words.

    The group gains one lollipop to award as it deems appropriate.

    Research, code and discuss: you wear that in public?

    (25 minutes – 14:45)

    Read the perllol documentation. Read from perldsc the sections on references (you may wish to skim perlref it this section makes your eyes cross), common mistakes and the arrays of hashes section. Read other sections that take your interest and as time allows.

    Create a data structure that contains the main visible articles of clothing currently worn by each of the members of the course. For each article you should store the dominant colour, material and the wearer's name.

    Search the data structure for all the items of apparel of a specific colour. Output a table showing the person's name, article type and material for each item found.

    The group gains four lollipops to award as it deems appropriate.

    Research: CPAN

    (5 minutes – 14:50)

    Use http://search.cpan.org/ to find and read the documentation for the module Lingua::EN::Sentence.

    Code and discuss: nasty sentences

    (10 minutes – 15:00)

    Use cpan or ppm to install Lingua::EN::Sentence. Write a script using Lingua::EN::Sentence to extract the sentences from:

    Sentences can be hard to parse. Take the following for example: "Mr. Jones works for I.B.M. ... or maybe not.". (Not so easy eh?) So, did this get it right?

    And print them one sentence per line with a blank line between each sentence. Can you find a sentence that breaks the module?

    The group gains one lollipop to award as it deems appropriate.

    Afternoon tea break

    (20 minutes): 15:00

    Session 4 – Putting it all together

    (90 minutes): 15:20

    Code and discuss: Create a password

    (5 minutes – 15:25)

    Using the characters:

    123456789ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnpqrstuvwxyz

    create a random password of length $length.

    The group gains two lollipops to award as it deems appropriate.

    Research, code and discuss: reprise - you wear that in public?

    (25 minutes – 15:50)

    Scan http://perlmonks.org/?node_id=896028 with attention to the example code and read the explanatory text as required to understand the code. If you are unfamiliar with SQL you may also like to visit http://www.sqlite.org/lang.html.

    Using the example code as a starting point create a database that contains the main visible articles of clothing currently worn by each of the members of the course. For each article you should store the dominant colour, material and the wearer's name. You may need to install DBD::SQLite from CPAN.

    The group gains two lollipops to award as it deems appropriate.

    Code and discuss: mix and match

    (15 minutes – 16:05)

    Write a script to search the database you have just created for all the items of apparel of a specific colour. Output a table showing the person's name, article type and material for each item found.

    The group gains two lollipops to award as it deems appropriate.

    Research, code and discuss: dir, ls or something else

    (10 minutes – 16:15)

    Use perldoc –f –e to find out about the file test 'functions' Perl provides (read down to the paragraph following the example). Also read the documentation for opendir, readdir and closedir.

    Write a script that generates a table listing the files in the current directory along with their size and last modification time. Note that the special variable $^T gives the epoch time the script started executing and localtime will generate a "pretty" time string given epoch time.

    The group gains one lollipop to award as it deems appropriate.

    Code and discuss: Simple calculator

    (20 minutes – 16:35)

    Write a script to accept a user supplied simple arithmetic expression (+, -, * and / operators only). You may ignore normal operator precedence and simply evaluate the expression left to right. Print the result of evaluating the expression.

    Using string eval is considered cheating!

    One lollipop awarded for sensible use of a dispatch table.

    One lollipop awarded for sensible use of anonymous subroutines.

    The group gains three lollipops to award as it deems appropriate.

    Research and discuss: perltraps

    (5 minutes – 16:40)

    Wrap up

    (20 minutes): 16:40

    Are there any questions?

    I'll follow up with the reference solutions next week - I forgot to email them home for formatting clean up before uploading here.

    True laziness is hard work

    Replies are listed 'Best First'.
    Re^2: Design a Perl training day - program and report
    by GrandFather (Saint) on Oct 05, 2011 at 20:52 UTC

      Somewhat more than a week later! However, here are the reference "answers":

      Session 1 Code: Hello World x 10 perl -e "print qq{Hello World\n} x 10" Code: Reverse stuff perl -e "print join ' ', reverse 1 .. 20" perl -e "print scalar reverse join '', 'a' .. 'z'" Code: Reverse less stuff perl -e "@array = 1 .. 20; @array[5 .. 10] = reverse @array[5 .. 10]; +print qq{@array}" Code: Reverse less stuff – string version my $str = join '', 'a' .. 'z'; substr $str, 6, 7, reverse substr $str, 6, 7; say "$str"; Code: Hashy uniqueness Case sensitive version my $name = "Peter Jaquiery"; my %letters; ++$letters{$_} for grep {/[a-z]/i} split '', $name; say join '', sort keys %letters; Case insensitive version my $name = "Peter Jaquiery"; my %letters; $letters{lc $_} //= $_ for grep {/[a-z]/i} split '', $name; say join '', map {$letters{$_}} sort keys %letters; Code: sub – optimal defaults #!/usr/bin/perl use strict; use warnings; use 5.010; test('a'); test('b', 0); test('c', 2, 0); test('peter ', 2, 3); sub test { my ($str, $lines, $reps) = @_; die "No string provided" if ! defined $str; $reps ||= 1; $lines //= 1; say $str x $reps for 1 .. $lines; } Session 2 Code: Go loopy #!/usr/bin/perl use strict; use warnings; use 5.010; my $count = 0; my $bailed; say "Enter 0 to bail."; while (my $target = int rand 11 and say "I have a number from 1 to 10. +") { print "What is your guess: "; my $guess = <>; chomp $guess; if (!$guess) { $bailed = 1; last; } if ($guess == $target) { say "You got it."; next; } say "Your guess is too ", $guess < $target ? "low" : "high"; redo; } continue { ++$count; } say "I'm sick of playing now." if ! $bailed; say "You guessed $count numbers."; Code: An odd hash #!/usr/bin/perl use strict; use warnings; use 5.010; my @array = map {int rand 100} 1 .. 20; my %numbers; push @{$numbers{$_ % 2 ? 'odd' : 'even'}}, $_ for @array; say "@{$numbers{odd}}"; say "@{$numbers{even}}"; Code: Passing Arrays #!/usr/bin/perl use strict; use warnings; use 5.010; my @array = map {int rand 100} 1 .. 20; my %numbers; push @{$numbers{$_ % 2 ? 'odd' : 'even'}}, $_ for sortList (@array); say "@{$numbers{odd}}"; say "@{$numbers{even}}"; sub sortList { return sort {$a <=> $b} @_; } Code: Passing Arrays by reference #!/usr/bin/perl use strict; use warnings; use 5.010; my @array = map {int rand 100} 1 .. 20; my %numbers; push @{$numbers{$_ % 2 ? 'odd' : 'even'}}, $_ for @array; $_ = sortList ($_) for @numbers{'odd', 'even'}; say "@{$numbers{odd}}"; say "@{$numbers{even}}"; sub sortList { return [sort {$a <=> $b} @{$_[0]}]; } Code: What the hash! my %hash = (1 .. 20); say join ' ', theSub(%hash); sub theSub {return @_;} Code: Hash sorted my %hash = (1 .. 20); say join ' ', theSub(%hash); sub theSub { my %hash = @_; return map {$_, $hash{$_}} sort {$a <=> $b} keys %hash; } Code: Reversing old school my @array = 1 .. 20; my @reversed; unshift @reversed, shift @array while @array; say "@reversed"; Session 3 Code: Normalise a file path (my $path = 'c:\This\That\something\else') =~ s!\\!/!g; say $path; Code: More normalise a file path (my $path = 'This:That\something/else') =~ s![:\\]!/!g; say $path; Code: Lines of numbers my @lines = split '\n', `perldoc perlrequick`; say for grep {/\d/} @lines; Code: This or that my @lines = split '\n', `perldoc perlrequick`; say for grep {/(or|not)\s+more\b/} @lines; Code: Just this or that my @lines = split '\n', `perldoc perlrequick`; say for map {/(or|not)\s+more\b/; $1} grep {/(or|not)\s+more\b/} @line +s; Code: play it again Sam my @lines = split '\n', `perldoc perlrequick`; say for grep {/\b(\w+)\s+\1/} @lines; Code: you wear that in public? my @apparel; while (defined(my $line = <DATA>)) { chomp $line; my ($name, @items) = split /\s*,\s*/, $line; while (@items >= 3) { my %row; @row{qw{name item colour material}} = ($name, splice @items, 0 +, 3); push @apparel, \%row; } } for my $row (grep {$_->{colour} eq 'blue'} @apparel) { printf "%-10s %-10s %-10s\n", @{$row}{'name', 'item', 'material'}; } __DATA__ Peter,shirt,white,cotton,trousers,black,cotton,shoes,black,leather,bum + bag,black,synthetic Ina,shirt,grey,cotton,jeans,blue,denim,shoes,light grey,leather Phil,T-shirt,blue,synthetic,jeans,blue,denim,sneakers,blue,synthetic Code: Nasty sentences #!/usr/bin/perl use strict; use warnings; use 5.010; use Lingua::EN::Sentence; my $para = <<PARA; Sentences can be hard to parse. Take the following for example: "Mr. Jones works for I.B.M. ... or maybe not.". (Not so easy eh?) So, did this get it right? PARA my $sentences = Lingua::EN::Sentence::get_sentences ($para); say "$_\n" for map {s/\n+/ /g; $_} @$sentences; Session 4 Code: Create a password perl -e "@chrs = ('a'..'z', 'A'..'Z', 0 .. 9); print ((map {$chrs[rand + @chrs]} 1 .. 10), qq{\n})" Code: reprise - you wear that in public? use DBI; my $dbh = DBI->connect ("dbi:SQLite:Test.sqlite"); die "connect failed: $DBI::errstr" if ! $dbh; $dbh->{AutoCommit} = 0; $dbh->{RaiseError} = 1; $dbh->{PrintError} = 0; my $sql = qq{CREATE TABLE People ( name VARCHAR(100), item VARCHAR(30), colour VARCHAR(30), material VARCHAR(50) ) }; eval { $dbh->do ($sql); $dbh->commit (); return 1; } or do { $dbh->rollback (); die "Failed to create table: $@\n"; }; $sql = qq{INSERT INTO People (name, item, colour, material) VALUES (?, + ?, ?, ?)}; my $entries = 0; eval { my $sth = $dbh->prepare ($sql); while (defined (my $line = <DATA>)) { chomp $line; my ($name, @items) = split /\s*,\s*/, $line; $sth->execute ($name, splice @items, 0, 3) while @items >= 3; } $dbh->commit (); return 1; } or do { my $err = $@ || "Unknown error inserting data"; eval {$dbh->rollback ()} or $err .= "\n Rollback processing fail +ed!"; die $err; }; __DATA__ Peter,shirt,white,cotton,trousers,black,cotton,shoes,black,leather,bum + bag,black,synthetic Ina,shirt,grey,cotton,jeans,blue,denim,shoes,light grey,leather Phil,T-shirt,blue,synthetic,jeans,blue,denim,sneakers,blue,synthetic Code: mix and match use DBI; my $dbh = DBI->connect ("dbi:SQLite:Test.sqlite"); die "connect failed: $DBI::errstr" if ! $dbh; $dbh->{AutoCommit} = 0; $dbh->{RaiseError} = 1; $dbh->{PrintError} = 0; my $sql = qq{SELECT name, item, material FROM People WHERE colour LIKE + ? order by name}; eval { my $sth = $dbh->prepare ($sql); $sth->execute ('blue'); while (my $row = $sth->fetchrow_hashref ()) { printf "%-10s %-10s %-10s\n", @{$row}{'name', 'item', 'materia +l'}; } }; Code: dir, ls or something else my $secPerDay = 60 * 60 * 24; opendir my ($scan), '.' or die "opendir failed: $!\n"; while (defined(my $entry = readdir $scan)) { next if !-f $entry; printf "%-20s %10d bytes %s\n", $entry, -s $entry, scalar localtime($^T + $secPerDay * -M $entry); } Code: Simple calculator #!/usr/bin/perl use strict; use warnings; use 5.010; my %kOps = ( '*' => sub {return $_[0] * $_[1];}, '+' => sub {return $_[0] + $_[1];}, '-' => sub {return $_[0] - $_[1];}, '/' => \&div, ); while ((print "Expression: ") and defined(my $line = <>)) { chomp $line; my @parts = grep {/\S/} split / |\b/, $line; last if !@parts; eval { unshift @parts, process(\@parts) while @parts > 1; return 1; } or do { print $@; next; }; if (!@parts) { say "No result calculated for: $line"; next; } say "$line = $parts[0]"; } sub process { my ($parts) = @_; my ($lhs, $op, $rhs) = splice @$parts, 0, 3; die "Bad expression syntax.\n" if !defined $rhs || $lhs !~ /^\d+$/ || $rhs !~ /^\d+$/; die "Not an operator: $op\n" if !exists $kOps{$op}; return $kOps{$op}->($lhs, $rhs); } sub div { my ($lhs, $rhs) = @_; die "Divide by zero\n" if !$rhs; return $lhs / $rhs; }
      True laziness is hard work