Perl Weekly Challenge: Strings Will Tear Us Apart

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.

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"

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 /r option 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::mesh requires 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 split function that emulates awk and not only splits on whitespace, but removes leading whitespace before doing so.
  • List::AllUtils::pairs returns a blessed object with key and value methods.
  • substr takes 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)
end

View 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.

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!!!"

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 a Regex that excludes multiples of ten, and a Range (which is Cool) as $inputcomb stringifies the Range before applying .comb on the resulting string. Check Str.comb for 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)
end

View 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

Leave a Reply