Perl Weekly Challenge 138: Workdays and Split Number

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

Spoiler Alert: This weekly challenge deadline is due in a few days from now (on November 14, 2021 at 24:00). This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.

Task 1: Workdays

*You are given a year, $year in 4-digits form.

Write a script to calculate the total number of workdays in the given year.

For the task, we consider, Monday - Friday as workdays.

Example 1:

Input: $year = 2021
Output: 261

Example 2:

Input: $year = 2020
Output: 262

A year has 52 weeks, plus one or two days (when the year is leap). So, we will have 5 * 52 = 260 work days, plus 0, 1, or 2 days. So we basically start with 260 work days and add one if Dec. 31 falls on a week day, plus another work day if the year is leap and if Dec. 30 falls on a week day.

Workdays in Raku

In Raku, we start with Jan. 1 of the input year and add 52 weeks to it. The later of the Dateish role will happily compute the date 52 weeks after Jan. 1 and accurately take care of leap years, so that it will return Dec. 30 on leap years and Dec. 31 on other years. It is then only a matter to find out is these dates fall on week days.

sub work-days (UInt $y) {
    my $new-year-day = Date.new("$y-01-01");
    my $fifty-two-weeks-later = $new-year-day.later(:52weeks);
    my $count = 52 * 5;
    $count++ if 1 <= $fifty-two-weeks-later.day-of-week < 6;
    return $count if $fifty-two-weeks-later.day == 31;
    $count++ if 1 <= $fifty-two-weeks-later.later(:1days).day-of-week < 6;
    return $count;
}
say "$_ -> ", work-days $_ for 2000..2021;

This program displays the following output:

$ raku ./working-days.raku
2000 -> 260
2001 -> 261
2002 -> 261
2003 -> 261
2004 -> 262
2005 -> 260
2006 -> 260
2007 -> 261
2008 -> 262
2009 -> 261
2010 -> 261
2011 -> 260
2012 -> 261
2013 -> 261
2014 -> 261
2015 -> 261
2016 -> 261
2017 -> 260
2018 -> 261
2019 -> 261
2020 -> 262
2021 -> 261

Workdays in Perl

In Perl, we’ll use the core Time::Piece module. We’ll use a slightly different strategy: we start with 5 * 52 = 260 work days, add one if Dec. 31 is a week day, and add one more if the year is leap and if Dec. 30 is a week day.

use warnings;
use feature qw/say/;
use Time::Piece;

sub work_days {
    my $y = shift;
    my $count = 52 * 5;
    my $t = Time::Piece->strptime("$y Dec 31", '%Y %b %e');
    my $last_d = $t->day_of_week;
    $count++ if $last_d > 0 and $last_d < 6;
    if ($t->is_leap_year) {
        my $thirtieth = ($last_d - 1) % 7;
        $count++ if $thirtieth > 0 and $thirtieth < 6;
    }
    return $count;
}
say "$_ -> ", work_days $_ for 2000..2021;

This script displays the following output:

$ perl ./working-days.pl
2000 -> 260
2001 -> 261
2002 -> 261
2003 -> 261
2004 -> 262
2005 -> 260
2006 -> 260
2007 -> 261
2008 -> 262
2009 -> 261
2010 -> 261
2011 -> 260
2012 -> 261
2013 -> 261
2014 -> 261
2015 -> 261
2016 -> 261
2017 -> 260
2018 -> 261
2019 -> 261
2020 -> 262
2021 -> 261

Task 2: Split Number

You are given a perfect square.

Write a script to figure out if the square root the given number is same as sum of 2 or more splits of the given number.

Example 1:

Input: $n = 81
Output: 1

Since, sqrt(81) = 8 + 1

Example 2:

Input: $n = 9801
Output: 1

Since, sqrt(9801) = 98 + 0 + 1

Example 3:

Input: $n = 36
Output: 0

Since, sqrt(36) != 3 + 6

Split Number in Raku

I first thought I could use some combination of the combinations and permutations methods to solve the problem, but this turned out to be more complicated than expected. So I decided to write a recursive partition subroutine to build the various partitions of the input square number and add the various parts. The split-square subroutine checks that the input number is a perfect square and calls the partition subroutine, passing to it a list of the input number digits. Note that we are using dynamic variables ($*found and $*root) to avoid passing them back and forth in the recursive subroutine calls.

use v6;

sub partition (Int $sum is copy, @in) {
    if @in.elems == 0 {
        $*found = 1 if $sum == $*root;
        return;
    }
    for 1..@in.elems -> $i {
        my $new-sum = $sum + join "", @in[0..($i-1)];
        last if $new-sum > $*root;
        partition $new-sum, @in[$i..@in.end];
    }
}

sub split-square (UInt $sq) {
    my $*root = sqrt($sq).Int;
    say "$sq is not a perfect square\n" and return 0 if $*root² != $sq;
    my $*found = 0;
    partition(0, $sq.comb);
    return $*found;
}
for <1 27 81 100 225 1296 6561 9801> -> $test {
    say $test.fmt("%5d\t") , split-square $test;
}

This program displays the following output:

raku ./split-squares.raku 1 1 27 is not a perfect square

   27   0
   81   1
  100   1
  225   0
 1296   1
 6561   0
 9801   1

Split Number in Perl

This is essentially a Perl port of the Raku solution above. We also implement a recursive partition subroutine to build the various partitions of the input square number and add the various parts. The split_square subroutine checks that the input number is a perfect square and calls the partition subroutine, passing to it a list of the input number digits.

use strict;
use warnings;
use feature "say";

my ($found, $root);

sub partition {
    my ($sum, @in) = @_;    
    if (scalar @in == 0) {
        $found = 1 if $sum == $root;
        return;
    }
    for my $i (1..(scalar @in)) {
        my $new_sum = $sum + join "", @in[0..($i-1)];
        last if $new_sum > $root;
        partition ($new_sum, @in[$i..$#in]);
    }
}

sub split_square {
    my $sq = shift;
    $root = int sqrt($sq);
    say "$sq is not a perfect square\n" and return 0 if $root ** 2 != $sq;
    $found = 0;
    partition(0, split //, $sq);
    return $found;
}
for my $test (qw<1 27 81 100 225 1296 6561 9801>) {
    printf "%5d\t%d\n", $test , split_square $test;
}

This program displays the following output:

$ perl  ./split-squares.pl
    1   1
27 is not a perfect square

   27   0
   81   1
  100   1
  225   0
 1296   1
 6561   0
 9801   1

## Wrapping up

The next week Perl Weekly Challenge will 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 November 21, 2021. And, please, also spread the word about the Perl Weekly Challenge if you can.

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.