Perl Weekly Challenge 184.

My solutions (task 1 and task 2 ) to the The Weekly Challenge - 184.

Task 1: Sequence Number

Submitted by: Mohammad S Anwar You are given list of strings in the format aa9999 i.e. first 2 characters can be anything ‘a-z’ followed by 4 digits ‘0-9’.

Write a script to replace the first two characters with sequence starting with ‘00’, ‘01’, ‘02’ etc.

Example 1 Input: @list = ( ‘ab1234’, ‘cd5678’, ‘ef1342’) Output: (‘001234’, ‘015678’, ‘021342’) Example 2 Input: @list = ( ‘pq1122’, ‘rs3334’) Output: (‘001122’, ‘013334’)

#+endexample

If I assume that the first two characters are to be replaced without checking at all what they are, there is a very simple solution: I get the arguments from @ARGV, substitute the first two characters by a string using Perl’s magic increment of strings and the capability of executing code in the replacement part of a substitution. The result is a compact oneliner.

perl -E '$i="00"; say join ", ", map {s/../$i++/e; $_} @ARGV' ab1234 cd5678 ef1342
perl -E '$i="00"; say join ", ", map {s/../$i++/e; $_} @ARGV' pq1122 rs3334

Results:

001234, 015678, 021342
001122, 013334

The full code is essentially the same, save for checking that the conditions of the problem are satisfied

 1  # Perl weekly challenge 184
 2  # Task 1:  Sequence Number
 3  #
 4  # See https://wlmb.github.io/2022/09/26/PWC184/#task-1-sequence-number
 5  use v5.36;
 6  die <<"EOF" unless @ARGV;
 7  Usage: $0 s1 [s2 ...]
 8  to replace the first 2 characters of strings S1 S2...
 9  by a two digit increasing code.
10  The strings should be of the form lldddd where l is a letter a..z
11  and d a digit 0..9.
12  EOF
13  die "More than 100 strings" if @ARGV>100;
14  my $counter="00";
15  say join ", ",
16      map {m/^[a-z]{2}\d{4}$/||die "Bad format $_\n"; s/../$counter++/e; $_}
17      @ARGV;

Example:

./ch-1.pl ab1234 cd5678 ef1342
./ch-1.pl pq1122 rs3334

Results:

001234, 015678, 021342
001122, 013334

Examples with mistakes:

./ch-1.pl Ab1234 cd5678 ef1342
./ch-1.pl 001234 cd5678 ef1342
./ch-1.pl ab123 cd5678 ef1342

Results:

Bad format Ab1234
Bad format 001234
Bad format ab123

The first program was completely tolerant. The full version version is dumb and extremely intolerant.

Task 2: Split Array

Submitted by: Mohammad S Anwar
You are given list of strings containing 0-9 and a-z separated by space only.

Write a script to split the data into two arrays, one for integers and one for alphabets only.

Example 1
Input: @list = ( 'a 1 2 b 0', '3 c 4 d')
Output: [[1,2,0], [3,4]] and [['a','b'], ['c','d']]
Example 2
Input: @list = ( '1 2', 'p q r', 's 3', '4 5 t')
Output: [[1,2], [3], [4,5]] and [['p','q','r'], ['s'], ['t']]

I obtain the input from @ARGV and assume it is in the correct format. Then I can split each argument on space and store each character on an array of numbers or letters. I use the new for my(...) construction to iterate over such arrays building an array of arrays of numbers and an array of arrays of letters. Finally, I stringify and print the resulting arrays using a recursive function. The code can be condensed into a somewhat incomprehensible 3-liner.

Example 1:

perl -Mexperimental=for_list -MList::Util=reduce -E '
for my ($N,$L)(map {@n=@l=(); /^[0-9]$/ and push @n, $_ or push @l, $_ for split " ";
[@n], [@l]} @ARGV){ push @N, $N if @$N; push @L, $L if @$L;} say p(@N), " and ", p(@L);
sub p(@X){ "[". join(", ", map{ref $_?p(@$_):$_} @X) ."]"}
' 'a 1 2 b 0' '3 c 4 d'

Results:

[[1, 2, 0], [3, 4]] and [[a, b], [c, d]]

Example 2:

perl -Mexperimental=for_list -MList::Util=reduce -E '
for my ($N,$L)(map {@n=@l=(); /^[0-9]$/ and push @n, $_ or push @l, $_ for split " ";
[@n], [@l]} @ARGV){ push @N, $N if @$N; push @L, $L if @$L;} say p(@N), " and ", p(@L);
sub p(@X){ "[". join(", ", map{ref $_?p(@$_):$_} @X) ."]"}
' '1 2' 'p q r' 's 3' '4 5 t'

Results:

[[1, 2], [3], [4, 5]] and [[p, q, r], [s], [t]]

The full code is

 1  # Perl weekly challenge 184
 2  # Task 2:  Split Array
 3  #
 4  # See https://wlmb.github.io/2022/09/26/PWC184/#task-2-split-array
 5  use v5.36;
 6  use experimental qw(for_list);
 7  use List::Util qw(reduce);
 8
 9  sub arr2str(@X){ # recursively converts array to string
10      "[" . join(", ", map{ref $_ eq "ARRAY"? arr2str(@$_):$_} @X) . "]"
11  }
12
13  die <<EOF unless @ARGV;
14  Usage: $0 S1 [S2...]
15  to split strings S1, S2... into arrays of numbers and letters.
16  Each string should contain space separated single letters a-z and/or digits 0-9.
17  EOF
18  for(@ARGV){ # check arguments
19      die "Wrong format: $_" unless /^[a-z0-9](\s+[a-z0-9])*$/;
20  }
21
22  my (@numbers_arr, @letters_arr); # arrays of array refs
23  for my ($numbers,$letters)( # array refs
24      map { # separate each arg into number and letters array refs.
25          my(@numbers, @letters);
26          /^[0-9]$/ and push @numbers, $_ or push @letters, $_ for split " ";
27  	[@numbers], [@letters]
28     } @ARGV
29  ) {
30     push @numbers_arr, $numbers if @$numbers; # ignore empty arrays
31     push @letters_arr, $letters if @$letters;
32  }
33  say arr2str(@numbers_arr), " and ", arr2str(@letters_arr);

Examples:

./ch-2.pl 'a 1 2 b 0' '3 c 4 d'
./ch-2.pl '1 2' 'p q r' 's 3' '4 5 t'

Results:

[[1, 2, 0], [3, 4]] and [[a, b], [c, d]]
[[1, 2], [3], [4, 5]] and [[p, q, r], [s], [t]]
Written on September 26, 2022