Advent Calendar - December 3, 2024

Monday, Dec 2, 2024| Tags: Perl

Advent Calendar 2024

|   Day 2   |   Day 3   |


The gift is presented by Peter Campbell Smith. Today he is talking about his solution to The Weekly Challenge - 253. This is re-produced for Advent Calendar 2024 from the original post.



Split the weakest



Task 1: Split Strings


You are given an array of strings and a character separator.

Write a script to return all words separated by the given character, excluding empty ones.

Analysis


So this looks easy:


$text = join($separator, @words);
$text =~ s|$separator+|$separator|g;
@output = split(/$separator/, $text);

Looks as if it will do the job. But there are a couple of complications! Firstly, @output may contain an empty word at the beginning or the end if - as in Example 2 - there are one or more separators at the beginning or end of $text. We need a shift or a pop on @output to remove those.

Secondly, Mr Anwar has cunningly given us examples where $separator has a special meaning in Perl code ($) or in a regular expression (.), so we need to be a bit careful with the split and use split(/\Q$separator\E+/, $text) because the usual interpretation of (most) special characters is suppressed between \Q and \E. Note that the + has to come after the \E because we do want it to mean one or more separators, not just a literal '+'.

I then wondered what happens if $separator is a backslash. As you’ll see from the examples in my code, that works provided you enter it as a double backslash in the code ('\\'). That’s only the case if, as I have done, you are supplying the data within your Perl code: if you were reading it in, a single backslash should work … and yes, I just tried it and it does work.

And it works too if any of the characters are utf8 multibyte ones - see my last example.


#!/usr/bin/perl

# Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge

use v5.26;    # The Weekly Challenge - 2024-01-22
use utf8;     # Week 253 task 1 - Split strings
use strict;   # Peter Campbell Smith
use warnings;
binmode STDOUT, ':utf8';

split_strings(['one.two.three','four.five','six'], '.');
split_strings(['$perl$$', '$$raku$'], '$');
split_strings(['xonex', 'xtwox'], 'x');

# some edge cases
split_strings([',,,,,'], ',');
split_strings(['\\three\\blind\\', '\\mice\\'], '\\');
split_strings(['ŐőŕŒœŔŕŖ', 'ŗŘřŚŕ'], 'ŕ');

sub split_strings {

    my (@words, $separator, $text, @output);

    # initialise
    @words = @{$_[0]};
    $separator = substr($_[1] . ' ', 0, 1); # default is blank

    # join the input strings together with single separators
    $text = join($separator, @words);
    $text =~ s|\Q$separator\E+|$separator|g;

    # split that into individual words
    @output = split(/\Q$separator\E+/, $text);

    # remove an empty first or last word
    shift @output if (@output > 0 and $output[0] eq '');
    pop @output if (@output > 0 and $output[-1] eq '');

    # publish results
    say qq[\nInput:  \@words = ('] . join(qq[', '], @words) . qq[')];
    say qq[        \$separator = '$separator'];
    say qq[Output: ('] . join(q[', '], @output) . qq[')];
}

Output



Input:  @words = ('one.two.three', 'four.five', 'six')
        $separator = '.'
Output: ('one', 'two', 'three', 'four', 'five', 'six')

Input:  @words = ('$perl$$', '$$raku$')
        $separator = '$'
Output: ('perl', 'raku')

Input:  @words = ('xonex', 'xtwox')
        $separator = 'x'
Output: ('one', 'two')

Input:  @words = (',,,,,')
        $separator = ','
Output: ('')

