Endings, Elephants and Explanations : Raku Weekly Challenge : Week 329

Week 329 : 2025-06-30

Endings

Before I go on I've just like to say RIP MST I met him a couple of times and interacted with him a few more. But I worked with his code a lot and it's made my life and the world better. I hope he can be remembered with positivity.

Elephants : Part 1

So for our first part of the challenge we are given a string made of lower case letters and numbers, so I'll quickly whip up a ValidInput to check for that.


subset ValidInput of Str where /^ <[a..z 0..9]>+ $/;
      

I'll also put together a test suite that I can run using the -t or --test flags. (One of those things I'd forgotten about in previos code if you check it out. Doh.


multi sub MAIN(:t(:$test)) is hidden-from-USAGE {
    use Test;
    is counter-string("the1weekly2challenge2"), (1,2);
    is counter-string("go21od1lu5c7k"), (21,1,5,7);
    is counter-string("4p3e2r1l"), (4,3,2,1);
    done-testing;
}
      

There's a couple of things I'lll point out here, the use of is hidden-from-USAGE which means that the test option won't appear in the auto generated usage output. I find it quite helpful and was very happy when it was added to Raku. Also we only load the Test module within our test suite which is nice it only exists within that lexical scope.

Ok enough faffing around what do we ween to do? Swap all the letters with spaces and then return a list of numbers. Taking into account that numbers in a group, like the 21 in the second test, are one integer. Also we only count any number in the list once, so there is only 1 number 2 in the first example. So this is simple enough.


sub counter-string( ValidInput $in is copy ) {
    $in ~~ s:g/ (<[a..z]>) / /;
    return $in.split(/" "+/).grep(* !~~ "").unique.map(*.Int);
}
      

So we take our string and replace each letter in it with a space, using the :g adverb to replace each match. Then we split the string on spaces, remove empty strings and throw out duplicates. Finally (though we don't really have to) we cast the results to the Int Class because that's what it asked for an I was in literal mode. Finally we can make a nice MAIN block to wrap this.


#|(Given a string made of lowercase letter and numbers
print a list of the unique numbers in it in order)
multi sub MAIN (
    ValidInput $str #= A string made up of lowercase letters and number
) {
    counter-string($str).join(", ").say;
}
      

But what, you might ask has this got to do with Elephants? Well there's one in the room and a couple of things I needed to bear in mind. After submitting my PR for this weeks challenge on Monday I saw there were a few other Raku submissions so I took a gander and all the ones I looked at had done the same thing for part one.


$str.comb( /\d+/ ).unique;
      

And as soon as I saw that I realised two things, firstly I finally understood comb. I've pretty much only ever used it as a way to split a string into it's parts. But now I see what it's doing, making a list of all the matching items. But I also realised I'd got to literal, a problem that I'm sure we've all fallen into at times, I got caught up attempting to replicate the exact description of the requirements (with replacing letters with spaces) and not focussing on the end result (find all the unqiue integers in the string). I'd missed the Elephant in the room, and it's not that big a room I do this in. Oh well, live and learn I guess.

Explanations : Part 2

So this time we've got a valid input defined as a string of upper and lower case letters so I'll quickly whip up a subset for it and a test suite.


# Tests fired if called with test parameter
multi sub MAIN(:t(:$test)) is hidden-from-USAGE {
    use Test;
    is nice-string("YaaAho"), "aaA";
    is nice-string("cC"), "cC";
    is nice-string("A"), "";
    done-testing;
}

subset ValidInput of Str where m/^ <[a..z A..Z]>+ $/;
      

So our object is to remove from the string any letter that is not found in both upper and lower case within the string. So as my brain often does it looked at this and thought "This looks like a job for Sets!". My brain is a strange thing at times. But it also had a point.

If we get a list of the letters in the string. Then get two sets, one being the difference between the letters and the uppercase letters and the second being the difference between the lettrs and the lowercase letters we can then check each letter of the alphabet in turn. If it's found in it's two different cases* within the sets then we note this in a hash.

After that is' a simple case of grepping for letters that appear in the hash and Ta Da!


sub nice-string( ValidInput $str ) {
    my @letters = $str.comb;
    my $upper = @letters ∩ ("A".."Z");
    my $lower = @letters ∩ ("a".."z");
    my %both;
    for "a".."z" -> $str {
        if ($str.uc ∈ $upper) && ($str ∈ $lower) {
            %both{$str} = True;
            %both{$str.uc} = True;
        }
    }
    return @letters.grep( { %both{$_} } ).join("");
}

#|(Given a string made of upper and lower case
letter print the string with any letters not
included in both cases removed)
multi sub MAIN(
    ValidInput $str #= A String made of lower and uppercase letters
) {
    nice-string($str).say;
}
      

So there we go I explained my code but there is another explanation to be made, whay have I changed the title structure for this blog. Well I blame Mohammad, specifically the wonderful Mohammad Anwar who has done an amazing job running the Weekly Challange for years now. You see there's a part in the challenge each week listing blog posts... but it's specifically Blogs with Creative Title so of course I had to rise to this second challenge and up my blogging and title game. And there we go, a bit more rambling and a snappy title. I hope you have a great weekend and are looking forward to next weeks challenge.

An Aside

While I was writing that section of the post my brain went off on a tangent about why it's called upper and lower case and I figured I'd tip that hat to one of my favourite authors and pop it here as footnote. For those of you who don't know when print was arranged by hand using movable type the letters were arranged in two large wooden cases. In the largest lower case you had the smaller letters, these were easier for the typesetter to reach. If he needed a capital letter he would reach up to the smaller upper case to get one.

But erudite readers that you are I'm sure you knew that.