Anagramic Squares¶

Itertools to the rescue! But first, we'll read the words we're working with.

In [1]:
with open("txt/0098_words.txt") as f:
    words = [word.strip('"') for line in f for word in line.split(',')]

We can then make groups where each word is an anagram of the other words in the group.

In [2]:
from itertools import groupby

anagram_groups = []
for _, g in groupby(sorted(words, key=sorted), key=sorted):
    g = tuple(g)
    if len(g) > 1:
        anagram_groups.append(g)

anagram_groups
Out[2]:
[('BOARD', 'BROAD'),
 ('CREATION', 'REACTION'),
 ('CARE', 'RACE'),
 ('ACT', 'CAT'),
 ('DANGER', 'GARDEN'),
 ('DEAL', 'LEAD'),
 ('PHASE', 'SHAPE'),
 ('EARTH', 'HEART'),
 ('HATE', 'HEAT'),
 ('ARISE', 'RAISE'),
 ('MALE', 'MEAL'),
 ('LEAST', 'STEAL'),
 ('MEAN', 'NAME'),
 ('EARN', 'NEAR'),
 ('RATE', 'TEAR'),
 ('EAST', 'SEAT'),
 ('EAT', 'TEA'),
 ('INTRODUCE', 'REDUCTION'),
 ('CREDIT', 'DIRECT'),
 ('CENTRE', 'RECENT'),
 ('EXCEPT', 'EXPECT'),
 ('COURSE', 'SOURCE'),
 ('DOG', 'GOD'),
 ('SHEET', 'THESE'),
 ('FILE', 'LIFE'),
 ('FORMER', 'REFORM'),
 ('IGNORE', 'REGION'),
 ('ITEM', 'TIME'),
 ('QUIET', 'QUITE'),
 ('NOTE', 'TONE'),
 ('SURE', 'USER'),
 ('FORM', 'FROM'),
 ('NIGHT', 'THING'),
 ('SIGN', 'SING'),
 ('THROW', 'WORTH'),
 ('SHOUT', 'SOUTH'),
 ('HOW', 'WHO'),
 ('SHUT', 'THUS'),
 ('ITS', 'SIT'),
 ('NO', 'ON'),
 ('NOW', 'OWN'),
 ('POST', 'SPOT', 'STOP')]

When looking for square mappings, we only need to consider square numbers with the same number of digits as a given word. This means if word has $n$ characters, we'll only try to map squares between $10^{n-1}$ and $10^n$. Here's a function to iterate over all square numbers with a given number of digits.

In [3]:
def n_digit_square_numbers(n):
    low = ceil(sqrt(10^(n-1)))
    high = floor(sqrt(10^n - 1))
    for k in range(low, high + 1):
        yield k^2

Next, a function to generate those mappings. Per the problem, we'll only consider mappings that map distinct characters to distinct digits (i.e. injective mappings).

In [4]:
def square_mappings(word):
    n = len(word)
    for k in n_digit_square_numbers(n):
        tr = dict(zip(word, str(k)))
        if len(set(tr.values())) != len(tr):
            continue
            
        yield str.maketrans(tr)

Then, given two anagrams, we'll determine if they are a square anagram word pair by generating square mappings for one word, and checking if any of those mappings also result in a square number for the other word. If there are multiple such mappings, we'll return the one that makes the largest square number.

In [5]:
def square_anagram_word_pair_mapping(s, t):
    best = None
    maximum = float('-inf')
    
    for tr in square_mappings(s):
        m, n = s.translate(tr), t.translate(tr)
        if m[0] == '0' or n[0] == '0':
            continue

        m, n = int(m), int(n)
        if is_square(m) and is_square(n) and maximum < max(m, n):
            maximum = max(m, n)
            best = tr
            
    return best

Now we'll look at every anagram pair and find their best square mapping, if it exists.

In [6]:
from itertools import combinations

pairs = dict()
for group in anagram_groups:
    for s, t in combinations(group, 2):
        pairs[(s, t)] = square_anagram_word_pair_mapping(s, t)

With the best mappings, we can find the largest square that can be made from each pair.

In [7]:
max_squares = {(s, t): max(int(s.translate(tr)), int(t.translate(tr))) for ((s, t), tr) in pairs.items() if tr is not None}
max_squares
Out[7]:
{('BOARD', 'BROAD'): 18769,
 ('CARE', 'RACE'): 9216,
 ('DEAL', 'LEAD'): 4761,
 ('HATE', 'HEAT'): 1936,
 ('MALE', 'MEAL'): 1936,
 ('MEAN', 'NAME'): 9604,
 ('RATE', 'TEAR'): 9604,
 ('EAST', 'SEAT'): 2916,
 ('EAT', 'TEA'): 961,
 ('DOG', 'GOD'): 961,
 ('FILE', 'LIFE'): 9216,
 ('NOTE', 'TONE'): 9216,
 ('HOW', 'WHO'): 961,
 ('SHUT', 'THUS'): 4761,
 ('ITS', 'SIT'): 961,
 ('NOW', 'OWN'): 961,
 ('POST', 'SPOT'): 2916,
 ('POST', 'STOP'): 9604}
In [8]:
max(max_squares.values())
Out[8]:
18769

Copyright (C) 2025 filifa¶

This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license and the BSD Zero Clause license.