Perl Weekly Challenge 138: split working days

It is sad that, after more than two years of me doing Raku, I still don’t have any production code project to work on. Therefore, in order to keep my coding and Raku-ing (is that a term?) knowdledge, I try to solve every Perl Weekly Challenge tasks.

In the following, the assigned tasks for Challenge 138.


And this week, as for the previous PWC, I had time to quickly implement the tasks also on PostgreSQL plpgsql language:

PWC 138 - Task 1

The first task was about computing how many working days there are in a given year. A working day is a day that is not either a Sunday or a Saturday.
I decided to implement it as huge loop:

sub MAIN( Int $year where { $year ~~ / \d ** 4 / } && $year > 1900,
        Bool :$verbose = False ) {
    my Date $date .= new: year => $year, day => 1, month => 1;
    my Date $stop .= new: year => $year, day => 31, month => 12;
    my $work-days = 0;
    while ( $date <= $stop ) {
        $work-days += 1 if $date.day-of-week != any( 6, 7 );
        $date = $date + 1;
    }

    "$year has $work-days work days".say if $verbose;
    $work-days.say if ! $verbose;
}


The idea is that $date contains the current date, starting from the very first day of the year. I don’t take into account holidays. The $stop represents the last day of the year, so my idea is to iterate one day at time from $start to $stop. If $date represents a weekly day that is not either 6 or 7 (the Raku Date representation of Saturday and Sunday), I increment the $work-days counter. Note that I use a junction to quickly test a double expressed condition.

PWC 138 - Task 2

The second task was assigning a perfect square number, and the program has to find out if the square of the number can be split as a combination of the digits of the number itself.
My solution is not ideal, but should work in many cases.

sub MAIN( Int:D $n where { $n > 0 } ) {
    my $qrt = $n.sqrt;
    my @digits = $n.split( '', :skip-empty );

    # short circuit: esay case
    1.say and exit if @digits.sum == $qrt;

    # try to aggregate from left to right
    for 1 ..^ @digits.elems {
        last if @digits[ 0 .. $_ ].join.Int > $qrt;
        '1'.say and exit if @digits[ 0 .. $_ ].join + @digits[ $_ + 1 .. * - 1 ].sum == $qrt;
    }
    
}




I compute the sqrt of the given input number, that I assign to the $qrt variable (there is no s because the $ sigil appears as an s). I then extract every @digits and look if the sum of single digits provides the square root, and this case I can stop the program.
Otherwise, I try to aggregate the digits from left to right only (thus, this is not ideal) and see if the sum of the slices of the @digits array provides me the searched for result.

PWC 138 - Task 1 in PostgreSQL plpgsql

The implementation in sql is not a mirror of the Raku solution because PostgreSQL provides a generate_series() function that can be used to generate a range of dates, in this case from the very beginning to the very end of the year incrementing one day at a time.

CREATE OR REPLACE FUNCTION
  f_working_days_per_year( yy int default extract( year from current_date ) )
  RETURNS int
AS $CODE$
  SELECT count( v )
  FROM generate_series( make_date( yy, 01, 01 ),
                       make_date( yy, 12, 31 ),
                       '1 days' ) v
  WHERE
     extract( dow from v ) NOT IN ( 0, 6 );
  $CODE$
  LANGUAGE sql;
                                                             
                                                             


It does then suffice to count all the resulting rows (i.e., days) that are not Sundays or Saturdays (respectively 6 and 0 in DOW) to get the final result.

PWC 138 - Task 2 in PostgreSQL plpgsql

The second task is a plpgsql re-implementation of the Raku solution.

CREATE OR REPLACE FUNCTION
  f_split_numbers( n int default 9801 )
  RETURNS int
AS $CODE$
  DECLARE
  sqrt int := sqrt( n );
  digits int[] := regexp_split_to_array( n::text, '' );
  aggregation int := 0;
  sum_left int := 0;
  sum_right int := 0;
BEGIN
  RAISE DEBUG 'Operating for % (sqrt = %)', n, sqrt;

  FOR aggregation IN 1 .. length( n::text ) LOOP
    RAISE DEBUG 'Aggregation index %', aggregation;

    SELECT array_to_string( digits[1:aggregation], '' )::int
           , sum( r )
      FROM ( SELECT unnest( digits[ aggregation + 1: length( n::text ) ] ) AS r ) rr
      INTO sum_left, sum_right;

    RAISE DEBUG '% + %', sum_left, sum_right;

    IF ( sum_left + sum_right ) = sqrt THEN
      RETURN 1;
    END IF;
  END LOOP;

  RETURN 0;
END
  $CODE$
  LANGUAGE plpgsql;


                                                                                                                                                                                          



There is a cast here and there from int to text and viceversa to make the whole thing work, but the idea is to split the given number into its digits (text) and then aggreate them from left to right. The difference from the Raku implementation is that, in SQL, arrays start at index 1. Please note also the use of unnest, that translates an array into a tabular data, so that the sum function can be used.

The article Perl Weekly Challenge 138: split working days has been posted by Luca Ferrari on November 8, 2021