Regular Expressions — Study Guide for CSE
6040
1. What Is a Regular Expression?
A regular expression (regex) is a pattern written as a special string that describes a set of strings you
want to match. Python's re module lets you compile a pattern and then search, match, or extract text
from strings using that pattern.
The workflow is always the same: (1) define a pattern string, (2) optionally compile it into a pattern
object, (3) apply it to an input string to find matches.
import re
pattern = r'\d{3}-\d{2}-\d{4}' # Step 1: define pattern
matcher = [Link](pattern) # Step 2: compile (optional)
result = [Link]('832-38-1847') # Step 3: apply
2. The Pattern Language — Building Blocks
2.1 Literal Characters
Most characters match themselves. The letter a in a pattern matches the letter a in text. But some
characters are metacharacters with special meaning: . ^ $ * + ? { } [ ] \ | ( )
To match a literal metacharacter, escape it with a backslash: \( matches a literal open-paren, \.
matches a literal period.
2.2 Character Classes [...]
Square brackets define a set of characters to match ONE character from:
Pattern Matches Example
Pattern Matches Example
[aeiou] Any single vowel 'e' in 'hello'
[a-z] Any lowercase letter Range shorthand
[A-Z] Any uppercase letter Range shorthand
[0-9] Any digit Same as \d
[a-zA-Z] Any letter Upper or lower
[a-zA-Z0-9_] Any "word" character Same as \w
[^0-9] Any NON-digit ^ inside [] = negation
2.3 Predefined Character Classes (Shortcuts)
These are the ones you'll use constantly. Always use raw strings ( r'...' ) to avoid Python's own
backslash processing.
Shortcut Meaning Equivalent
\d Any digit [0-9]
\D Any non-digit [^0-9]
\w Any word character [a-zA-Z0-9_]
\W Any non-word character [^a-zA-Z0-9_]
\s Any whitespace [ \t\n\r\f\v]
\S Any non-whitespace [^ \t\n\r\f\v]
. Any character except newline Almost everything
2.4 Quantifiers (How Many?)
Quantifiers go after the thing they quantify:
Quantifier Meaning Example
X{3} Exactly 3 of X \d{3} = exactly 3 digits
X{2,5} Between 2 and 5 of X \d{2,5} = 2 to 5 digits
X{2,} 2 or more of X [aeiou]{2,} = 2+ vowels
X* 0 or more of X \s* = optional whitespace
Quantifier Meaning Example
X+ 1 or more of X \d+ = at least one digit
X? 0 or 1 of X (optional) -? = optional hyphen
2.5 Anchors (Where in the String?)
Anchors don't match characters — they match positions:
Anchor Meaning
^ Start of string
$ End of string
When you use both ^ and $ , you're saying the entire string must match the pattern — nothing extra
allowed. This is critical for validation tasks (SSN, email, phone).
2.6 Alternation (OR)
The pipe | means "or": lazy|brown matches either "lazy" or "brown" .
2.7 Groups (...)
Parentheses serve two purposes: (1) grouping for alternation or quantifiers, (2) capturing matched text
so you can extract it.
# Capturing group — extracts the digits inside parens
m = [Link](r'\((\d{3})\)', '(404)')
[Link](1) # '404'
# Non-capturing group — groups but doesn't capture
m = [Link](r'(?:\d{3})-(\d{4})', '555-1212')
[Link](1) # '1212' (only one group)
3. Python's re Functions — When to Use Which
Function What it does Returns
Function What it does Returns
[Link](pattern, s) Find first match ANYWHERE in Match object or None
string
[Link](pattern, s) Match only at the START of string Match object or None
[Link](pattern,
s) Match the ENTIRE string Match object or None
[Link](pattern, s) Find ALL non-overlapping matches List of strings
Iterator of Match
[Link](pattern, s) Find ALL matches as iterator
objects
[Link](pattern, repl,
s) Replace all matches New string
Key distinction for exams: [Link]() with ^...$ anchors is equivalent to [Link]() .
Both require the entire string to match. Plain [Link]() finds a match anywhere inside the string.
Working with Match Objects
m = [Link](r'(\d{3})-(\d{4})', 'call 555-1212 now')
[Link]() # '555-1212' — the entire match
[Link](0) # '555-1212' — same as above
[Link](1) # '555' — first capture group
[Link](2) # '1212' — second capture group
[Link]() # ('555', '1212') — tuple of all groups
[Link]() # (5, 13) — start and end positions
4. How to Build a Regex — Step by Step Method
This is the process to follow on the exam when you see a string-matching problem:
Step 1: Write out example strings. List valid and invalid inputs side by side.
Step 2: Identify the fixed structure. What literal characters appear in every valid input? (Dashes,
parens, @ signs, etc.)
Step 3: Identify the variable parts. What changes between valid inputs? Classify each variable part:
digits only? letters only? specific set of characters?
Step 4: Translate each part into regex syntax. Use \d for digits, [a-zA-Z] for letters, quantifiers
for length, ? for optional parts.
Step 5: Add anchors. For validation (must match exactly), use ^...$ or [Link]() .
Step 6: Add capture groups. Wrap (...) around the parts you need to extract.
Worked Example: SSN Validation
Valid: 832-38-1847 . Invalid: 832381847 , 832-bc-3847 .
Structure: [3 digits] - [2 digits] - [4 digits]
Pattern: r'^\d{3}-\d{2}-\d{4}$'
Worked Example: Email Parsing
Valid: richie@[Link] . Rules: username starts with alpha, then [\w.+-]* ; domain ends
with alpha, body is [\w.-]* .
Pattern: r'^([a-zA-Z][\w.\-+]*)@([\w.\-]*[a-zA-Z])$'
The two capture groups let you extract username and domain separately.
Worked Example: Phone Number (XXX) XXX-XXXX
Allow leading/trailing spaces, flexible spacing after ) , but no spaces inside the area code or local
number.
Pattern: r'^\s*\((\d{3})\)\s*(\d{3})-(\d{4})\s*$'
Reading it piece by piece: optional whitespace, literal ( , capture 3 digits, literal ) , optional
whitespace, capture 3 digits, literal - , capture 4 digits, optional whitespace.
Worked Example: Flexible Phone (with or without parens)
Try two patterns separately — cleaner than one complex alternation:
s = [Link]()
# Try with parens
m = [Link](r'\((\d{3})\)\s*(\d{3})-?(\d{4})', s)
if m: return [Link]()
# Try without parens
m = [Link](r'(\d{3})-?(\d{3})-?(\d{4})', s)
if m: return [Link]()
raise ValueError(...)
5. Distance Parsing — A Regex Pattern from the Golf
Exam
This is a real exam pattern. Parse strings like "167 yds" , "30 ft 6 in" , "422" , "8 in" .
import re
from math import ceil
def parse_distance(distance_str, yards_interval, feet_interval):
if distance_str in ('0', ''):
return 0, 'ft'
def round_up(value, interval):
return ceil(int(value) / interval) * interval
# Pattern 1: yards — "167 yds" or just "167"
m = [Link](r'(\d+)\s*yds?', distance_str)
if m:
return round_up([Link](1), yards_interval), 'yds'
m = [Link](r'(\d+)', distance_str)
if m:
return round_up([Link](1), yards_interval), 'yds'
# Pattern 2: feet and inches — "30 ft 6 in" or "12 ft" or "8 in"
m = [Link](r'(?:(\d+)\s*ft\.?)?\s*(?:(\d+)\s*in\.?)?', distance
if m and ([Link](1) or [Link](2)):
feet = int([Link](1)) if [Link](1) else 0
inches = int([Link](2)) if [Link](2) else 0
total_feet = feet + inches / 12
return round_up(str(ceil(total_feet)), feet_interval), 'ft'
raise ValueError(f"Invalid distance format: {distance_str}")
The key technique: try multiple specific patterns in sequence rather than one monster regex.
6. Text Cleaning with Regex — Lyrics Exam Pattern
From the Pop Song exam, cleaning raw text involves chaining regex operations:
def cleanse_lyrics(lyrics_list):
text = '\n'.join(lyrics_list)
text = [Link]()
text = [Link]('-', ' ')
text = [Link](r"[^a-z\s'()]", '', text) # keep only letters, white
text = [Link](r'\(.*?\)', '', text, flags=[Link]) # remove (backg
lines = [Link]('\n')
return [' '.join([Link]()) for line in lines if [Link]()]
Key patterns used: [^...] for "everything except", .*? for non-greedy match (match as little as
possible), [Link] flag to let . match newlines.
7. Common Regex Pitfalls
Forgetting r prefix: '\d' is a Python escape sequence. r'\d' is a regex digit class. Always use
raw strings.
Greedy vs. non-greedy: .* matches as much as possible (greedy). .*? matches as little as possible
(non-greedy). This matters when removing parenthesized text: \(.*?\) removes each (...)
individually, while \(.*\) would swallow everything from the first ( to the last ) .
match() vs search() : [Link]() only checks the start of the string. If your pattern doesn't
have $ , it will match partial strings. Use [Link]() for exact matching.
Escaped characters inside [] : Inside a character class, most metacharacters lose their special
meaning. But - and ] still need escaping (or careful placement). Put - first or last: [a-zA-Z0-
9._-] or [-a-z] .
\w includes underscore: \w is [a-zA-Z0-9_] , not just letters and digits. This is usually fine for
email validation since _ is allowed, but be aware.
8. Quick Reference Card
MATCHING: EXTRACTING:
[Link](pattern, s) [Link](1) — first capture group
[Link](r'^...$', s) [Link](2) — second capture group
bool([Link](...)) [Link]() — tuple of all groups
COMMON PATTERNS:
\d{3} 3 digits \s* optional whitespace
\d+ one or more digits \s+ required whitespace
[a-zA-Z] one letter -? optional hyphen
[\w.\-+]* word chars/./+/- \( \) literal parentheses
^...$ anchor full string (group1|group2) alternation
VALIDATION TEMPLATE:
def validate(s):
m = [Link](r'YOUR_PATTERN_HERE', s) # or [Link] with ^...$
if m is None:
raise ValueError('...')
return [Link]() # or ([Link](1), [Link](2))