This is my response to the Perl Weekly Challenge #39.
A guest house had a policy that the light remain ON as long as the at least one
guest is in the house. There is guest book which tracks all guest in/out time.
Write a script to find out how long in minutes the light were ON.
Guest Book

I'll start with the Guest Book itself, which I have placed in a separate file (so that I can have several versions of it):
File: guestbook.txt1) Alex IN: 09:10 OUT: 09:45
2) Arnold IN: 09:15 OUT: 09:33
3) Bob IN: 09:22 OUT: 09:55
4) Charlie IN: 09:25 OUT: 10:05
5) Steve IN: 09:33 OUT: 10:01
6) Roger IN: 09:44 OUT: 10:12
7) David IN: 09:57 OUT: 10:23
8) Neil IN: 10:01 OUT: 10:19
9) Chris IN: 10:10 OUT: 11:00
Looking at this file gives some clues as to how to handle this:
Then the program
File: guestbookuse Time::Repeat::HHMM; # [9]
unit sub MAIN (:$verbose, # [1]
:$guestbook where $guestbook.IO.r = "guestbook.txt"); # [2]
my $from; # [3]
my $to; # [3]
for $guestbook.IO.lines > $line # [4]
{
die "Wrong file format" unless $line ~~ # [5]
/IN \: \s+ (\d\d) \: (\d\d) \s+ OUT \: \s+ (\d\d) \: (\d\d)/;
############ 5a ###### 5b ################## 5c ###### 5d ###
my $currentfrom = "$0$1"; # [6]
my $currentto = "$2$3"; # [6]
say " Line (from: $currentfrom to: $currentto)" if $verbose; # [1]
if ! $from.defined # [7]
{
$from = $currentfrom; # [7a]
$to = $currentto; # [7b]
}
else
{
$to = $currentto if $currentto > $to; # [8]
}
}
say "From: $from to: $to" if $verbose; # [1]
my $fromo = Time::Repeat::HHMM.new($from); # [9]
my $too = Time::Repeat::HHMM.new($to); # [9]
my $diff = $too.Real  $fromo.Real; # [10]
say "The light were ON for $diff minutes."; # [11]
[1] I have added support for a verbose mode, to help during development (and also to help a user seeing what is going on).
[2] We can specify another guest book if we want. I'll get back to that later.
[3] We have a single continous timespan, and we keep the start and end in these variables.
[4] Read the guest book, line for line.
[5] Abort if the line doesn't match the regex, as we reghard that as an unrecoverable error. (The regex picks out the four double digit parts: in hour (5a), in minutes (5b), out hours (5c) and out minutes (5d). I have done it this way as the colons have to go.)
[6] Set the current from and to values.
[7] This kicks in the first time (as $from
is undefined), sets
the start (7a) and end values (7b) to the current values.
[8] After the first line, increase the $to
value if the new value
is higher.
[9] Calculating the number of minutes between to points in time can be done with the «Time::Repeat::HHMM» class (which is part of the «Time::Repeat» module (github  modules.raku.org) version 0.0.1). We set up two objects, for the start and the end,
[10] and calculate the difference in minutes by subtracting the values. As the
author of the module didn't think about this use case, we have to coerce the
objects to the «Num» type with .Real
for this to work. Bummer...
[11] Print the result.
Note that we do not change the
$from
value after initialising it, as the first entry
on the guest book has the earliest «in» value.
If you disapprove of my critical tone towards the module author, don't worry. I have written the module myself, and can cope with selfcriticism.
Running it:
$ raku guestbook
The light was ON for 110 minutes.
say "From: $from to: $to" if $verbose;
my $diff = ( $to.substr(0,2) * 60 + $to.substr(2,2) )
 ( $from.substr(0,2) * 60 + $from.substr(2,2) );
