# Perl Weekly Challenge 248: enjoy nested loops!

This post presents my solutions to the Perl Weekly Challenge 248.
I keep doing the Perl Weekly Challenge in order to mantain my coding skills in good shape, as well as in order to learn new things, with particular regard to Raku, a language that I love.
This week, I solved the following tasks:
The PL/Perl implementations are very similar to a pure Perl implementation, even if the PostgreSQL environment could involve some more constraints. Similarly, the PL/PgSQL implementations help me keeping my PostgreSQL programming skills in good shape.

# Raku Implementations

## PWC 248 - Task 1 - Raku Implementation

The first task was about finding the distance, in terms of indexes, from a given char in a string of chars. Therefore, given a string and a char, the script must print out how many places (left or right) the given character is distant from the closest char in the string.

``````sub MAIN( Str :\$string, Str :\$char where { \$char.chars == 1 } ) {

my @distances;
my @matching;
my @letters = \$string.comb;

@matching.push: \$_ if ( \$char eq @letters[ \$_ ] ) for 0 ..^ @letters.elems;

for 0 ..^ @letters.elems -> \$me {
@distances[ \$me ] = 0 and next if ( \$char ~~ @letters[ \$me ] );
@distances[ \$me ] = @matching.map( { abs( \$_ - \$me ) } ).min;
}

@distances.join( ', ' ).say;
}

``````

The implementation first of all searches for all the indexes that contains the given character, and stores them in the `@matching` array. Then I loop over all the `@letters` of the given string and, in the case is not equal to the searched for character, computes the `min` distance from any of the given indexes. To compute the minimum distance, I `map` the `@matching` indexes with the `abs` value of the indexes itself minus the current letter index.

## PWC 248 - Task 2 - Raku Implementation

Given a matrix, compute a matrix where each element is the sum of the `2x2` matrix with the top-leftmost corner placed in the current element.

``````sub MAIN() {
my \$a = [
[1,  2,  3,  4],
[5,  6,  7,  8],
[9, 10, 11, 12]
];

my \$b = [];
for 0 ..^ \$a.elems - 1 -> \$row {
\$b[ \$row ].push: [];
for 0 ..^ \$a[ \$row ].elems - 1 -> \$col {
\$b[ \$row ][ \$col ] = \$a[ \$row ][ \$col ] + \$a[ \$row ][ \$col + 1 ] + \$a[ \$row + 1 ][ \$col ] + \$a[ \$row + 1 ][ \$col + 1 ];
}
}

\$b.join( "\n" ).say;
}

``````

I haven’t placed code for reading the matrix as input, and to print it out as a well formatted matrix, but it works!

# PL/Perl Implementations

## PWC 248 - Task 1 - PL/Perl Implementation

Same implementation as in Raku, but uses a inner `\$min` function to compute the min of a given set of values to do the same `map` trick.

``````CREATE OR REPLACE FUNCTION
RETURNS TABLE( c char, distance int )
AS \$CODE\$
my ( \$string, \$char ) = @_;

die "Use a single char!" if ( length( \$char ) > 1 );
die "Not matching at all!" if ( \$string !~ / \$char /x );

my \$min = sub {
my \$current_min = \$_[ 0 ];
shift;
for ( @_ ) {
\$current_min = \$_ if ( \$_ < \$current_min );
}

return \$current_min;
};

my @distances;
my @matching;

my @letters = split //, \$string;
for ( 0 .. @letters - 1 ) {
next if \$letters[ \$_ ] ne \$char;
push @matching, \$_;
}

for my \$me ( 0 .. @letters - 1 )  {

\$distances[ \$me ] = 0 if ( \$letters[ \$me ] eq \$char );
\$distances[ \$me ] = \$min->( map { abs( \$me - \$_ ) } @matching ) if ( \$letters[ \$me ] ne \$char );

return_next( { c => \$letters[ \$me ],
distance => \$distances[ \$me ] } );

}

return undef;

\$CODE\$
LANGUAGE plperl;

``````

## PWC 248 - Task 2 - PL/Perl Implementation

Same implementation as in Raku.