Input:  @words = ('\three\blind\', '\mice\')
        $separator = '\'
Output: ('three', 'blind', 'mice')

Input:  @words = ('ŐőŕŒœŔŕŖ', 'ŗŘřŚŕ')
        $separator = 'ŕ'
Output: ('Őő', 'ŒœŔ', 'Ŗ', 'ŗŘřŚ')

Task 2: Weakest Row



You are given an m x n binary matrix, ie only 0 and 1 where 1 always appear before 0. A row i is weaker than a row j if one of the following is true:

- The number of 1s in row i is less than the number of 1s in row j.
- Both rows have the same number of 1 and i < j.

Write a script to return the order of rows from weakest to strongest.

Analysis



It could be said that its handling of matrices is not one of Perl's greater strengths, but this task is fairly straightforward in that we treating the matrix simply as a list of rows.

My approach is:

First loop over the rows constructing a 'score' for each, which is a string 'nnnn-rrrr' where nnnn is the number of 1s and rrrr is the row number, both padded to 4 characters with 0s. The scores are stored as the keys of a hash, %scores.

Second, I iterate over sort keys %scores. These come out in the required order, that is, sorted first by the count of 1s and then by the row number. I build an array @order of the row numbers and create another hash %legend keyed on the row number giving the explanatory 'Row r contains n ones' data.

I suppose this could be done in a single loop, but we’re only looping (twice) over the number of rows in a matrix, which isn’t going to be a massive number.


#!/usr/bin/perl

# Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge

use v5.26;    # The Weekly Challenge - 2024-01-22
use utf8;     # Week 253 task 2 - Weakest row
use strict;   # Peter Campbell Smith
use warnings;
binmode STDOUT, ':utf8';

weakest_row([[1, 1, 0, 0, 0],
             [1, 1, 1, 1, 0],
             [1, 0, 0, 0, 0],
             [1, 1, 0, 0, 0],
             [1, 1, 1, 1, 1]]);

weakest_row([[1, 0, 0, 0],
             [1, 1, 1, 1],
             [1, 0, 0, 0],
             [1, 0, 0, 0]]);

sub weakest_row {

    my ($matrix, $row, $ones, %scores, $row_number, $cell,%legend, @order);

    $matrix = shift;

    # count the ones in each row and construct %scores
    for $row (@$matrix) {
        $ones = 0;
        $ones += $_ for @$row;
        $scores{sprintf('%04d-%04d', $ones, $row_number ++)} = 1;
    }

    # list the number of 1s in each row and create @order
    for $row (sort keys %scores) {
        $row =~ m|(\d+)-(\d+)|;
        ($ones, $row_number) = ($1, $2);
        $legend{$row_number} = sprintf("Row %d contains %d one%s",
            $row_number, $ones, $ones == 1 ? '' : 's');
        push @order, $row_number + 0;
    }

    # show the results
    print_matrix(qq{Input:  [}, $matrix, 1);
    say qq[Output: (] . join(', ', @order) . ')';

    for $row (sort keys %legend) {
        say qq[        $legend{$row}];
    }
}

sub print_matrix {

    my ($legend, $matrix, $j, $out, $max);

    ($legend, $matrix, $max) = @_;

    # format rows of matrix with numbers of equal width
    $out = '';
    for $j (0 .. @$matrix - 1) {
        $out .= qq[\n$legend] . join(', ', @{$matrix->[$j]}) . qq(]);
        $legend = (' ' x (length($legend) - 1)) . '[' if $j == 0;
    }
    $out =~ s|(\d+)|sprintf("%${max}d", $1)|ge;
    say qq[$out\n];
}

Output



Input:  [1, 1, 0, 0, 0]
        [1, 1, 1, 1, 0]
        [1, 0, 0, 0, 0]
        [1, 1, 0, 0, 0]
        [1, 1, 1, 1, 1]

Output: (2, 0, 3, 1, 4)
        Row 0 contains 2 ones
        Row 1 contains 4 ones
        Row 2 contains 1 one
        Row 3 contains 2 ones
        Row 4 contains 5 ones

Input:  [1, 0, 0, 0]
        [1, 1, 1, 1]
        [1, 0, 0, 0]
        [1, 0, 0, 0]

Output: (0, 2, 3, 1)
        Row 0 contains 1 one
        Row 1 contains 4 ones
        Row 2 contains 1 one
        Row 3 contains 1 one


If you have any suggestion then please do share with us perlweeklychallenge@yahoo.com.

|   Advent Calendar 2024   |

SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.

Contact with me