Embercode
✕
Variables and Data TypesLesson 8 of 16

40 min read

Character arithmetic with ord and chr

Why this matters

Text in a program is made of single characters: letters, digits, spaces and punctuation. A word search checks whether one letter comes before another. A spell checker counts how often each letter appears. A program that encrypts a message moves each letter a few places along the alphabet. All of these need to treat a character as a value you can calculate with, not only as something to print.

In Python, a character is a string of length 1. Python has no separate character type. Each character has a code number behind it, and two built-in functions move between the two forms. ord gives the code of a character, and chr gives the character for a code. Once you know this, three common tasks become short: adding an offset to a letter, comparing letters, and turning a letter into a position in a list. This lesson covers those three tasks.

A character has a code number

Start with a single example. Write a one-character string in quotes, such as "a". The function ord returns the code number Python uses for it. The codes come from the Unicode table, which gives a number to each character in most of the world's writing systems.

Python · editable
a
97
65
48
32

The lowercase a is 97, and the uppercase A is 65. The digit character "0" is 48, not 0. This last point surprises most beginners. The character "0" is a symbol that looks like zero, and its code is 48. The number 0 is not a character at all. The gap of 32 between A and a is also useful, and you will see it in the mistakes section.

The function chr does the reverse. chr(97) is "a". The program below uses ord and chr together.

Python · editable
a
A
b

Adding a number to a character

Python does not let you add a number directly to a one-character string. The + operator joins two strings, or it gives an error when one side is a number. The way to do arithmetic on a letter is to convert it to its code first with ord, do the arithmetic on the number, and convert back with chr.

Python · editable
98
b
195
7

The first line prints 98, not b. The expression ord(c) + 1 takes the number 97 from c, adds 1, and gives 98. The second line converts that number back to the character b. The third line adds two codes as numbers: 97 + 98 is 195. The last line shows a useful trick. Subtracting the code of "0" from the code of a digit gives the number that digit stands for. The character "7" has code 55 and "0" has code 48, so the difference is 7. This is how many programs read a digit from text, and it works for every digit from "0" to "9".

The mistake of adding a number directly is shown in the mistakes section, with the message Python gives.

Changing a character by a small amount

A string cannot be changed in place. You cannot write c += 1 on a one-letter string and expect the letter to change, because += on a string joins text and does not add to a code. To move a letter forward, compute the new letter with chr and store it under the same name or a new one.

Python · editable
b
d
122

The first change adds 1 to the code of a, which gives b. The second adds 2 to the code of b, which gives d. Each step computes a new string, and the name then points to it. The last line shows the code of z, which is 122.

Comparing characters

Because each character has a code, the comparison operators <, <=, >, >=, == and != work on one-letter strings too. The comparison compares codes. "a" < "b" is True because 97 is less than 98. "A" < "a" is also True, because 65 is less than 97. This is why a plain comparison treats all uppercase letters as smaller than all lowercase letters, which affects sorting text.

Most checks need a range. To test whether a character is a lowercase letter, check that it is at least "a" and at most "z". Python allows the chained form "a" <= c <= "z", which reads the same way as the arithmetic. The same pattern works for uppercase letters with "A" and "Z", and for digits with "0" and "9".

Python · editable
True
True
True
q lower? True upper? False
True

The methods isalpha, islower, isupper and isdigit do the same checks in one call, as in c.islower(). They follow the Unicode table, so they also accept letters from other alphabets, such as "é". The range check accepts only the English letters, because it compares codes between "a" and "z". Use the range check when the problem says the input is plain English, and the method when any letter should count.

Worked example: counting letters

Counting how often each letter appears is a classic beginner task. It uses everything in this lesson. The idea is to make a list of 26 numbers, one per letter, and to add 1 to the slot for each letter you meet. A list is an ordered collection of values, and an index is the position of one value in it. Python indexes start at 0, so the slot for a is index 0, the slot for b is index 1, and so on up to z at index 25.

The key step is to turn a letter into its index. The index of a letter is its code minus the code of "a". The letter "a" gives 97 minus 97, which is 0. The letter "d" gives 100 minus 97, which is 3. The program below does this for every letter in a sentence.

Python · editable
a: 2
d: 1
e: 1
f: 1
h: 1
i: 1
j: 1
l: 3
n: 1
o: 2
r: 1
s: 1
u: 1
v: 1
w: 1

Read the program in three parts. The first loop goes through every character of the text after text.lower() has changed the capitals to lowercase, so H and h count together. The test "a" <= ch <= "z" skips the comma, the space, the exclamation mark and the full stop. The line counts[ord(ch) - ord("a")] += 1 finds the slot for the letter and adds 1 to it.

The second loop walks the 26 slots and prints each letter that appeared. The expression chr(ord("a") + i) turns an index back into a letter. The letter l appears three times, in Hello and World, which is why its count is 3.

The same count can be written with collections.Counter, which keeps a count for each character it meets. A list of 26 slots is the better choice when the problem is about the English alphabet, because the slots are fixed in number and in order.

Worked example: shifting letters

The same arithmetic can change letters, not only count them. A Caesar cipher moves each letter a fixed number of places along the alphabet. It is one of the oldest ways to hide a message, and it is a good test of the offset method, because the letters must wrap around. After "z" comes "a" again.

The wrap is done with the remainder operator %. For a lowercase letter, the position is ord(ch) - ord("a"), which is 0 for a and 25 for z. Adding the shift and taking the remainder after dividing by 26 keeps the answer between 0 and 25. The uppercase letters work the same way with "A" in place of "a". Other characters, such as spaces and punctuation, are copied without change.

