Perl Weekly Challenge 287: Strong Password

These are some answers to the Week 287, Task 1, of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few days from now (on September 22, 2024, at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 1: Strong Password

You are given a string, $str.

Write a program to return the minimum number of steps required to make the given string very strong password. If it is already strong then return 0.

Criteria:

- It must have at least 6 characters.
- It must contain at least one lowercase letter, at least one upper case letter and at least one digit.
- It shouldn't contain 3 repeating characters in a row.

Following can be considered as one step:

- Insert one character
- Delete one character
- Replace one character with another

Example 1

Input: $str = "a"
Output: 5

Example 2

Input: $str = "aB2"
Output: 3

Example 3

Input: $str = "PaaSW0rd"
Output: 0

Example 4

Input: $str = "Paaasw0rd"
Output: 1

Example 5

Input: $str = "aaaaa"
Output: 3

My strategy will be as follows. If the input string doesn't contain an uppercase letter (we check that with a regex), then we add an uppercase letter (an "A"). We do the same for lowercase letters and for digits. Then we check for sequences of 3 repeated characters and replace the last one with the next letter in the alphabet (or anything that comes next in the ASCII table). Finally, and only then, we check the length. This way, we might have started with a password that was too short, but may have made it longer through the addition of some characters in the previous steps.

Strong Password in Raku

See the previous section for the overall strategy. In Raku, we use the regexes' predefined character classes (or Unicode properties) <:Lu> (uppercase letter) and <:Ll> (lowercase letter) for finding uppercase and lowercase letters. For sequences of 3 repeated characters, we look for any letter followed by two repetitions of it with the /(.)$0**2/ regex; when we find such a sequence, we replace the third character by another (the next one in the alphabet).

sub strong-password ($pwd is copy) {
    my $count = 0;
    # At least one uppercase letter
    $pwd ~= "A" and $count++ if $pwd !~~ /<:Lu>/; 
    # At least one lowercase letter
    $pwd ~= "b" and $count++ if $pwd !~~ /<:Ll>/;
    # At least one digit
    $pwd ~= "3" and $count++ if $pwd !~~ /\d/;
    # no repeating characters
    while $pwd ~~ /(.)$0**2/ {
        my $subst = ($0.ord + 1).chr;
        $pwd ~~ s/(.)$0**2/$0$0$subst/;
        $count++;
    }
    for 'a'..'z' -> $ch {
        last if $pwd.chars >= 6;
        $count++;
        $pwd ~= $ch
    }
    return $count;
} 

my @tests = <a aB2 PaaSW0rd Paaasw0rd aaaaa foob>;
for @tests -> $test {
    printf "%-10s => ", $test;
    say strong-password $test;
}

This program displays the following output:

$ raku ./strong-password.raku
a          => 5
aB2        => 3
PaaSW0rd   => 0
Paaasw0rd  => 1
aaaaa      => 3
foob       => 2

Strong Password in Perl

This is a port to Perl of the Raku program above. Please refer to the comments in the two sections above if you need further explanations. Perl doesn't have built-in character classes for uppercase or lowercase letters, but it's very easy to define them, using for example [A-Z]for the uppercase character class.

use strict;
use warnings;
use feature 'say'; uppercase chara cter class

sub strong_password {
    my $pwd = shift;
    my $count = 0;
    # At least one uppercase letter
    $pwd .= "A" and $count++ if $pwd !~ /[A-Z]/; 
    # At least one lowercase letter
    $pwd .= "b" and $count++ if $pwd !~ /[a-z]/;
    # At least one digit
    $pwd .= "3" and $count++ if $pwd !~ /\d/;
    # no repeating characters
    while ($pwd =~ /(.)\1{2}/) {
        my $subst = chr (1 + ord $1);
        $pwd =~ s/(.)$1{2}/$1$1$subst/;
        $count++;
    }
    for my $ch ('a'..'z') {
        last if length $pwd >= 6;
        $count++;
        $pwd .= $ch
    }
    return $count;
} 

my @tests = qw<a aB2 PaaSW0rd Paaasw0rd aaaaa foob>;
for my $test (@tests) {
    printf "%-10s => ", $test;
    say strong_password $test;
}

This program displays the following output:

$ perl ./strong-password.pl
a          => 5
aB2        => 3
PaaSW0rd   => 0
Paaasw0rd  => 1
aaaaa      => 3
foob       => 2

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on September 29, 2024. And, please, also spread the word about the Perl Weekly Challenge if you can.

1 Comment

aaaaa should return 2: e.g. replace aaAaa, then insert aaAaa0.

Similarly, 000aaa000 should return 3 (we can replace the middle char in every group with A), but your code returns 4.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.