Perl Weekly Challenge # 19: Weekends and Wrapping Lines

These are some answers to the Week 19 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in several days from now (August 4, 2019). This blog post offers some solutions to this challenge, please don't read on if you intend to complete the challenge on your own.

Challenge # 1: Five Weekends in a Month

Write a script to display months from the year 1900 to 2019 where you find 5 weekends i.e. 5 Friday, 5 Saturday and 5 Sunday.

This time, I'll start with Perl 6, because the built-in Date type seems to make it easier.

Five Weekends in Perl 6

My first idea was to loop over each month in the range 1900-2019. For each month, find the first Friday and then count the number of Sundays after that in the month. Then, even before I started to code that, it came to my mind that I didn't need to loop over all the days of the month, but just to count how many days there were in the month after the first Friday: there will five weekends if there are more than 29 days (4 weeks plus 1 day) after that first Friday of the month.

use v6;

for 1900..2019 -> $year {
    for 1..12 -> $month {
        my $day = 1;
        my $date = Date.new($year, $month, $day);
        my $last-day-of-month = 
            $date.later(month => 1).earlier(day => 1);
        ++$date until $date.day-of-week == 5;
        say $year, "-", $month.fmt("%02d"), " has 5 weekends" 
            if $last-day-of-month - $date > 29;
    }
}

That works fine:

1901-03 has 5 weekends
1902-08 has 5 weekends
1903-05 has 5 weekends
1904-01 has 5 weekends
...
(lines omitted for brevity)
...
2016-01 has 5 weekends
2016-07 has 5 weekends
2017-12 has 5 weekends
2019-03 has 5 weekends

Then I started to check the result and looked at a calendar, and it became obvious to me that it is actually even much simpler than that: to have 5 full weekends (Friday through Sunday), a month needs to have 31 days (so January, March, May, etc.) and to start with a Friday. So this is my new simpler script:

use v6;

for 1900..2019 -> $year {
    for 1, 3, 5, 7, 8, 10, 12 -> $month {
        say "$year-{$month.fmt("%02d")} has 5 weekends." 
            if Date.new($year, $month, 1).day-of-week == 5;
    }
}

This prints the same as before, there no point repeating the output.

Five Weekends in Perl 5

I originally thought that solving the challenge in Perl 5 would require either complex computations or the use of sophisticated modules such as Date::Calc or Time::Piece. Given the simplification found in the course of solving the problem in Perl 6, we only need the Time::Local core module, which provides functions that are the inverse of built-in localtime() and gmtime() functions.

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;
use Time::Local;

for my $year (0..119) {
    for my $month ( 1, 3, 5, 7, 8, 10, 12) {
        my $date = timegm (0, 0, 0, 1, $month - 1, $year);
        say $year + 1900, "-", sprintf("%02d ", $month)
            if (gmtime $date)[6] == 5;
    }
}

This program produces the same output as the P6 solution above.

Wrapping Lines

Write a script that can wrap the given paragraph at a specified column using the greedy algorithm.

For this, we will suppose our wrap subroutine receives a line of text as a parameter and return a string wrapped to fit a certain maximal width. We will also suppose that the input text is plain ASCII, some changes may have to be done for Unicode text.

Wrapping Lines in Perl 6

The most typical way to solve such a problem is usually to split the input into words and to add words to a line until it goes over the maximal width; at this point, the script removes the last word, produces the line and starts the new iteration with the removed word.

I tend to think it will be more efficient to look for a space backward (with the rindex built-in function) from the maximal length position, because this will be doing less string manipulations:

use v6;

sub wrap (Str $line is copy, Int $width) {
    my $out = '';
    while ($line) {
        return $out ~ "$line\n" if $line.chars < $width;
        my $pos = rindex $line, ' ', $width - 1;
        $out = $out ~ substr($line, 0, $pos) ~ "\n";
        $line = substr $line, $pos+1;
    }
    return $out;
}

my $in = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dolor sed viverra ipsum nunc aliquet bibendum enim. In massa tempor nec feugiat. Nunc aliquet bibendum enim facilisis gravida. Nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Amet luctus venenatis lectus magna fringilla.";

say wrap $in, $_ for 60, 35;

This produces the following output:

$ perl6 wrap.p6
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Dolor sed viverra ipsum nunc aliquet bibendum enim.
In massa tempor nec feugiat. Nunc aliquet bibendum enim
facilisis gravida. Nisl nunc mi ipsum faucibus vitae
aliquet nec ullamcorper. Amet luctus venenatis lectus magna
fringilla.

Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut
labore et dolore magna aliqua.
Dolor sed viverra ipsum nunc
aliquet bibendum enim. In massa
tempor nec feugiat. Nunc aliquet
bibendum enim facilisis gravida.
Nisl nunc mi ipsum faucibus vitae
aliquet nec ullamcorper. Amet
luctus venenatis lectus magna
fringilla.

Wrapping Lines in Perl 5

Since Perl 5 also has the rindex built-in function, we can just do the same in Perl 5:

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;

sub wrap {
    my ($line, $width) = @_;
    my $out = '';
    while ($line) {
        return $out . "$line\n" if length $line < $width;
        my $pos = rindex $line, ' ', $width - 1;
        $out = $out . substr($line, 0, $pos) . "\n";
        $line = substr $line, $pos+1;
    }
    return $out;
}

my $in = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dolor sed viverra ipsum nunc aliquet bibendum enim. In massa tempor nec feugiat. Nunc aliquet bibendum enim facilisis gravida. Nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Amet luctus venenatis lectus magna fringilla.";
say wrap $in, $_ for 60, 35;

This produces the same output as the Perl 6 implementation, so I won't repeat it here.

Wrapping up

The next week Perl Weekly Challenge is due to start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on Sunday, August 11. And, please, also spread the word about the Perl Weekly Challenge if you can.

And see you hopefully at the European Perl Conference in Riga, Aug. 7 to 9, 2019.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.