Python Arena
Step 3 of 4

Repetition and groups

\d\d\d\d matches four digits, but writing the same part again and again gets tiring, and often you don't even know how many there will be. A quantifier right after a part of a pattern says how many times that part repeats:

  • * zero or more times
  • + one or more times
  • ? zero times or once: the part is optional
  • {m} exactly m times
  • {m,n} from m to n times

A quantifier repeats only the one thing right before it: a character, a class or a short form such as \d. So r"ha+" is an h followed by one or more a's, and r"[0-9]+" is one or more digits:

In the Python interpreter
>>> import re
>>> re.findall(r"ha+", "ha haaa h hah")
['ha', 'haaa', 'ha']
>>> re.findall(r"colou?r", "color or colour?")
['color', 'colour']
>>> re.findall(r"\d{4}", "1984, 42, 2026")
['1984', '2026']
>>> re.findall(r"[A-Z][a-z]*", "Ada met Bo and X")
['Ada', 'Bo', 'X']

The last pattern is a capital letter followed by any number of small letters, none at all included, so it finds capitalized words.

Groups

Round brackets make a group. They collect part of a pattern into one unit: a quantifier after a group repeats the whole group, and a | inside a group chooses only within it:

import re

for laugh in ["haha", "hahaha", "hah"]:
    print(laugh, re.search(r"^(ha)+quot;, laugh) is not None)
for word in ["gray", "grey", "groy"]:
    print(word, re.search(r"^gr(a|e)yquot;, word) is not None)
Output
haha True
hahaha True
hah False
gray True
grey True
groy False

Without the brackets, r"^ha+quot; would be an h followed by a's, which matches haaa but not haha. And r"^gra|eyquot; would mean "starts with gra, or ends with ey". One thing to know: when a pattern has a group, re.findall returns only the part that the group matched, not the whole match. So use groups with re.search, or with the function below.

The whole text: re.fullmatch

^ and $ around a pattern make it describe the whole text. re.fullmatch(pattern, text) does the same more simply: it matches only when the pattern covers the text from its first character to its last. That makes it the natural tool for checking a whole value, such as a year:

import re


def is_year(text):
    return re.fullmatch(r"\d{4}", text) is not None


for text in ["2026", "20266", "year 2026", "99"]:
    print(text, is_year(text))
Output
2026 True
20266 False
year 2026 False
99 False

re.search(r"\d{4}", "20266") would find a match, the first four digits. re.fullmatch doesn't, because a fifth digit is left over.

Your task

Challenge 1 of 3

A US ZIP code is exactly five digits. Fill in the quantifier so that the program prints:

90210 valid
1234 invalid
123456 invalid
02139 valid

Challenge 2

What does this program print? Type each line it prints.

Challenge 3

A product code is two capital letters, a dash and four digits, such as AB-1234. It may also end with a dash and one more capital letter, as in AB-1234-X. Write a function is_product_code(text) that returns True if the whole text is a product code, and False otherwise. Use re.fullmatch. For example,

print(is_product_code("AB-1234"))
print(is_product_code("AB-1234-X"))
print(is_product_code("AB-123"))

should print:

True
True
False