Perl Weekly Challenge 374‘s tasks are “Count Vowel” and “Largest Some-digits Number”.
I can’t go back to Count von Count every time we have a counting problem, so I started thinking about “some”. It didn’t take long… my brain started asking “Hey… won’t you play… another some-digits largest vowel count song? And make me feel at home…”
Task 1: Count Vowel
You are given a string.
Write a script to return all possible vowel substrings in the given string. A vowel substring is a substring that only consists of vowels and has all five vowels present in it.
Input: $str = "aeiou"
Output: ("aeiou")
Input: $str = "aaeeeiioouu"
Output: ("aaeeeiioou", "aaeeeiioouu", "aeeeiioou", "aeeeiioouu")
NOTE: Updated output [2025-05-18]
Input: $str = "aeiouuaxaeiou"
Output: ("aeiou", "aeiou", "eiouua", "aeiouu", "aeiouua")
NOTE: Updated output [2025-05-18]
Input: $str = "uaeiou"
Output: ("aeiou", "uaeio", "uaeiou")
Input: $str = "aeioaeioa"
Output: ()
Approach
I feel like this is a tree traversal problem again. My instinct is to first split the string into multiple substrings on non-vowel characters (example 3 would yield "aeiouua" and "aeiou". Each substring would then be tested to make sure that they have all five vowels. If they do, the substring would be added to the output list, and then we would start chopping off characters from each end of the string and re-testing to see if it still has all five vowels (I guess this is not one of the “sometimes” for the letter Y).
Checking to make sure that the string has at least one of the five vowels, I’m going to use a regular expression with positive lookahead. A positive lookahead is a zero-length assertion that particular characters exist after the current pointer in the string. For example, the regex /(?=a)/ will succeed if the character a appears anywhere in the string being matched. How is this different than just using /a/? Because the lookahead assertion is zero-length, it doesn’t consume any characters in the string. It doesn’t really matter if you’re just matching a single character, but what if you wanted to match a string where the character u was followed by the character a somewhere? You could match /u.*a/. But what if you wanted to know if there was a u followed by an a or an a followed by a u? You could match /a.*u|u.*a/. But this gets unwieldy quick: if I wanted to look for all five vowels this way, I would need 120 alternations. But positive lookahead makes this easy: /(?=a)(?=e)(?=i)(?=o)(?=u)/. Each positive lookahead will evaluate to true if the letter we’re looking for appears anywhere in the string ahead of the current match cursor, without moving the match cursor forward, meaning the next lookahead will start at the beginning of the string, too.
Except… that didn’t work. After some more digging, I found out that where I was going wrong (emphasis mine):
Therefore, do not expect the pattern A(?=5) to match the A in the string AB25. Many beginners assume that the lookahead says that “there is a 5 somewhere to the right”, but that is not so. After the engine matches the A, the lookahead (?=5) asserts that at the current position in the string, what immediately follows is a 5. If you want to check if there is a 5 somewhere (anywhere) to the right, you can use (?=[^5]*5).
Moreover, don’t expect the pattern A(?=5)(?=[A-Z]) to match the A in the string A5B. Many beginners assume that the second lookahead looks to the right of the first lookahead. It is not so. At the end of the first lookahead, the engine is still planted at the very same spot in the string, after the A. When the lookahead (?=[A-Z]) tries to assert that what immediately follows the current position is an uppercase letter, it fails because the next character is still the 5. If you want to check that the 5 is followed by an uppercase letter, just state it in the first lookahead: (?=5[A-Z]).
So, since what I want to do is match the vowels anywhere in the string, what I need to do is allow for 0 or more characters to be matched by the lookahead before the vowel I’m seeking. So what I want is /(?=.*a)(?=.*e)(?=.*i)(?=.*o).*u.*/. I don’t need a lookahead for the last vowel because I’ve already matched the previous vowels without moving the position of the regex cursor.
Perl
One thing I don’t need to worry about with my regex is there being characters other than a, e, i, o, or u: my split on line 17 will consume any non-vowel characters and remove them from consideration.
One thing I needed to do, however, was to play with uniq and sort to get the results that were shown in the examples. If we don’t pass the output through uniq on line 12, we wind up with Example 2 yielding the output ("aaeeeiioou", "aaeeeiioouu", "aeeeiioou", "aeeeiioou", "aeeeiioouu") (the entry "aeeeiioou" is duplicated), and if we don’t pass it through uniq on line 18, the output comes out in the wrong order. However , I really feel like the uniq should be on line 18 as well, which would eliminate the duplicated "aeiou" from the output in Example 3.
use List::AllUtils qw/ uniq /;
sub checkSubstrings($str) {
return unless $str =~ /(?=.*a)(?=.*e)(?=.*i)(?=.*o).*u.*/i;
my @output;
push @output, $str;
push @output, countVowel(substr($str, 1));
push @output, countVowel(substr($str, 0, -1));
return uniq @output;
}
sub countVowel($str) {
my @output;
push @output, checkSubstrings($_) for split /[^aeiou]+/, $str;
return sort @output;
}View the entire Perl script for this task on GitHub.
$ perl/ch-1.pl
Example 1:
Input: $str = "aeiou"
Output: ("aeiou")
Example 2:
Input: $str = "aaeeeiioouu"
Output: ("aaeeeiioou", "aaeeeiioouu", "aeeeiioou", "aeeeiioouu")
Example 3:
Input: $str = "aeiouuaxaeiou"
Output: ("aeiou", "aeiou", "aeiouu", "aeiouua", "eiouua")
Example 4:
Input: $str = "uaeiou"
Output: ("aeiou", "uaeio", "uaeiou")
Example 5:
Input: $str = "aeioaeioa"
Output: ()Raku
Of course, Raku has it’s own syntax for lookahead assertions:
m:i/<?before .* a><?before .* e><?before .* i><?before .* o>.*u.*/
Also, I had to be careful to use .append to add entries to the array, because .push will add lists to an array as a single element, instead of adding the individual elements.
sub checkSubstrings($str) {
return [] unless $str ~~ m:i/
<?before .*a><?before .*e><?before .*i><?before .*o>.*u.*
/;
my @output;
@output.append($str);
@output.append(checkSubstrings($str.substr(1)));
@output.append(checkSubstrings($str.substr(0, *-1)));
return @output.unique;
}
sub countVowel($str) {
my @output;
@output.append(checkSubstrings($_))
for $str.split(/<-[aeiou]>/);
return @output.sort;
}View the entire Raku script for this task on GitHub.
Python
In Python, it’s .extend that we use to add elements from a list to a list without making nested lists, but… because a Str object is an iterable in Python, if you .extend a list with a string, you’ll wind up pushing the individual letters onto the list. So that’s why, on line 13, we’re using .append: it takes whatever value it’s given, and adds it to the end of the list.
import re
all_vowels = re.compile(
r'(?=.*a)(?=.*e)(?=.*i)(?=.*o).*u.*', flags=re.I
)
def check_substrings(string):
if not all_vowels.search(string):
return []
output = []
output.append(string)
output.extend(check_substrings(string[1:]))
output.extend(check_substrings(string[:-1]))
return list(set(output)) # unique elements
def count_vowel(string):
output = []
for s in re.split(r'[^aeiou]+', string, flags=re.I):
output.extend(check_substrings(s))
return sorted(output)View the entire Python script for this task on GitHub.
Elixir
In Elixir, I was able to leverage piping to avoid creating temporary variables for things like the results from the split or the list of strings being returned from check_substrings/1.
def check_substrings(str) do
if Regex.match?(~r/(?=.*a)(?=.*e)(?=.*i)(?=.*o).*u.*/, str) do
[ str ]
++ check_substrings(String.slice(str, 1..-1//1))
++ check_substrings(String.slice(str, 0..-2//1))
else
[]
end
|> Enum.uniq
end
def count_vowel(str) do
Regex.split(~r/[^aeiou]+/, str)
|> Enum.reduce([], &(&2 ++ check_substrings(&1)))
|> Enum.sort
endView the entire Elixir script for this task on GitHub.
I think this may be the first time I spent the majority of my time on the Perl and Raku solutions and barely any on the Python and Elixir solutions. I think the reason for that is I started trying to write the Raku solution with my mistaken impression of how positive lookahead was going to work, and then I figured I should write it in Perl because that’s the regex engine I understood better, and then I realized that I didn’t know the regex engine as well as I thought I did. Once I got the regex working, and then realized it would make things simpler if I isolated the recursion into just the bit that checked for vowels and sliced off characters from either end, the Python and Elixir solutions were simply implementations of the algorithm I’d worked out using language structures I already knew pretty well. The only wrinkle was discovering that Python’s .extend treated the Str value as an iterable (because it is), and that git fixed pretty quickly.
Task 2: Largest Same-digits Number
You are given a string containing 0-9 digits only.
Write a script to return the largest number with all digits the same in the given string.
Input: $str = "6777133339"
Output: 3333
Input: $str = "1200034"
Output: 4
Input: $str = "44221155"
Output: 55
Input: $str = "88888"
Output: 88888
Input: $str = "11122233"
Output: 222
Approach
I had to re-read this a few times to understand what the problem was. We’re looking for the largest-numeric-value substring that has all the same digits.
So, this feels like making a single pass through the string, building a substring as we go, and whenever the next character we process isn’t the same as the last character we processed, we start a new substring. Then we test to see if the substring has a larger numeric value than the last substring. If it does, we keep that as the largest value.
Raku
Really, the only glitch I ran into (and I quickly realized what was wrong), was I wasn’t explicitly casting the values in line 12 as .Ints, so I was getting the maximum string value. It was a quick fix.
sub maxSame($str) {
my @digits = $str.comb;
my $current = @digits.shift;
my $largest = $current.Int;
for @digits -> $digit {
$current = $current.starts-with($digit)
?? $current ~ $digit
!! $digit;
$largest = [$current.Int, $largest].max;
}
$largest;
} View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: $str = "6777133339"
Output: 3333
Example 2:
Input: $str = "1200034"
Output: 4
Example 3:
Input: $str = "44221155"
Output: 55
Example 4:
Input: $str = "88888"
Output: 88888
Example 5:
Input: $str = "11122233"
Output: 222Perl
The Perl solution is the same except for
- Needing to import
maxfrom List::AllUtils - Using
split //instead of.comb shiftbeing a function and not a method on theListclass- The ternary syntax being
bool ? true : falseinstead ofbool ?? true !! false - Not having a
.starts-withmethod on theStrclass and needing to use thesubstrfunction.
use List::AllUtils qw( max );
sub maxSame($str) {
my @digits = split //, $str;
my $current = shift @digits;
my $largest = $current + 0;
for my $digit (@digits) {
$current = substr($current,0,1) eq $digit
? $current . $digit
: $digit;
$largest = max($current + 0, $largest);
}
$largest;
}View the entire Perl script for this task on GitHub.
Python
Again, Python strings being iterable was significant here. My first thought was that I could avoid making the string into a list because I could just iterate over it, but then I realized that I needed it to be a list because I was shifting off the first value before the loop. Then it being an iterable was important because I could convert it to a list by using list().
def max_same(string):
digits = list(string)
current = digits.pop(0)
largest = int(current)
for digit in digits:
current = (
current + digit if current[0:1] == digit
else digit
)
largest = max(int(current), largest)
return largestView the entire Python script for this task on GitHub.
Elixir
Again, I liked that I could avoid temporary values in Elixir. hd(digits) gives me the first element from the digits list, and tl(digits) gives me the list without the first element. I didn’t have to assign an initial value for largest outside the Enum.reduce/3 because I’m assigning it in the first value of the tuple I’m passing in as the accumulator on line 8.
def max_same(str) do
digits = String.graphemes(str)
current = hd(digits)
{ largest, _ } = tl(digits) |> Enum.reduce(
{ String.to_integer(current), current },
fn digit, { largest, current } ->
current = if String.starts_with?(current, digit), do:
current <> digit, else: digit
{ max( String.to_integer(current), largest), current }
end
)
largest
endView the entire Elixir script for this task on GitHub.
Here’s all my solutions in GitHub: https://github.com/packy/perlweeklychallenge-club/tree/challenge-374-packy-anderson/challenge-374/packy-anderson