Text puzzles: ciphers and anagrams

Part of the free LogicWiz Python course, Module 4: Strings. The examples and practice run in the app.

Aarav and Meher pass notes in class that say Nffu bu dbouffo bu 2. Every letter is shifted one step. That's a Caesar cipher, and with ord() and chr() you can write it in eight lines.

You'll learn to

  • Turn characters into numbers and back with `ord` and `chr`
  • Write a Caesar cipher that wraps Z round to A
  • Check whether two words are anagrams
  • Count how often each character appears

Learn

Every character is a number

Inside the computer each character is stored as a number (its Unicode code point). ord(ch) gives the number; chr(n) gives the character back.

  • 'A' to 'Z' are 65 to 90
  • 'a' to 'z' are 97 to 122
  • '0' to '9' are 48 to 57

Letters are in a row, so ord('C') - ord('A') is 2: C is letter number 2, counting from 0. Small letters are exactly 32 more than capitals.

ord and chr

print(ord("A"), ord("Z"), ord("a"), ord("0"))
print(chr(66), chr(100))
print(chr(ord("C") + 2))
print(ord("b") - ord("a"))
print(chr(ord("m") - 32))

Shifting with wrap-around

To shift a capital letter by k:

  1. Position in the alphabet: pos = ord(ch) - ord('A') (A → 0, Z → 25)
  2. Shift and wrap: new = (pos + k) % 26. The % 26 turns 26 back into 0, so Z + 1 lands on A. It works for negative k too.
  3. Back to a letter: chr(new + ord('A'))

For small letters use ord('a') instead.

Shift one letter

def shift(ch, k):
    pos = ord(ch) - ord("A")
    new = (pos + k) % 26
    return chr(new + ord("A"))

print(shift("C", 3))
print(shift("Y", 3))
print(shift("A", -1))

The whole cipher

def caesar(text, k):
    out = ""
    for ch in text:
        if ch.isupper():
            out += chr((ord(ch) - 65 + k) % 26 + 65)
        elif ch.islower():
            out += chr((ord(ch) - 97 + k) % 26 + 97)
        else:
            out += ch
    return out

secret = caesar("Meet at canteen at 2", 1)
print(secret)
print(caesar(secret, -1))

Spaces and digits pass through unchanged. Decoding is just shifting by -k.

Forget the % 26 and 'Z' shifted by 1 becomes chr(91), which is '['. Always go letter → position → shift % 26 → letter.

Anagrams: same letters, new order

def is_anagram(a, b):
    a = a.replace(" ", "").lower()
    b = b.replace(" ", "").lower()
    return sorted(a) == sorted(b)

print(sorted("listen"))
print(is_anagram("Listen", "Silent"))
print(is_anagram("Dormitory", "Dirty room"))
print(is_anagram("chai", "chat"))

sorted(s) gives a list of the characters in order. Two words are anagrams exactly when their sorted letters match.

Character frequency

text = "banana"
seen = ""
for ch in text:
    if ch not in seen:
        print(ch, text.count(ch))
        seen += ch

seen remembers which characters were already reported, so each prints once. In Module 6 a dictionary does this in one pass.

TCS NQT, Infosys and Wipro coding rounds love exactly these: Caesar shifts, anagram checks, the most frequent character, and compressing aaabb to a3b2. They are string loops plus a little ord arithmetic.

In one line

ord turns a character into its code and chr back; shift letters with (pos + k) % 26; anagrams have equal sorted() letters.