Python · editable
Dwwdfn dw gdzq, abc

Check the wrap by hand. The letter x has position 23, and 23 + 3 is 26. The remainder of 26 divided by 26 is 0, which is the position of a. So x becomes a. The letters y and z become b and c. Without the % step, x would become a code past z, and the program would print a symbol instead of a letter.

The list out collects one character at a time, and "".join(out) builds the text at the end. This is the same pattern the strings lesson recommended for building text in a loop.

Worked example: decoding a shift

Decoding is the same idea with the shift going backwards. The word Dwwdfn is Attack moved three places forward, so moving each letter three places back gives the original. In many languages this case needs an extra step, because the shift can make the position negative. The letter a has position 0, and 0 minus 3 is -3.

In Python the remainder operator never gives a negative result when the divisor is positive. The value -3 % 26 is 23, which is the position of x. So no extra step is needed. The program below decodes the message from the last example.

Python · editable
Attack at dawn, xyz
23

The decoded text is the original sentence. The abc at the end became xyz, because a minus 3 wraps around to x. The second line shows the rule behind it: -3 % 26 is 23.

Languages where the remainder keeps the sign of the number on the left need the extra step + 26 before the remainder. Python does not, so the same code works without it. If you read Java or C code that does this, the step is there for that reason.

Common mistakes

The first mistake is adding a number to a one-letter string. Python stops with this message:

c = "a"
c = c + 1
Traceback (most recent call last):
  File ".../add.py", line 2, in <module>
    c = c + 1
        ~~^~~
TypeError: can only concatenate str (not "int") to str

The fix is to convert with ord, add, and convert back: c = chr(ord(c) + 1).

The second mistake is using a double-length string where one letter was meant. Python accepts "ab" as text, so there is no error when you write it. The error appears when you ask for the code of a string that is not one character long:

Traceback (most recent call last):
  File ".../ord.py", line 1, in <module>
    print(ord("ab"))
          ~~~^^^^^^
TypeError: ord() expected a character, but string of length 2 found

The fix is to use one character, or to loop over the characters of the text.

The third mistake is using a letter as an index without subtracting "a". The letter "h" has code 104, and a list of 26 slots has indexes only from 0 to 25. Python checks the index on every access and stops the program:

counts = [0] * 26
counts[ord("h")] += 1
Traceback (most recent call last):
  File ".../index.py", line 2, in <module>
    counts[ord("h")] += 1
    ~~~~~~^^^^^^^^^^
IndexError: list index out of range

The number 104 is the code of h, which shows that the code was used directly. The fix is counts[ord(ch) - ord("a")], which gives index 7 for h.

The fourth mistake is using a character as a list index without ord. counts["h"] gives TypeError: list indices must be integers or slices, not str. The message names the type that was used, which is the first thing to check.

The fifth mistake is relying on the gap between A and a. The gap is 32 in the code table, so the uppercase version of a lowercase English letter is its code minus 32. This is true for those letters, but a program that relies on the gap is hard to read. Use str.upper and str.lower instead.

Practice

Work each exercise yourself before you open the answer. Predict the output on paper first.

  1. What does print(ord("c") - ord("a")) print?
  2. What does print(chr(ord("A") + 25)) print?
  3. The name x holds "x" and needs to become "y". Write one statement that does this, and explain why x = x + 1 gives an error.
  4. Write one line that turns the digit character "7" into the number 7, and state what the line stores.
  5. Count the vowels (a, e, i, o, u) in the word "Programming", ignoring case. What number should the program print?

Each exercise has an answer below a "Show the answer" line. Try each one first, then read the answer.

  1. The code of c is 99 and the code of a is 97. Their difference is 2, which is the distance from a to c in the alphabet.

Show the answer

print(ord("c") - ord("a"))

The output is 2.

  1. The code of A is 65, and 65 + 25 is 90. The character with code 90 is Z.

Show the answer

print(chr(ord("A") + 25))

The output is Z.

  1. Compute the next letter with chr and ord. The expression x + 1 tries to add a number to a string, which Python refuses.

Show the answer

x = chr(ord(x) + 1)

Before this line, x is "x", so after it x is "y". The line x = x + 1 gives TypeError: can only concatenate str (not "int") to str.

  1. The code of "7" is 55 and the code of "0" is 48, so the difference is 7.

Show the answer

n = ord("7") - ord("0")
print(n)

The line stores the integer 7.

  1. The vowels are the o, the a and the i, so the answer is 3. The program lowers the text first, then tests each character against the five vowels.

Show the answer

word = "Programming"
count = 0
for ch in word.lower():
    if ch in "aeiou":
        count += 1
print(count)

The output is 3. The test ch in "aeiou" is True when the character is one of the five vowels.

What you now know

  • A character is a one-letter string. ord gives its code and chr gives the character for a code.
  • Arithmetic on a character needs ord first. Convert back with chr.
  • A one-letter string cannot be changed in place. Compute the new letter and store it under the name.
  • The comparison operators compare the codes, so "a" <= c <= "z" checks for a lowercase English letter.
  • Subtracting ord("a") from a lowercase letter gives an index from 0 to 25, which is how a 26-slot list counts letters.
  • Python's % never gives a negative result for a positive divisor, so a Caesar shift wraps without an extra step.
Practice this topic: coding problems for Variables and Data Types →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Literals and number bases in Python