Perl Weekly Challenge 347: string mangling
This post presents my solutions to the Perl Weekly Challenge 347.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:
- PWC 347 - Task 1 - Raku
- PWC 347 - Task 2 - Raku
- PWC 347 - Task 1 in PostgreSQL PL/Perl
- PWC 347 - Task 2 in PostgreSQL PL/Perl
- PWC 347 - Task 1 in PostgreSQL PL/PgSQL
- PWC 347 - Task 2 in PostgreSQL PL/PgSQL
- PWC 347 - Task 1 in PL/Java
- PWC 347 - Task 2 in PL/Java
- PWC 347 - Task 1 in Python
- PWC 347 - Task 2 in Python
Raku Implementations
PWC 347 - Task 1 - Raku Implementation
The first task was about to convert a date in the form1st Dec 2025 into an ISO format 2025-12-01:
sub MAIN( Str $date ) {
my %months = 'Jan' => 1,
'Feb' => 2,
'Mar' => 3,
'Apr' => 4,
'May' => 5,
'Jun' => 6,
'Jul' => 7,
'Aug' => 8,
'Sep' => 9,
'Oct' => 10,
'Nov' => 11,
'Dec' => 12;
if ( $date ~~ / ^ $<day> = ( \d{1 .. 2} ) ( 'st' | 'nd' | 'rd' | 'th' ) \s+ $<month> = ( \w+ ) \s+ $<year> = ( \d+ ) $ / ) {
say '%04d-%02d-%02d'.sprintf: $/<year>, %months{ $/<month> }, $/<day>;
}
}
I do a regexp named capture for all the parts and reconstruct the date in the right format.
PWC 347 - Task 2 - Raku Implementation
The second task was about rebuilding a phone string separating it into groups made from three digits and, in the case the last group is not a two or three digits, recombine it with the last-to-last group.sub MAIN( Str $phone is copy ) {
$phone .= subst( / \s+ /, '', :g );
$phone .= subst( / <[-]> /, '', :g );
my @groups = $phone.comb( :skip-empty ).rotor( 3, :partial );
@groups .= map( *.join );
if @groups[ * - 1 ].chars != 2|3 {
my $adjust = @groups[ * - 2 ] ~ @groups[ * - 1 ];
@groups.pop;
@groups.pop;
@groups.push: |$adjust.comb.rotor( 2 ).map( *.join );
}
@groups.join( '-' ).say;
}
First of all I remove all spaces and dashes from the
$phone string. Then I split it into @groups thanks to rotor in groups of three elements.
If the last group has a wrong length, I build a string concatenating the last two groups, then split into two parts of two digits each.
PL/Perl Implementations
PWC 347 - Task 1 - PL/Perl Implementation
Same implementation as in Raku, I use named captures.CREATE OR REPLACE FUNCTION
pwc347.task1_plperl( text )
RETURNS date
AS $CODE$
my ( $date ) = @_;
my %months = ( 'Jan' => 1,
'Feb' => 2,
'Mar' => 3,
'Apr' => 4,
'May' => 5,
'Jun' => 6,
'Jul' => 7,
'Aug' => 8,
'Sep' => 9,
'Oct' => 10,
'Nov' => 11,
'Dec' => 12 );
if ( $date =~ / ^ (?<day>\d{1,2}) \D{2} \s+ (?<month>\D{3}) \s+ (?<year>\d{4}) $ /x ) {
return sprintf '%04d-%02d-%02d', $+{year}, $months{ $+{month} }, $+{day};
}
else {
elog( WARN, "No valid date format" );
return undef;
}
$CODE$
LANGUAGE plperl;
PWC 347 - Task 2 - PL/Perl Implementation
Similar to the Raku implementation, I build an array of@groups of digits, and in the case the last group has a wrong length, I join it with the previous group and split it again into two digits strings:
CREATE OR REPLACE FUNCTION
pwc347.task2_plperl( text )
RETURNS text
AS $CODE$
my ( $phone ) = @_;
$phone =~ s/ [- ] //gx;
my @groups;
while ( length( $phone ) >= 3 ) {
push @groups, join( '', ( ( split( //, $phone ) )[ 0 .. 2 ] ) );
$phone =~ s/ ^ .{3} //xg;
}
if ( $phone ) {
push @groups, $phone;
}
if ( length( $groups[ $#groups ] ) != 2 || length( $groups[ $#groups ] ) != 3 ) {
my ( $end, $begin ) = ( pop @groups, pop @groups );
my $last = $begin . $end;
push @groups, join( '', ( split( //, $last ) )[ 0 .. 1 ] );
push @groups, join( '', ( split( //, $last ) )[ 2 .. 3 ] );
}
return join( '-', @groups );
$CODE$
LANGUAGE plperl;
PostgreSQL Implementations
PWC 347 - Task 1 - PL/PgSQL Implementation
I use a lookup table for the months and reconstruct the date.CREATE OR REPLACE FUNCTION
pwc347.task1_plpgsql( d text )
RETURNS date
AS $CODE$
DECLARE
x text[];
r text;
BEGIN
CREATE TEMPORARY TABLE IF NOT EXISTS months( i int, m text );
TRUNCATE TABLE months;
INSERT INTO months( i, m )
VALUES
( 1, 'Jan' )
, ( 2, 'Feb' )
, ( 3, 'Mar' )
, ( 4, 'Apr' )
, ( 5, 'May' )
, ( 6, 'Jun' )
, ( 7, 'Jul' )
, ( 8, 'Aug' )
, ( 9, 'Sep' )
, ( 10, 'Oct' )
, ( 11, 'Nov' )
, ( 12, 'Dec' );
r := '';
x := regexp_matches( d, '(\d+).{2}\s+(\D{3})\s+(\d{4})' );
SELECT x[ 3 ] || '-' || m.i || '-' || x[ 1 ]
INTO r
FROM months m
WHERE m = x[ 2 ];
RETURN r;
END
$CODE$
LANGUAGE plpgsql;
PWC 347 - Task 2 - PL/PgSQL Implementation
Here a cheat a little: in the case the last group is of the wrong size, since Iām not so good at handling arrays into PL/PgSQL, I call the PL/Perl part.CREATE OR REPLACE FUNCTION
pwc347.task2_plpgsql( phone text )
RETURNS text
AS $CODE$
DECLARE
x text;
r text;
i int;
groups text[];
BEGIN
i := 0;
FOREACH x IN ARRAY regexp_split_to_array( phone, '' ) LOOP
IF x = '-' OR x = ' ' THEN
CONTINUE;
END IF;
RAISE INFO 'x = %', x;
IF r IS NULL THEN
r := x;
ELSE
r := r || x;
END IF;
i := i + 1;
IF i = 3 THEN
groups := groups || r;
r := NULL;
i := 0;
END IF;
END LOOP;
IF i <> 0 THEN
groups := groups || r;
END IF;
IF length( groups[ array_length( groups, 1 ) ] ) THEN
RETURN pwc347.task2_plperl( phone );
END IF;
RETURN array_to_string( groups, '-' );
END
$CODE$
LANGUAGE plpgsql;
Java Implementations
PWC 347 - Task 1 - PostgreSQL PL/Java Implementation
Here I use another trick: the day part of the string is converted into its numeric part by replacing all the suffixes, without having to check if they are present in the string. @Function( schema = "pwc347",
onNullInput = RETURNS_NULL,
effects = IMMUTABLE )
public static final String task1_pljava( String date ) throws SQLException {
logger.log( Level.INFO, "Entering pwc347.task1_pljava" );
Map<String, Integer> months = new HashMap<String, Integer>();
months.put( "Jan", 1 );
months.put( "Feb", 2 );
months.put( "Mar", 3 );
months.put( "Apr", 4 );
months.put( "May", 5 );
months.put( "Jun", 6 );
months.put( "Jul", 7 );
months.put( "Aug", 8 );
months.put( "Sep", 9 );
months.put( "Oct", 10 );
months.put( "Nov", 11 );
months.put( "Dec", 12 );
String[] parts = date.split( "\\s+" );
return String.format( "%04d-%02d-%02d",
Integer.parseInt( parts[ 2 ] ),
months.get( parts[ 1 ] ),
Integer.parseInt( parts[ 0 ].replace( "st", "" ).replace( "nd", "" ).replace( "rd", "" ).replace( "rh", "" ) ) );
}
PWC 347 - Task 2 - PostgreSQL PL/Java Implementation
Same implementation as PL/Perl: I build groups and check the last group length to rejoin it with the previous one. public static final String task2_pljava( String phone ) throws SQLException {
logger.log( Level.INFO, "Entering pwc347.task2_pljava" );
phone = phone.replaceAll( "-", "" ).replaceAll( " ", "" );
List<String> groups = new LinkedList<String>();
String current = "";
for( int i = 0; i < phone.length(); i++ ) {
current += phone.charAt( i );
if ( current.length() % 3 == 0 ) {
groups.add( current );
current = "";
}
}
if ( current.length() > 0 ) {
groups.add( current );
}
if ( groups.get( groups.size() - 1 ).length() != 2
|| groups.get( groups.size() - 1 ).length() != 3 ) {
String begin = groups.get( groups.size() - 2 );
String end = groups.get( groups.size() - 1 );
groups.remove( groups.size() - 1 );
groups.remove( groups.size() - 1 );
String last = begin + end;
groups.add( "" + last.charAt( 0 ) + last.charAt( 1 ) );
groups.add( "" + last.charAt( 2 ) + last.charAt( 3 ) );
}
String result = "";
for ( String s : groups ) {
result += s + "-";
}
return result;
}
Python Implementations
PWC 347 - Task 1 - Python Implementation
Similar to PL/Java: I split the string and remove all the suffixes from the day part.import sys
import re
# task implementation
# the return value will be printed
def task_1( args ):
months = { 'Jan' : 1,
'Feb' : 2,
'Mar' : 3,
'Apr' : 4,
'May' : 5,
'Jun' : 6,
'Jul' : 7,
'Aug' : 8,
'Sep' : 9,
'Oct' : 10,
'Nov' : 11,
'Dec' : 12 }
r = re.compile( r'\s+' )
parts = r.split( args[ 0 ] )
return '-'.join( [ parts[ 2 ],
str( months[ parts[ 1 ] ] ),
parts[ 0 ].replace( 'st', '' ).replace( 'nd', '' ).replace( 'rd', '' ).replace( 'th', '' ) ] )
# invoke the main without the command itself
if __name__ == '__main__':
print( task_1( sys.argv[ 1: ] ) )
PWC 347 - Task 2 - Python Implementation
Same implementation as PL/perl.import sys
# task implementation
# the return value will be printed
def task_2( args ):
phone = args[ 0 ].replace( '-', '' ).replace( ' ', '' )
groups = []
while len( phone ) > 0:
if len( phone ) >= 3:
groups.append( phone[ 0 : 3 ] )
phone = phone[ 3: ]
else:
groups.append( phone )
phone = ''
if len( groups[ -1 ] ) != 2 and len( groups[ -1 ] ) != 3:
begin = groups[ -2 ]
end = groups[ -1 ]
last = begin + end
groups[ -2 ] = last[ 0 : 2 ]
groups[ -1 ] = last[ 2 : 4 ]
return '-'.join( groups )
# invoke the main without the command itself
if __name__ == '__main__':
print( task_2( sys.argv[ 1: ] ) )