Peter’s blog ✴ Week 350 ✴ 1 December 2025

THE WEEKLY CHALLENGE
Good pairs

The Perl Camel

Task 1

Good substrings

You are given a string. Write a script to return the number of good substrings of length three in the given string. A string is good if there are no repeated characters.

Examples


Example 1
Input: $str = 'abcaefg'
Output: 5
Good substrings of length 3: abc, bca, cae, aef and efg

Example 2
Input: $str = 'xyzzabc'
Output: 3
Good substrings of length 3: 'xyz', 'zab' and 'abc'

Example 3
Input: $str = 'aababc'
Output: 1
Good substrings of length 3: 'abc'

Example 4
Input: $str = 'qwerty'
Output: 4
Good substrings of length 3: 'qwe', 'wer', 'ert' and 'rty'

Example 5
Input: $str = 'zzzaaa'
Output: 0

Analysis

This time I have gone for efficiency and looked for a solution that never repeats a comparison. For example, if the string is αβγδ, the obvious solution is to test α ne β and α ne γ and β ne γ and then move on to β ne γ and β ne δ and γ ne δ.

Note that we have therefore calculated β ne γ twice. In any sensible scenario that is unlikely to matter, but I have saved a few microseconds by coming up with a solution that avoids repeating any comparison.

The essence of this is that we first compare β and γ. If these are the same we know that the triplets starting at α and the one starting at β are bad, and we can move on to looking at the triplet starting at γ.

Otherwise we check whether α and β differ, and if they do, whether α and γ differ, and if these are both true we have a good substring.

This algorithm never repeats a comparison and, I believe, never performs an unnecessary comparison.

Lastly, I start my solution by converting the string into an array. I did that because $chars[$p] is easier to read than substr($chars, $p, 1). Past experiments have shown that there is negligible difference in performance between these two approaches.

Perl Weekly’s review

from Perl Weekly issue 750

Both solutions showcase excellent Perl craftsmanship with thoughtful comments, clear variable naming, and robust handling of edge cases. Peter demonstrates both theoretical understanding (mathematical bounds, algorithmic complexity) and practical implementation skills.

Try it 

Try running the script with any input:



example: committeemeeting

Script


#!/usr/bin/perl

# Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge

use v5.26;    # The Weekly Challenge - 2025-12-01
use utf8;     # Week 350 - task 1 - Good substrings
use warnings; # Peter Campbell Smith
binmode STDOUT, ':utf8';
use Encode;

good_substrings('abcaefg');
good_substrings('xyzzabc');
good_substrings('aababc');
good_substrings('qwerty');
good_substrings('zzzaaa');

sub good_substrings {
    
    my (@chars, $p, $good, $explain);
    
    # initialise
    @chars = split('', $_[0]);
    
    # loop over chars
    $p = 0;
    while ($p < @chars - 2) {
        
        # skip forward 2 if these two match
        if ($chars[$p + 1] eq $chars[$p + 2]) {
            $p ++;

        # result!
        } elsif ($chars[$p] ne $chars[$p + 1] and $chars[$p] ne $chars[$p + 2]) { 
            $good ++;
            $explain .= qq['$chars[$p]$chars[$p + 1]$chars[$p + 2]', ];
        }
        $p ++;
    }
    
    say qq[\nInput:  '$_[0]'];
    say qq[Output: ] . ($good ? qq[$good - ] . substr($explain, 0, -2) : 0);
}

13 lines of code

Output from script


Input:  'abcaefg'
Output: 5 - 'abc', 'bca', 'cae', 'aef', 'efg'

Input:  'xyzzabc'
Output: 3 - 'xyz', 'zab', 'abc'

Input:  'aababc'
Output: 1 - 'abc'

Input:  'qwerty'
Output: 4 - 'qwe', 'wer', 'ert', 'rty'

Input:  'zzzaaa'
Output: 0

 

Any content of this website which has been created by Peter Campbell Smith is in the public domain