say "The light was ON for $diff minutes.";
And remove the «use Time::Repeat::HHMM» line.
1) Alex IN: 09:10 OUT: 09:45
2) Arnold IN: 09:15 OUT: 09:33
3) Bob IN: 09:22 OUT: 09:55
4) Charlie IN: 09:25 OUT: 10:05
5) Steve IN: 09:33 OUT: 10:01
6) Roger IN: 09:44 OUT: 10:12
7) David IN: 09:57 OUT: 10:23
8) Neil IN: 10:01 OUT: 10:19
9) Chris IN: 10:10 OUT: 11:00
10) Donald IN: 14:55 OUT: 15:00
Running it:
$ raku guestbook guestbook=guestbookdisjunct.txt
The light was ON for 350 minutes.
That is certainly wrong. The challenge doesn't address this problem, but I'll have a go at it anyway.
Using existing code is a good way, but the «Time::Interval» module isn't quite up to the task. So I rewrote it...
The .Real
calls are annoying, but we can get rid of them if we add a
«Numeric» method:
method Numeric
{
return $.isnextday * 24 * 60 + $.hour * 60 + $.minute;
}
This looks much nicer:
File: guestbookdisjunct (changes only)my $diff = $too  $fromo;
Removing the «Real» method is not a
good idea, as a numeric comparison (e.g. with >
) depends on it.
We don't need it now, but we will later on.
That was nice, but we still haven't addressed the problem of disjunct periods.
We can add to the module. A new «HHMM::Interval» class seems appropriate.
File: Time/Repeat/HHMM/Interval.pm6use Time::Repeat::HHMM;
unit class Time::Repeat::HHMM::Interval:ver<0.0.2> is export;
our $VERSION = '0.02';
has HHMM $.start;
has HHMM $.stop;
multi method new(HHMM $start, HHMM $stop)
{
return self.bless(start => $start, stop => $stop);
}
multi method new(Str $start, Str $stop)
{
return self.bless(start => Time::Repeat::HHMM.new($start),
stop => Time::Repeat::HHMM.new($stop) );
}
method Str
{
return $.start ~ "" ~ $.stop;
}
method Numeric
{
return $.stop  $.start;
}
It doesn't do much yet. We can set up intervals, with the start and stop either specified as strings or «HHMM» objects. The «Str» method ensures that we can print the intervals (and also sort them). The «Numeric» method uses the »Numeric» method in the «HHMM» class to get the duration in minutes.
Now we can start rewriting the program:
File: guestbookdisjunctuse Time::Repeat::HHMM;
use Time::Repeat::HHMM::Interval;
unit sub MAIN (:$verbose,
:$guestbook where $guestbook.IO.r = "guestbook.txt");
my @intervals;
for $guestbook.IO.lines > $line
{
die "Wrong file format" unless
$line ~~ /IN \: \s+ (\d\d) \: (\d\d) \s+ OUT \: \s+ (\d\d) \: (\d\d)/;
my $currentfrom = "$0$1";
my $currentto = "$2$3";
say " Line (from: $currentfrom to: $currentto)" if $verbose;
@intervals.push(Time::Repeat::HHMM::Interval.new($currentfrom,
$currentto));
}
for @intervals > $interval
{
say $interval.Str;
}
The program doesn't do anything useful yet, besides printing the intervals (and showing that the new module does work).
But let us show off a little, with a modified guest book where the entries are shuffled around. The values are the same as before:
File: guestbookdisjunct2.txt1) Alex IN: 09:10 OUT: 09:45
8) Neil IN: 10:01 OUT: 10:19
7) David IN: 09:57 OUT: 10:23
3) Bob IN: 09:22 OUT: 09:55
6) Roger IN: 09:44 OUT: 10:12
4) Charlie IN: 09:25 OUT: 10:05
5) Steve IN: 09:33 OUT: 10:01
9) Chris IN: 10:10 OUT: 11:00
10) Donald IN: 14:55 OUT: 15:00
2) Arnold IN: 09:15 OUT: 09:33
Running it:
$ raku guestbookdisjunct guestbook=guestbookdisjunct2.txt
09100945
10011019
09571023
09220955
09441012
09251005
09331001
10101100
14551500
09150933
That was as expected. But we can get them ordered with a single method call:
File: guestbookdisjunct (changes only)for @intervals.sort > $interval
Running it with this change:
$ raku guestbookdisjunct guestbook=guestbookdisjunct2.txt
09100945
09150933
09220955
09251005
09331001
09441012
09571023
10011019
10101100
14551500
So now we have a way of handling entries in wrong order. (Probably not very useful, but easy to do.)
Now the exciting part, merging intervals. That belongs to our module, as a function:
File: Time/Repeat/HHMM/Interval.pm6 (changes only)our sub merge (Time::Repeat::HHMM::Interval @intervals is copy) # [1]
{
my Time::Repeat::HHMM::Interval @result; # [2]
my $first = @intervals.shift; # [3]
my $start = $first.start; # [3]
my $stop = $first.stop; # [3]
for @intervals.sort > $interval
{
if $interval.start > $stop # [4]
{
@result.push: Time::Repeat::HHMM::Interval.new($start, $stop);
$start = $interval.start;
$stop = $interval.stop;
}
else # [5]
{
$stop = $interval.stop if $interval.stop > $stop;
}
}
@result.push: Time::Repeat::HHMM::Interval.new($start, $stop); # [6]
return @result; # [2]
}
[1] It takes a list of Interval objects,
[2] and returns another list of Interval objects.
[3] This time I pick out the start values before the loop. (Compare it with «guestbook» where I did it in the loop.) Using «shift» on the input requires a writeable list, so we get that with «is copy» in [1].
[4] This comparison coerces the values with the «Real» method (in «HHMM»), so it has to say. If the new interval is disjunct with the current one, push the current to the result list and use the new interval as the current one.
[5] If not disjunct (they overlapp), use the highest stop value.
[6] We are left with an interval after the loop. Add that to the list.
Now we can rewrite the program. Replace the loop at the end with this:
File: guestbookdisjunct (changes only)my Time::Repeat::HHMM::Interval @merged
= Time::Repeat::HHMM::Interval::merge(@intervals);
for @merged > $interval
{
say "The light was ON for { +$interval } minutes (at { $interval.Str }).";
}
Running it:
$ raku guestbookdisjunct guestbook=guestbookdisjunct2.txt
The light was ON for 110 minutes (at 09101100).
The light was ON for 5 minutes (at 14551500).
$ raku guestbookdisjunct guestbook=guestbookdisjunct.txt
The light was ON for 110 minutes (at 09101100).
The light was ON for 5 minutes (at 14551500).
$ raku guestbookdisjunct guestbook=guestbook.txt
The light was ON for 110 minutes (at 09101100).
The three guest book files are handled correctly.
The «modules.raku.org» Indexer hasn't recognised the new version yet. I do not know why.
Write a script to demonstrate Reverse Polish notation(RPN). Checkout the wiki page for more information about RPN. 
The wiki page explains this as a push/pop task on a stack, so I'll do just that.
My first idea was to write it as a loop that reads input from the user, and acts on that. The wikipedia article has an example (where I have replaced the mathematical operator symbols with the normal ascii versions):
RPN> 15 7 1 1 +  / 3 * 2 1 1 + +  =
5
So the program should support both command line arguments, and interactive mode.
Note that specifying an unquoted *
on the command line is not a good idea, as your shell will expand it to a list of all
the files in the current directory. Quoting one or more of these values is not practical,
so the input should be specified as a single string, like this:
$ raku rpn "15 7 1 1 +  / 3 * 2 1 1 + +  ="
5
The program:
File: rpn (partial)my @stack; # [1]
multi sub MAIN (:$verbose) # [2]
{
loop # [2a]
{
my $input = prompt "> "; # [2b]
parse($input, $verbose); # [2c]
}
}
multi sub MAIN (Str $args, :$verbose) # [3]
{
for $args.words > $word # [3a]
{
$word.Numeric # [4]
?? parse($word.Numeric, $verbose) # [4a]
!! parse($word, $verbose); # [4b]
}
}
[1] We store the values in this variable, treated as a stack.
[2] The first «multi sub MAIN» candidate (which could have been written as «multi MAIN» instead, saving a few characters) is used when we run the program without arguments (on the command line). It goes in an eternel loop [2a], prompting for input [2b], and acting on it [2c].
[3] The other «multi sub MAIN» candidate is used when we specify a singles string on the command line. It splits the string (with «words», using spaces as delimiters) [3a].
[4] All the words are strings, and we must coerce any numeric value to a number as the «parse» procedure relies on the type [4a]. If not numeric, pass it on as is [4b].
Note the «verbose» support. The value is passed on to each procedure call, ensuring encapsulation for everything except the stack («@stack»).
The «parse» procedure does the heavy lifting:
File: rpn (partial)sub parse ($input, $verbose)
{
if $input ~~ Numeric # [5]
{
stackpush($input, $verbose);
}
elsif $input eq any( <* / +  ^> ) # [6]
{
my $result = calculate($input, $verbose); # [6a]
say ":  Result: $result" if $verbose; # [6b]
stackpush($result, $verbose); # [6c]
}
elsif $input eq "=" # [7]
{
@stack.elems # [7a]
?? say @stack.pop # [7a]
!! say "Error. Not enough values on the stack"; # [7b]
}
elsif $input eq "s" # [8]
{
say @stack.join(", ");
}
elsif $input eq "h" # [9]
{
say 'Enter a number, an operator (* / +  ** div %), '
~ '= (show the value), s (show the stack)';
}
else # [10]
{
say "Illegal input ($input). Use 'h' for help";
}
}
[5] • If the input is numeric, push it on the stack.
[6] If the input is one of these operators, calculate the new value [6a] and push it on the stack [6c].
[7] • If the input is "=", pop a value from the stack and display it [7a] (or give a warning if the stack is empty [7b]).
[8] • If the input is "s", show the stack. This is primarily useful for debugging.
[9] • If the input is "h", show a short help message.
[10] • if something else, show a warning.
The rest of the åproghram:
File: rpn (partial)sub stackpush ($value, $verbose) # [11]
{
@stack.push: $value;
if $verbose
{
say ": Stack: { @stack.join(", ") }";
say ":  Added: $value";
}
}
sub calculate ($operator, $verbose) # [12]
{
if @stack.elems < 2 # [13]
{
say "Error. Not enough values on the stack";
return;
}
my $right = @stack.pop; # [14]
my $left = @stack.pop; # [14]
if $verbose
{
say ": Operator: $operator";
say ":  Stack: { @stack.join(", ") }";
say ":  Values: $left $right";
}
return $left  $right if $operator eq ""; # [15]
return $left + $right if $operator eq "+";
return $left / $right if $operator eq "/";
return $left * $right if $operator eq "*";
return $left ** $right if $operator eq "^";
die "Unknown operator"; # [16]
}
[11] This is a separate procedure because of the verbose output.
[12] This procedure does the calculations.
[13] We must have at least two values on the stack. If not, display a warning and do nothing.
[14] Get two values from the stack.
[15] Apply the operator on the values, and return the result.
[16] Die in case of error. Note that this cannot happen with this program, as the test in [6] ensures a legal operator.
Runing it in interactive mode:
./rpn
> 12
> 8
> +
> s
20
> 11
> *
> =
220
> ^C
With verbose mode:
$ raku rpn verbose
> 12
:  Add to stack: 12
: Stack: 12
> 8
:  Add to stack: 8
: Stack: 12, 8
> +
: Operator: +
:  Stack:
:  Values: 12 8
:  Result: 20
:  Add to stack: 20
: Stack: 20
> 11
:  Add to stack: 11
: Stack: 20, 11
> *
: Operator: *
:  Stack:
:  Values: 20 11
:  Result: 220
:  Add to stack: 220
: Stack: 220
> =
220
> ^C
The example shown initially does work:
$ raku rpn "15 7 1 1 +  / 3 * 2 1 1 + +  ="
5
And with verbose mode:
$ raku rpn verbose "15 7 1 1 +  / 3 * 2 1 1 + +  ="
:  Add to stack: 15
: Stack: 15
:  Add to stack: 7
: Stack: 15, 7
:  Add to stack: 1
: Stack: 15, 7, 1
:  Add to stack: 1
: Stack: 15, 7, 1, 1
: Operator: +
:  Stack: 15, 7
:  Values: 1 1
:  Result: 2
:  Add to stack: 2
: Stack: 15, 7, 2
: Operator: 
:  Stack: 15
:  Values: 7 2
:  Result: 5
:  Add to stack: 5
: Stack: 15, 5
: Operator: /
:  Stack:
:  Values: 15 5
:  Result: 3
:  Add to stack: 3
: Stack: 3
:  Add to stack: 3
: Stack: 3, 3
: Operator: *
:  Stack:
:  Values: 3 3
:  Result: 9
:  Add to stack: 9
: Stack: 9
:  Add to stack: 2
: Stack: 9, 2
:  Add to stack: 1
: Stack: 9, 2, 1
:  Add to stack: 1
: Stack: 9, 2, 1, 1
: Operator: +
:  Stack: 9, 2
:  Values: 1 1
:  Result: 2
:  Add to stack: 2
: Stack: 9, 2, 2
: Operator: +
:  Stack: 9
:  Values: 2 2
:  Result: 4
:  Add to stack: 4
: Stack: 9, 4
: Operator: 
:  Stack:
:  Values: 9 4
:  Result: 5
:  Add to stack: 5
: Stack: 5
5
And that's it.
Except for a Happy Christmas wish.
And pointing out that the banner image is from Wroclaw in Poland.
But that absolutely is it.