Perl Weekly Challenge 360‘s tasks are “Valid Tag” and “Group Division”.
Really, when I saw the word “Division”, it made me think of the 2026 Rock & Roll Hall of Fame inductees which were announced last night, which included Joy Division and New Order being inducted together. So let’s have some Joy Division to code to: Love Will Tear Us Apart.
Task 1: Valid Tag
You are given a given a string caption for a video.
Write a script to generate tag for the given string caption in three steps as mentioned below:
1. Format as camelCase
Starting with a lower-case letter and capitalising the first letter of each subsequent word.
Merge all words in the caption into a single string starting with a #.
2. Sanitise the String
Strip out all characters that are not English letters (a-z or A-Z).
3. Enforce Length
If the resulting string exceeds 100 characters, truncate it so it is
exactly 100 characters long.
Input: $caption = "Cooking with 5 ingredients!"
Output: "#cookingWithIngredients"
Input: $caption = "the-last-of-the-mohicans"
Output: "#thelastofthemohicans"
Input: $caption = " extra spaces here"
Output: "#extraSpacesHere"
Input: $caption = "iPhone 15 Pro Max Review"
Output: "#iphoneProMaxReview"
Input: $caption = "Ultimate 24-Hour Challenge: Living in a Smart Home controlled entirely by Artificial Intelligence and Voice Commands in the year 2026!"
Output: "#ultimateHourChallengeLivingInASmartHomeControlledEntirelyByArtificialIntelligenceAndVoiceCommandsIn"
Approach
So the approach to this is pretty straightforward: split the string on whitespace into words, capitalize the first letter of all but the first word, concatenate them back together with a hash at the beginning, filter out non-alphabetic characters, and then truncate the string if it’s > 100 characters.
Raku
I set out to make this a one-liner; the bit that initially escaped me was how to have the map apply lc the first word but tclc the remaining words. Eventually, I fount the key: .pairs, which returns a .key/.value pair for each element in the list. The .subst replaces all the non-alphabetic, non-whitespace characters with the empty string, .split splits the string into words, .join connects the words back into a single string, and .substr returns the first 99 characters of the string if it’s > 99 characters. 99? Yes, because then we prepend the # character to the beginning for a max length of 100.
sub validTag($caption) {
'#' ~ $caption.subst(/<-[a..zA..Z\s]>/,:g)
.split(/\s+/, :skip-empty)
.pairs
.map({ .key ?? .value.tclc !! .value.lc })
.join
.substr(0..98);
}View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1:
Input: $caption = "Cooking with 5 ingredients!"
Output: "#cookingWithIngredients"
Example 2:
Input: $caption = "the-last-of-the-mohicans"
Output: "#thelastofthemohicans"
Example 3:
Input: $caption = " extra spaces here"
Output: "#extraSpacesHere"
Example 4:
Input: $caption = "iPhone 15 Pro Max Review"
Output: "#iphoneProMaxReview"
Example 5:
Input: $caption = "Ultimate 24-Hour Challenge: Living in a Smart Home controlled entirely by Artificial Intelligence and Voice Commands in the year 2026!"
Output: "#ultimateHourChallengeLivingInASmartHomeControlledEntirelyByArtificialIntelligenceAndVoiceCommandsIn"Perl
It’s the same approach for Perl, but I’m not able to get it into a single line for a couple reasons:
- I was going to say that
s///returns the number of substitutions made, but then I remembered that the/roption runs the substitution on a copy of the string and instead of returning the number of substitutions, it returns the copy whether or not a substitution occurred. - The big reason is that
List::AllUtils::meshrequires arrays, not lists, so I needed to put the words (and the indices I’m going to mesh with them) into arrays.
But then the rest of the problem can be done in a single statement. Things of note:
- We’re using a special case of the
splitfunction that emulatesawkand not only splits on whitespace, but removes leading whitespace before doing so. List::AllUtils::pairsreturns a blessed object withkeyandvaluemethods.substrtakes an offset and a length, not a range of characters like Raku did, so we need to specify a length of 100.
use List::AllUtils qw( pairs mesh );
sub validTag($caption) {
my @words = split(" ", $caption =~ s/[^a-zA-Z\s]//gr);
my @indices = 0 .. $#words;
my @meshed = mesh @indices, @words;
substr(
'#' . join('',
map { $_->key ? ucfirst(lc($_->value)) : lc($_->value) }
pairs @meshed
),
0, 100
);
}View the entire Perl script for this task on GitHub.
Python
In Python, however, I was able to get it into a single statement again.
import re
def valid_tag(caption):
return ('#' + ''.join([
v.lower() if i==0 else v.title()
for i,v in
enumerate(re.sub(r'[^a-zA-Z\s]', '', caption).split())
]))[0:100]View the entire Python script for this task on GitHub.
Elixir
Similarly, I was able to get the Elixir into a single statement. I particularly liked being able to use Enum.map_join/3, which maps and joins the enumerable in a single statement. In my use of Enum.zip/2, I’m passing the range (where I’m using the number of characters in the caption as an upper bound for the indices) as the second argument, so when I unpack the resulting tuple in line 9, it’s {val, key} instead of {key, val}.
def valid_tag(caption) do
String.slice("#" <> (
Regex.replace(~r/[^a-zA-Z\s]/, caption, "")
|> String.split(" ", trim: true)
|> Enum.zip(0..String.length(caption))
|> Enum.map_join(fn {val, key} ->
if key==0, do: String.downcase(val),
else: String.capitalize(val)
end)
), 0..99)
endView the entire Elixir script for this task on GitHub.
Task 2: Group Division
You are given a string, group size and filler character.
Write a script to divide the string into groups of given size. In the last group if the string doesn’t have enough characters remaining fill with the given filler character.
Input: $str = "RakuPerl", $size = 4, $filler = "*"
Output: ("Raku", "Perl")
Input: $str = "Python", $size = 5, $filler = "0"
Output: ("Pytho", "n0000")
Input: $str = "12345", $size = 3, $filler = "x"
Output: ("123", "45x")
Input: $str = "HelloWorld", $size = 3, $filler = "_"
Output: ("Hel", "loW", "orl", "d__")
Input: $str = "AI", $size = 5, $filler = "!"
Output: "AI!!!"
Approach
This, too, is straightforward. We check the length of the string to see if it’s a multiple of $size characters, and if not, we pad it with $filler until it is. Then we divide the string into $size character chunks.
Raku
I knew how I was going to do this back when I was looking at the .comb documentation when I was thinking about task 1. I noticed towards the end of the section the following:
The second statement exemplifies the first form of
comb, with aRegexthat excludes multiples of ten, and aRange(which isCool) as$input.combstringifies theRangebefore applying.combon the resulting string. CheckStr.combfor its effect on different kind of input strings. When the first argument is an integer, it indicates the (maximum) size of the chunks the input is going to be divided in.
Well, chunking input is what this task is about! I check the length of the input string and pad it out with $filler until it’s a multiple of $size.
sub groupDivision($str is copy, $size, $filler) {
$str ~= $filler while $str.chars % $size;
$str.comb($size)
}View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: $str = "RakuPerl", $size = 4, $filler = "*"
Output: ("Raku", "Perl")
Example 2:
Input: $str = "Python", $size = 5, $filler = "0"
Output: ("Pytho", "n0000")
Example 3:
Input: $str = "12345", $size = 3, $filler = "x"
Output: ("123", "45x")
Example 4:
Input: $str = "HelloWorld", $size = 3, $filler = "_"
Output: ("Hel", "loW", "orl", "d__")
Example 5:
Input: $str = "AI", $size = 5, $filler = "!"
Output: ("AI!!!")Perl
There is a function in Perl that lets us chunk a string, but it takes a little setup: unpack. But we need to build a TEMPLATE to pass it. The template a4a4 chunks the string into two 4-character strings, so all we need to do is repeat a followed by the number of characters in each chunk a number of times we’re going to be chunking it.
sub groupDivision($str, $size, $filler) {
$str .= $filler while length($str) % $size;
my $count = length($str) / $size;
my $template = "a$size" x $count;
unpack $template, $str;
}View the entire Perl script for this task on GitHub.
Python
The think I don’t always use in this solution is calling range() with a step.
def group_division(string, size, filler):
while len(string) % size != 0:
string += filler
return [
string[i:i+size] for i in range(0, len(string), size)
]View the entire Python script for this task on GitHub.
Elixir
In Elixir, I calculated the number of chunks I would need and then used String.pad_trailing/3 to pad it out to the desired length. Range.to_list/1 produced a list of starting positions for the chunks, and String.slice/3 produced the chunks.
def group_division(str, size, filler) do
chunks = ceil(String.length(str) / size)
str = String.pad_trailing(str, chunks * size, filler)
Range.to_list(0..String.length(str)-1//size)
|> Enum.map(fn i -> String.slice(str, i, size) end)
endView the entire Elixir script for this task on GitHub.
Here’s all my solutions in GitHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-360/packy-anderson