Cryptogram generator in Python: the letter that codes as itself
A plain shuffle leaves a letter standing for itself in 63% of keys, and about one coded quote in 500 spells a rude word. Both measured on 747 public-domain quotes, with forty lines of Python that avoid them.
A cryptogram is a quote with every letter swapped for another: A becomes Q, B becomes M, and so on, the same swap all through the puzzle. Making one takes a shuffled alphabet and three lines of Python. Making one that a puzzle reader will not complain about takes two more rules, and both are easy to miss because the quick version looks fine.
We measured both on 29 October 2026 with the 747 quotes of our own cryptogram books, all by writers who died before 1930.
The short version#
- A letter that codes as itself: a plain
random.shuffleof the alphabet left at least one letter in its own place in 63.0% of 100,000 keys, one letter on average, two or more in 26.3%. Readers of cryptograms expect that never to happen, and some use it to solve. - The fix: shuffle again until no letter is in its place. That took 2.71 shuffles on average, 29 at most, about 27 microseconds a key.
- Rude coded words: with fair random keys, 0.19% of the coded quotes held a word from a public list of rude English words. That is rare for one puzzle and likely for a book: 17% of 100-puzzle books and 43% of 300-puzzle books would have at least one.
- The fix: check every coded word and draw a new key when one is blocked. All 747 quotes got a clean key, in about half a millisecond each.
The code#
import random, re, string
AZ = string.ascii_uppercase
def make_key(rng=random):
"""A substitution key where no letter stands for itself: shuffle, and shuffle again until that holds."""
while True:
key = rng.sample(AZ, 26)
if all(k != a for k, a in zip(key, AZ)):
return "".join(key)
def encode(text, key):
return text.upper().translate(str.maketrans(AZ, key))
def bad_words(cipher, blocked):
"""Coded words that are a blocked word, or hold one of 4 letters or more."""
return [w for w in re.findall("[A-Z]+", cipher)
if w in blocked or any(len(b) >= 4 and b in w for b in blocked)]
def cryptogram(quote, blocked, hints=1, rng=random, tries=200):
"""(coded quote, {coded letter: real letter} given as hints, key). Hints are letters used at least twice."""
letters = [c for c in quote.upper() if c in AZ]
for _ in range(tries):
key = make_key(rng)
cipher = encode(quote, key)
if not bad_words(cipher, blocked):
often = sorted({c for c in letters if letters.count(c) >= 2})
given = rng.sample(often, min(hints, len(often)))
return cipher, {key[AZ.index(p)]: p for p in given}, key
raise RuntimeError("no clean key for this quote")rng = random.Random(7)
cipher, given, key = cryptogram("Well done is better than well said.", blocked, hints=1, rng=rng)
print(cipher)
print(given)With that seed it printed BLUU VEXL FI WLHHLG HYJX BLUU IJFV. and the hint {'L': 'E'}: Benjamin Franklin's "Well done is better than well said." from Poor Richard's Almanack, 1737.
No letter stands for itself#
The share is not a quirk of Python's shuffle. For any fair shuffle of 26 letters, the chance that none stays in place is very close to 1/e, about 36.8%, so 63.2% of keys have at least one letter in place. We counted 63.0%. Reshuffling until none is left takes e shuffles on average, about 2.72; we counted 2.71.
You will also find Sattolo's shuffle suggested for this. It never leaves a letter in place: 0 of 20,000 keys did. But it only makes keys that are one long chain, A to some letter, that letter to another, all 26 before you are back at A. Of all fair keys, only 10.45% are like that. Ours were 2,076 of 20,000. For a puzzle it hardly matters. It is just not the same as picking a fair key at random, and reshuffling is as short to write.
Coded words that spell something#
A coded word is random letters, so it rarely spells anything. Rarely is not never: 148 rude words turned up across 74,700 coded quotes, in 0.19% of them. bad_words() catches a coded word that is a blocked word, and a longer coded word that holds a blocked word of 4 letters or more, and cryptogram() then draws a new key. We used the LDNOOBW list, 275 single words. A list for a children's book would be longer.
Real, harmless words turn up too: 0.12% of coded words of three letters or more were ordinary English words. That is no fault, but a reader may stop and stare at one, so a book with a theme for children may want to skip those keys as well.
Hints that help#
An easy cryptogram gives a few letters away. A letter that appears once in the quote gives away one square. cryptogram() only gives letters that appear at least twice; across our quotes the given letter appeared 3.8 times on average. Every quote also decoded back to itself from its key, which is the test to run before anything is printed.
A whole book, not one puzzle#
For KDP you need a hundred of these, laid out with room to write, with answers at the back. Our cryptogram book maker does that, with these rules and more: no letter ever stands for itself, each puzzle has its own key, no coded word spells a word from our blocked list, and a second program decodes every puzzle again from the printed letters. The quotes are the same 747, by writers who died before 1930, each checked against its source. You pick the level, the number of puzzles, large print and the trim size, and the book is checked against KDP's paperback specs before you can download it. One cryptogram book costs 500 credits, from $3.30 in the big pack. Why KDP rejects puzzle books covers the checks KDP runs on its side.
written
10 min readLarge print sudoku for seniors: what to look for, in millimetres
Large print is generally 16 to 18 point to RNIB, and at least 16 point, ideally 18, to UKAAF. We measured our own sudoku book for seniors: 32.85-point bold digits, 7.73 to 8.02 mm tall, in squares 19.98 mm wide, one puzzle per page, and exactly one answer for each of 120 puzzles. What to check before you buy or make one.
7 min readLarge print word search: what large print means, in millimetres
Large print is generally 16 to 18 point to RNIB, and at least 16 point, ideally 18, to UKAAF. We measured our own Christmas word search book: 18-point bold grid letters, capitals 4.23 mm tall, letters 10.24 mm apart, and nothing smaller than 16 point. What that means on the page, and what else makes a puzzle book easy to read.
7 min readWord search generator in Python: the two bugs a quick one ships
Placing the words is the easy part. In 900 test grids a one-pass fill left a rude word in more than half and a listed word twice in about one in ten. What causes both, and the fill that fixes them.