Quiz: Regular Expressions

Test your understanding before moving on. Target: 70% or higher to proceed confidently.


Section 1: Multiple Choice (1 point each)

1. Which re function searches the ENTIRE string and returns the first match?

  • A) re.match()
  • B) re.search()
  • C) re.findall()
  • D) re.fullmatch()
Answer **B)** `re.search()` *Why B:* `re.search()` scans through the entire string looking for the first location where the pattern matches. *Why not A:* `re.match()` only checks at the beginning of the string. *Why not C:* `re.findall()` returns ALL matches, not just the first. *Why not D:* `re.fullmatch()` checks whether the entire string matches the pattern. *Reference:* Section 22.2

2. What does the pattern \d{3} match?

  • A) The literal characters \d{3}
  • B) Any three characters
  • C) Exactly three digits
  • D) Three or more digits
Answer **C)** Exactly three digits *Why C:* `\d` matches a single digit, and `{3}` means "exactly three times." *Why not A:* `\d` is a regex metacharacter, not literal text. *Why not B:* `\d` matches digits specifically, not any character. *Why not D:* `{3}` means exactly three, not three or more (that would be `{3,}`). *Reference:* Section 22.3, 22.4

3. What is the purpose of the r prefix in r"\bword\b"?

  • A) It makes the regex case-insensitive
  • B) It creates a raw string so backslashes aren't interpreted as Python escape sequences
  • C) It compiles the regex for faster execution
  • D) It makes the regex match only whole words
Answer **B)** It creates a raw string so backslashes aren't interpreted as Python escape sequences *Why B:* Without the `r` prefix, Python would interpret `\b` as a backspace character before the regex engine even sees it. *Why not A:* Case-insensitivity requires the `re.IGNORECASE` flag. *Why not C:* Compilation is done with `re.compile()`. *Why not D:* The `\b` anchors create the word-boundary behavior, not the `r` prefix. *Reference:* Section 22.1

4. Which character class matches any single whitespace character (space, tab, newline)?

  • A) \d
  • B) \w
  • C) \s
  • D) \b
Answer **C)** `\s` *Why C:* `\s` matches space, tab, newline, carriage return, form feed, and vertical tab. *Why not A:* `\d` matches digits. *Why not B:* `\w` matches word characters (letters, digits, underscore). *Why not D:* `\b` is an anchor (word boundary), not a character class. *Reference:* Section 22.3

5. What does re.findall(r"\b\w+\b", "Hello, World!") return?

  • A) ['Hello, World!']
  • B) ['Hello', 'World']
  • C) ['Hello,', 'World!']
  • D) ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
Answer **B)** `['Hello', 'World']` *Why B:* `\b` marks word boundaries, `\w+` matches one or more word characters. The comma, space, and exclamation mark are not word characters, so they are excluded. *Why not A:* The entire string is not a single word. *Why not C:* Punctuation is not matched by `\w`. *Why not D:* `\w+` matches sequences of word characters, not individual characters. *Reference:* Section 22.5

6. What is the difference between greedy * and lazy *??

  • A) * matches zero or more; *? matches one or more
  • B) * matches as many characters as possible; *? matches as few as possible
  • C) * is case-sensitive; *? is case-insensitive
  • D) * works with any character class; *? only works with .
Answer **B)** `*` matches as many characters as possible; `*?` matches as few as possible *Why B:* Greedy quantifiers try to match the longest possible string; lazy quantifiers try the shortest. *Why not A:* Both `*` and `*?` match zero or more; `+` is one or more. *Why not C:* Greediness has nothing to do with case sensitivity. *Why not D:* Both work with any pattern element. *Reference:* Section 22.8

7. Which function would you use to replace all occurrences of a pattern in a string?

  • A) re.search()
  • B) re.findall()
  • C) re.sub()
  • D) re.match()
Answer **C)** `re.sub()` *Why C:* `re.sub(pattern, replacement, string)` finds all matches and replaces them. *Why not A:* `re.search()` finds but doesn't replace. *Why not B:* `re.findall()` extracts but doesn't replace. *Why not D:* `re.match()` finds but doesn't replace. *Reference:* Section 22.7

8. What does (?P<name>\d+) do in a regex pattern?

  • A) Matches the literal text "name" followed by digits
  • B) Creates a named capture group called "name" that matches one or more digits
  • C) Creates a non-capturing group for digits
  • D) Matches the word "name" and any following digits
Answer **B)** Creates a named capture group called `"name"` that matches one or more digits *Why B:* `(?P...)` is the Python syntax for named capture groups. The contents can be retrieved with `match.group("name")`. *Why not A:* `(?P...)` is special syntax, not literal text matching. *Why not C:* Non-capturing groups use `(?:...)`. *Why not D:* `name` is the group name, not text to match. *Reference:* Section 22.6

Section 2: True/False with Justification (1 point each)

9. "re.match() checks whether the pattern matches the entire string."

Answer **False** *Explanation:* `re.match()` checks whether the pattern matches at the *beginning* of the string, not the entire string. Use `re.fullmatch()` to check the entire string. For example, `re.match(r"\d+", "123abc")` succeeds (matches `"123"`) even though the full string isn't all digits. *Reference:* Section 22.2