``````CREATE OR REPLACE FUNCTION
RETURNS int[]
AS \$CODE\$
my ( \$matrix ) = @_;

my \$result = [];

for my \$row ( 0 .. \$matrix->@* - 1 ) {
\$result->[ \$row ] = [];

for my \$col ( 0 .. \$matrix->@* - 1 ) {
\$result->[ \$row ][ \$col ] = \$matrix->[ \$row ][ \$col ] + \$matrix->[ \$row ][ \$col + 1 ] + \$matrix->[ \$row + 1 ][ \$col ] + \$matrix->[ \$row + 1 ][ \$col + 1 ];
}
}

return \$result;

\$CODE\$
LANGUAGE plperl;

``````

# PostgreSQL Implementations

## PWC 248 - Task 1 - PL/PgSQL Implementation

I use a temporary table to store the letters and their indexes, and then use a query to compute the minimum distance.

``````CREATE OR REPLACE FUNCTION
pwc248.task1_plpgsql( s text, c char )
RETURNS TABLE ( cc char, distance int )
AS \$CODE\$
DECLARE
BEGIN
CREATE TEMPORARY TABLE IF NOT EXISTS distances( cc char, ind int );
TRUNCATE distances;

INSERT INTO distances
SELECT v, row_number() over ()
FROM regexp_split_to_table( s, '' ) v;

RETURN QUERY
SELECT d.cc, ( SELECT min( abs( d2.ind - d.ind ) )
FROM distances d2
WHERE d2.cc = c )
FROM distances d;
END
\$CODE\$
LANGUAGE plpgsql;

``````

## PWC 248 - Task 2 - PL/PgSQL Implementation

Implementation similar to the PL/Perl one. Surprisingly, this implementation is quite short, and comparable in code size to PL/Perl.

``````CREATE OR REPLACE FUNCTION
RETURNS SETOF int[]
AS \$CODE\$
DECLARE
current_row int[];
BEGIN

FOR r IN 1 .. array_length( matrix, 1 ) - 1 LOOP

current_row = array[ array_length( matrix, 1 ) ];

FOR c IN 1 .. array_length( matrix, 2 ) - 1 LOOP
current_row[ c ] = matrix[ r ][ c ] + matrix[ r ][ c + 1 ] + matrix[ r + 1 ][ c ] + matrix[ r + 1 ][ c + 1 ];
END LOOP;

RETURN NEXT current_row;
END LOOP;

RETURN;
END
\$CODE\$
LANGUAGE plpgsql;

``````

# Python Implementations

## PWC 248 - Task 1 - Python Implementation

Implementation similar to the Raku one, with the only difference in the need to `append` data to the array.

``````import sys

def main( argv ):
string = argv[ 0 ]
char   = argv[ 1 ]

matching = []
for i in range( 0, len( string ) ):
c = string[ i ]
if c == char:
matching.append( i )

distances = []
for i in range( 0, len( string ) ):
if string[ i ] == char:
distances.append( 0 )
else:
distances.append( min( map( lambda x: abs( i - x ), matching ) ) )

print( ', '.join( map( str, distances ) ) )

# invoke the main without the command itself
if __name__ == '__main__':
main( sys.argv[ 1: ] )

``````

## PWC 248 - Task 2 - Python Implementation

Implementation that reflects the Raku one.

``````import sys

def main( argv ):
matrix = argv
output = []

for r in range( 0, len( matrix ) - 1 ):

output.append( [] )

for c in range( 0, len( matrix[ r ] ) - 1 ):
output[ r ].append( matrix[ r ][ c ] + matrix[ r ][ c + 1 ] + matrix[ r + 1 ][ c ] + matrix[ r + 1 ][ c + 1 ] )

print( "\n".join( map( str, output ) ) )

# invoke the main without the command itself
if __name__ == '__main__':
matrix = [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
]

matrix = [
[1,  2,  3,  4],
[5,  6,  7,  8],
[9, 10, 11, 12]
]

main( matrix )

``````

The article Perl Weekly Challenge 248: enjoy nested loops! has been posted by Luca Ferrari on December 18, 2023