Advent Calendar - December 24, 2024

Tuesday, Dec 24, 2024| Tags: Perl, Raku

Advent Calendar 2024

|   Day 23   |   Day 24   |   Day 25   |


The gift is presented by Jaldhar H. Vyas. Today he is talking about his solution to The Weekly Challenge - 299. This is re-produced for Advent Calendar 2024 from the original post.



Perl Weekly Challenge: Week 299


Challenge #1: Replace Words

You are given an array of words and a sentence.

Write a script to replace all words in the given sentence that start with any of the words in the given array.

Perl first for a change and it’s the easiest challenge we’ve had for a long time.

All we have to do is for each word in @words


for my $word (@words) {

Replace any instance of that word in $sentence followed by one or more instances of any "word" character (i.e. alphanumeric or punctuation) with the word by itself.

    $sentence =~ s/$word\w+/$word/g;
}

And print out the result.

say $sentence;

(Full code on Github.)

The only real change we need to make for Raku is that function arguments are immutable so we have to give $sentence the is copy role.

sub MAIN(
    $sentence is copy,
    *@words
) {
    for @words -> $word {
        $sentence ~~ s:g/$word\w+/$word/;
    }

    say $sentence;
}

(Full code on Github.)


You are given a grid of characters and a string.

Write a script to determine whether the given string can be found in the given grid of characters. You may start anywhere and take any orthogonal path, but may not reuse a grid cell.

This one is a little more complicated.

First we break up $str into a List of individual characters.

my @string = $str.comb;

Each element of @chars is also turned into a List of characters. and added to @grid.

my @grid;

for @chars -> $row {
    @grid.push($row.comb);
}

We need a place to store the result. Initially it is assumed to be False.

my $found = False;

$current represents the first character in $str.

my $current = @string.shift;

Now we visit every cell in the @grid.

for 0 ..^ @grid.elems -> $row {
    for 0 ..^ @grid[$row].elems -> $col {

If the cell contains the $current letter (i.e. the first letter in $str,) we can proceed with searching for the rest. If it doesn’t, we move on to the next cell.

        if @grid[$row;$col] eq $current {

Assuming this was the correct cell, we set up a SetHash to hold the positions of any cells we have already seen in this search. This fulfills the specs’ requirement ‘…may not reuse a grid cell’.

            my %visited is SetHash[Str];

We call the traverse() function recursively (explained below) and if it return True, we set $found to True and stop processing.

            if traverse(@grid, @string, %visited, $row, $col) {
                $found = True;
                last;
            }
        }
    }
}

Finally we print the value of $found.

say $found;

traverse() is a function that takes the @grid, the list of characters originally in $str, the set of visited cells, and the row and column of the current cell as parameters.


sub traverse(@grid, @string, %visited, $row, $col) {

We are going to need to know the horizontal and vertical neighboring cells of the one we are in. It is a state (what C++ would call static) variable so we don’t have to recreate it every time the function is called.

state @directions = ([-1, 0], [0,  1], [1,  0], [0, -1]);

If @string is empty, it means we have successfully found the target string within @grid so we can return True and leave the function.

unless @string.elems {
    return True;
}

If not, first we mark this cell as visited.

%visited.set("$row;$col");

Because parameters are immutable, we cant make direct changes to @stringso we first copy it to a new variable imaginativly named @newstring. The first character from it is removed and made the current letter we are searching for.

my @newstring = @string;
my $current = @newstring.shift;

For each direction in our @directions list…

for @directions -> $dir {

…we use it to create the co-ordinates of a neighbor of the current cell.

    my ($newRow, $newCol) = ($row, $col) Z+ @$dir;

If this neighbor is:

within the bounds of @grid.

if $newRow ~~ 0 ..^ @grid.elems && $newCol ~~ 0 ..^ @grid[0].elems &&

not yet visited.

"$newRow;$newCol"  %visited &&

contains the $current letter we are searching for.

@grid[$newRow;$newCol] eq $current {

We recursively call traverse() again.

        return traverse(@grid, @newstring, %visited, $newRow, $newCol);
    }
}

If none of the neighbors contained the character we are searching for, we return False.

return False;
}

(Full code on Github.)

The Perl version uses the same algorithm. We are using the new experimental true and false constants in the newest Perl versions so we have to include these two lines at the top of the script:

use builtin qw/ true false /;
no warnings 'experimental::builtin';

my @string = split //, $str;
my @grid;

for my $row (@chars) {
    push @grid, [split //, $row];
}

my $found = false;
my $current = shift @string;

for my $row (0 .. scalar @grid - 1) {
    for my $col (0 .. scalar @{$grid[$row]} - 1) {
        if ($grid[$row]->[$col] eq $current) {

Because we don’t have Sets in Perl, %visited is a plain old hash.

            my %visited;
            if (traverse(\@grid, \@string, \%visited, $row, $col)) {
                $found = true;
                last;
            }
        }
    }
}

say $found ? 'true' : 'false';

traverse() also works almost the same as in Raku. Every hash or array has to be passed and accessed as a reference which is a bit annoying.

sub traverse($grid, $string, $visited, $row, $col) {
    state @directions = ([-1, 0], [0,  1], [1,  0], [0, -1]);

    unless (scalar @{$string}) {
        return true;
    }

    $visited->{"$row;$col"} = true;
    my @newstring = @{$string};
    my $current = shift @newstring;

    for my $dir (@directions) {
        my $newRow = $row + $dir->[0];
        my $newCol = $col + $dir->[1];

        if ($newRow >= 0 && $newRow < scalar @{$grid} &&
            $newCol >= 0 && $newCol <  scalar $grid->[0] &&
            !$visited->{"$newRow;$newCol"} &&

One little thing which tripped me up is that in Perl, I also have to check if a cell exists before attempting to access it otherwise an unsightly warning is emitted.

            defined $grid->[$newRow]->[$newCol] &&
            $grid->[$newRow]->[$newCol] eq $current
        ) {
            return traverse($grid, \@newstring, $visited, $newRow, $newCol);
        }
    }

    return false;
}

(Full code on Github.)



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