The Weekly Challenge 370

Popular Word & Scramble String!

Original Challenge Link

Task 1: Popular Word

"Most Frequent Non-Banned Word"

Given a paragraph and a list of banned words, return the most frequent word that is not banned. The comparison is case-insensitive and punctuation is ignored. It is guaranteed that there is at least one non-banned word and the answer is unique.

The Strategy: Remove punctuation, split into words, convert to lowercase, count frequencies excluding banned words, and return the word with the highest count.
Perl Implementation
sub popular_word {
    state $check = compile(Str, ArrayRef);
    my ($paragraph, $banned_ref) = $check->(@_);

    my %banned = map { lc $_ => 1 } @$banned_ref;

    # Remove punctuation and split
    $paragraph =~ s/[^\w\s]/ /g;
    my @words = map { lc } split /\s+/, $paragraph;

    my %count;
    for my $w (@words) {
        next if $banned{$w};
        $count{$w}++;
    }

    my ($popular, $max_freq) = ('', 0);
    for my $w (keys %count) {
        if ($count{$w} > $max_freq) {
            $popular = $w;
            $max_freq = $count{$w};
        }
    }
    return $popular;
}
Python Implementation
def popular_word(paragraph: str, banned: Sequence[str]) -> str:
    """Return the most frequent non‑banned word in *paragraph*."""
    # Normalise: replace non‑word/space with space, lower case, split.
    words = re.sub(r"[^\w\s]", " ", paragraph).lower().split()
    banned_set = {b.lower() for b in banned}

    freq: dict[str, int] = {}
    for w in words:
        if w in banned_set:
            continue
        freq[w] = freq.get(w, 0) + 1

    # It is guaranteed that there is at least one non‑banned word and the answer is unique.
    return max(freq.items(), key=lambda kv: kv[1])[0]

Task 2: Scramble String

"Recursive String Scrambling"

Given two strings A and B of the same length, determine if B is a scramble of A. A scramble operation involves splitting the string into two non-empty parts, optionally swapping them, and recursively scrambling each part.

The Strategy: Use recursion with memoization. First, check if the strings are equal. If not, check if they have the same character counts (prune). Then, for every possible split position, check the two possibilities: without swap and with swap.
Perl Implementation
sub scramble_string {
    state $check = compile(Str, Str);
    my ($s1, $s2) = $check->(@_);

    # Memoization
    state %memo;
    my $key = "$s1|$s2";
    return $memo{$key} if exists $memo{$key};

    if ($s1 eq $s2) {
        $memo{$key} = 1;
        return 1;
    }

    # Early exit: character counts must match
    my @cnt1 = (0) x 26;
    my @cnt2 = (0) x 26;
    for my $ch (split //, $s1) { $cnt1[ord($ch) - 97]++ }
    for my $ch (split //, $s2) { $cnt2[ord($ch) - 97]++ }
    for my $i (0 .. 25) {
        if ($cnt1[$i] != $cnt2[$i]) {
            $memo{$key} = 0;
            return 0;
        }
    }

    my $n = length $s1;
    for my $i (1 .. $n-1) {
        # No swap
        if (scramble_string(substr($s1,0,$i), substr($s2,0,$i)) &&
            scramble_string(substr($s1,$i), substr($s2,$i))) {
            $memo{$key} = 1;
            return 1;
        }
        # Swap
        if (scramble_string(substr($s1,0,$i), substr($s2,$n-$i)) &&
            scramble_string(substr($s1,$i), substr($s2,0,$n-$i))) {
            $memo{$key} = 1;
            return 1;
        }
    }

    $memo{$key} = 0;
    return 0;
}
Python Implementation
from functools import lru_cache

@lru_cache(maxsize=None)
def _is_scramble(s1: str, s2: str) -> bool:
    """Return True if s2 is a scramble of s1 using recursion + memoisation."""
    if s1 == s2:
        return True
    if sorted(s1) != sorted(s2):  # prune: different character multisets
        return False

    n = len(s1)
    # Try every split position
    for i in range(1, n):
        # No swap: s1[0:i] ~ s2[0:i] and s1[i:] ~ s2[i:]
        if (_is_scramble(s1[:i], s2[:i]) and _is_scramble(s1[i:], s2[i:])):
            return True
        # Swap: s1[0:i] ~ s2[n-i:] and s1[i:] ~ s2[0:n-i]
        if (_is_scramble(s1[:i], s2[n-i:]) and _is_scramble(s1[i:], s2[:n-i])):
            return True
    return False

def scramble_string(str1: str, str2: str) -> bool:
    """Public wrapper – matches the Perl interface (returns True/False)."""
    if len(str1) != len(str2):
        return False
    return _is_scramble(str1, str2)