10. "The pattern [^0-9] matches any character that is NOT a digit."

Answer **True** *Explanation:* Inside a character class `[...]`, the caret `^` at the beginning negates the class. So `[^0-9]` means "any character except a digit." This is equivalent to the shorthand `\D`. *Reference:* Section 22.3

11. "You should always use regex instead of string methods because regex is more powerful."

Answer **False** *Explanation:* String methods like `in`, `startswith()`, `split()`, and `replace()` are simpler, more readable, and sufficient for many tasks. Regex adds complexity that's only justified when you need pattern matching, character classes, groups, or variable-format matching. The chapter explicitly discusses when NOT to use regex in Section 22.9. *Reference:* Section 22.9

12. "The pattern \bcat\b would match the word 'cat' inside the word 'concatenate'."

Answer **False** *Explanation:* `\b` is a word boundary anchor. It matches only at the boundary between a word character and a non-word character. In `"concatenate"`, the letters before and after `"cat"` are all word characters, so there are no word boundaries around `"cat"`. The pattern only matches standalone `"cat"`. *Reference:* Section 22.5

Section 3: Short Answer (2 points each)

13. Write a regex pattern that matches a date in MM/DD/YYYY format where MM is 01-12, DD is 01-31, and YYYY is a four-digit year. Then write it again using named groups for month, day, and year.

Sample Answer Basic version:
r"\d{2}/\d{2}/\d{4}"
Named groups version:
r"(?P<month>\d{2})/(?P<day>\d{2})/(?P<year>\d{4})"
Note: A more rigorous pattern would restrict months to 01-12 and days to 01-31, but the basic digit-counting version is acceptable for this chapter's scope. *Rubric — full credit requires:* - Correct use of `\d{n}` quantifiers - Proper slash delimiters (escaped if needed, though `/` isn't a regex metacharacter in Python) - Named groups using `(?P...)` syntax

14. Explain in your own words what "greedy vs. lazy" matching means. Give a concrete example where using greedy matching produces an unwanted result and lazy matching fixes it.

Sample Answer Greedy matching tries to match as many characters as possible, while lazy matching tries to match as few as possible. For example, given the string `"bold and italic"`, the greedy pattern `r"<.*>"` matches everything from the first `<` to the last `>` (the entire string), because `.*` consumes as much as it can. The lazy pattern `r"<.*?>"` matches each individual tag (``, ``, ``, ``) because `.*?` stops at the first `>` it finds. *Rubric — full credit requires:* - Clear explanation of greedy (match most) vs. lazy (match least) - A specific example showing the difference in behavior - Correct use of the `?` suffix to make a quantifier lazy

15. When would you use re.compile() instead of calling re.search() or re.findall() directly? Give one practical scenario.

Sample Answer `re.compile()` pre-compiles a regex pattern into a reusable pattern object. You'd use it when the same pattern is applied many times — for example, processing thousands of log lines in a loop. Compiling once avoids re-parsing the pattern string on every iteration. It also improves readability by giving the pattern a descriptive variable name like `date_pattern` or `email_pattern`. *Rubric — full credit requires:* - Explanation that compile pre-parses the pattern for reuse - A concrete scenario (loop processing, multiple calls) - Bonus: mention of readability benefit

Section 4: Applied Scenario (3 points)

16. You receive a text file containing mixed-format data. Each line contains a person's information in one of these formats:

John Smith, 42, john@example.com
Jane Doe | age: 35 | jane.doe@test.org
Bob (28) - bob_jones@company.co.uk

Write a Python function using regex that extracts the name, age, and email from each line, regardless of format. Return the data as a list of dictionaries.

Sample Answer
import re

def parse_records(lines: list[str]) -> list[dict]:
    results = []
    for line in lines:
        # Extract email (most consistent format)
        email_match = re.search(r"[\w.+-]+@[\w.-]+\.\w{2,}", line)
        email = email_match.group() if email_match else None

        # Extract age (a number, possibly with "age:" prefix)
        age_match = re.search(r"(?:age:\s*)?(\d{1,3})", line)
        age = int(age_match.group(1)) if age_match else None

        # Extract name (alphabetic words at the start)
        name_match = re.match(r"([A-Za-z]+(?:\s+[A-Za-z]+)*)", line)
        name = name_match.group(1) if name_match else None

        results.append({"name": name, "age": age, "email": email})
    return results
*Rubric:* | Criterion | 0 pts | 1 pt | 2 pts | 3 pts | |-----------|-------|------|-------|-------| | Regex patterns | Invalid patterns | 1-2 patterns work | All patterns work | All patterns work with edge cases | | Group extraction | Not attempted | Groups used but incorrectly | Groups work for most cases | Named groups or clean extraction | | Code quality | No function | Function but poor structure | Clean function, some error handling | Clean, documented, handles missing fields |

Scoring & Next Steps

Score Assessment Recommended Action
< 50% Needs review Re-read sections 22.1-22.4, redo Part A exercises
50-70% Partial Review weak areas, redo Part B exercises
70-85% Solid Ready to proceed; revisit greedy/lazy or groups if unsure
> 85% Strong Proceed; consider the research extensions