Perl Weekly Challenge 173: sly

It is sad that, after more than three 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 173.

and for the sake of some Perl 5, let’s do some stuff also in PostgreSQL Pl/Perl:
Last, the solutions in PostgreSQL PL/PgSQL:

PWC 173 - Task 1

Decide if a given number is esthetic, that means that every digit from left to right is different from the previous one by one unit:

sub MAIN( Int $n where { $n >= 10 } ) {
    my @digits = $n.comb;
    for 0 ..^ @digits.elems - 1 -> $index {
        "Not esthetic $n".say and exit if abs( @digits[ $index ] - @digits[ $index + 1 ] ) != 1
    }

    "$n is esthetic!".say;
}


The idea is simple: iterate on all possible digits and see if the current one and the rightmost one are separated by one unit. Break the program as soon as this condition is not met.

PWC 173 - Task 2

Compute the Sylverster’s sequence, hence the name of the post to Sly. But, it’s not the same Sylvester!
The sequence is quite simple to compute: bootstraps with 2 and 3, and the continues with the product of all preceeding terms added by one:

sub MAIN( Int $limit = 10 ) {
    my @sly = 2, 3;

    while ( @sly.elems < $limit ) {
        @sly.push: ( [*] @sly ) + 1;
    }

    @sly.join( "\n" ).say;
}



Here I use the [*] reduction operator to quickly compute the product.

PWC 173 - Task 1 in PostgreSQL PL/Perl

Very likely the Raku solution:

CREATE OR REPLACE FUNCTION
pwc173.task1_plperl( int )
RETURNS BOOL
AS $CODE$
my ( $n ) = @_;
return 0 if $n < 10;

my @digits = split //, $n;

for ( 0 .. $#digits - 1 ) {
    return 0 if ( abs( $digits[ $_ ] - $digits[ $_ + 1 ] ) != 1 );
}

return 1;
$CODE$
LANGUAGE plperl;



PWC 173 - Task 2 in PostgreSQL PL/Perl

Same as the Raku solution:

CREATE OR REPLACE FUNCTION
pwc173.task2_plperl( int )
RETURNS SETOF BIGINT
AS $CODE$

my ( $limit ) = @_;

my @sly = ( 2, 3 );

return_next( $sly[ 0 ] );
return_next( $sly[ 1 ] );

while ( $#sly < $limit ) {
      my $new_value = 1;
      $new_value *= $_ for (@sly);
      push @sly, ++$new_value;
      return_next( $new_value );
}

return undef;

$CODE$
LANGUAGE plperl;



One possible problem is that bigint is going out of range, with Perl using the scientific notation that is not understood by bigint.

PWC 173 - Task 1 in PostgreSQL PL/PgSQL

There is a function to do the job:

CREATE OR REPLACE FUNCTION
pwc173.task1_plpgsql( n int )
RETURNS BOOL
AS $CODE$
DECLARE
        d text;
        previous int := -1;
BEGIN
        FOR d IN SELECT regexp_split_to_table( n::text, '' ) LOOP
            IF previous = -1 THEN
               previous = d::int;
               CONTINUE;
            END IF;

            IF abs( previous - d::int ) != 1 THEN
               RETURN FALSE;
            ELSE
                previous = d::int;
            END IF;
        END LOOP;

RETURN TRUE;
END
$CODE$
LANGUAGE plpgsql;


The function keeps track of the previously seen number and exists with a false value as soon as the difference between the current one and the previous one is not 1.
There is also a single query approach:

WITH nums AS
(
        SELECT v::int, v::int - lag( v::int, 1, 0 ) OVER () AS diff
        FROM regexp_split_to_table( 12345::text, '' ) v
)
SELECT NOT EXISTS(
       SELECT *
       FROM nums
       WHERE abs( diff ) <> 1
);



The CTE takes the number and splits it into a table, where lag is used to compute the difference between the number and the previous one (i.e., the one of the previous row). The outer query selects rows where the difference is not of one unit, and in the case such rows exists, the query returns a false value to indicate that the number is not esthetic.

PWC 173 - Task 2 in PostgreSQL PL/PgSQL

Since there is no aggregation for multiplication, I implemented a simple function:

CREATE OR REPLACE FUNCTION
pwc173.task2_plpgsql( n int default 10 )
RETURNS SETOF BIGINT
AS $CODE$
DECLARE
        product bigint;
BEGIN

        product := 2 * 3;
        RETURN NEXT 2;
        RETURN NEXT 3;
        n := n - 2;

        WHILE n > 0 LOOP
              RETURN NEXT ( product + 1 );
              product := ( product + 1 ) * product;
              n := n - 1;
        END LOOP;

RETURN;

END
$CODE$
LANGUAGE plpgsql;



The function does return the bootstrap terms, and then keeps track of the previously computed product, so that it can quickly recompute the next one. As already stated, there could be an overflow with bigintegers and long sequences. Even here it is possible to solve the problem with a single query:

WITH RECURSIVE numbers AS
(
        SELECT 2::numeric AS v, 0::numeric AS p, 1 AS r
        UNION
        SELECT 3 AS v, 6 as p, 2 AS r

        UNION

        SELECT p + 1, (p + 1) * p, r + 1
        FROM numbers
        WHERE p <> 0

)
SELECT v
FROM numbers
LIMIT 10;



The query provides the bootstrap terms 2 and 3, as well as a precomputed product p. Then the recursive term computes the next value as the previous product p added by one, and prepares the next product. The outer query does imposes the limit to avoid PostgreSQL to compute the whole universe!

The article Perl Weekly Challenge 173: sly has been posted by Luca Ferrari on July 11, 2022