Loops and Conditionals in Python
A grep simulator reads a file line by line, tests each line against a user-supplied regular expression, and counts how many lines match the pattern.
From grep to a Python loop
The Unix grep command can be understood as a small sequence of programming actions: receive a pattern, open a file, inspect each line, decide whether the line matches, count successful matches, and report the final count. A Python grep simulator is useful because it makes the loop and conditional decision visible. The loop visits every line, while the conditional determines whether the counter changes.
The counter belongs before the loop because it represents the total collected across all iterations.
The execution flow
The simulator follows one repeating path. It obtains a line from the file, tests that line against the user-supplied regular expression, and then takes one of two branches. If the pattern matches, the counter increases by one. If it does not match, the counter remains unchanged. After the loop has processed the final line, the program reports the counter.
A five-line trace
Counting lines that start with X
Trace a search for the pattern ^X through five file lines.
Before iteration: The counter starts at 0.
Author: John: The line does not match ^X, so the counter stays 0.
X-Priority: 1: The line matches ^X, so the counter becomes 1.
Subject: Meeting: The line does not match ^X, so the counter stays 1.
X-Mailer: Outlook: The line matches ^X, so the counter becomes 2.
Date: Monday: The line does not match ^X, so the counter stays 2.
After iteration: The loop ends after all five lines have been tested.
Two lines match ^X.
Building the simulator
The implementation mirrors the trace. The pattern and filename are supplied by the user. The file is opened, the counter is initialized before iteration begins, and each line is tested inside the loop. The counter is reported only after the loop has finished.
import re pattern = input("Pattern: ") filename = input("Filename: ") count = 0 with open(filename) as file: for line in file: line = line.strip() if re.search(pattern, line): count += 1 print(count)
Tracing before debugging
When a count looks wrong, trace the execution state instead of looking only at the final number. For every iteration, record the current line, whether the regular expression matches, and the counter after the conditional. This exposes whether the loop is visiting the expected lines and whether the counter changes only on matches.
| Trace question | What to inspect |
|---|---|
| Which input is being tested? | The current line from the file |
| What decision was made? | Whether the line matches the supplied pattern |
| What changed? | The counter increases only after a match |
| When is the result reported? | After the loop ends |
A compact checklist for tracing one iteration at a time
Mistakes in line matching
Initializing the counter inside the loop
Earlier matches are discarded on every iteration.
Fix:
Initialize the counter before the loop so it can accumulate matches across the entire file.Forgetting to strip the newline character
The text being tested is not exactly the visible line content.
Fix:
Strip each line before applying the regular expression.Using re.match() instead of re.search()
The source identifies this as a common mistake when implementing the simulator.
Fix:
Use re.search for the line test in this grep-style program.Reporting the count inside the loop
The reported value is not yet the total for the file.
Fix:
Report the count after the loop ends.Testing only the first few lines
Matches later in the file are never included.
Fix:
Allow the file iteration to test every line.
| Correct structure | Mistaken structure |
|---|---|
| Counter before the loop | Counter reset inside the loop |
| Test the current line | Test the wrong input |
| Count only after a match | Change the count on every iteration |
| Report after all lines | Report before iteration is complete |
Patterns beyond a prefix
The same loop can search for more complex line formats. Instead of looking only for lines beginning with X, the pattern can describe a structure such as a line containing New Revision: followed by a number. The loop, conditional, counter, and final report remain the same; only the supplied regular expression changes.
Write or adapt the simulator so that it searches a file for lines matching a pattern describing New Revision: followed by a number. Then trace at least three iterations by recording the current line, match result, and counter.
Hints
- Keep the counter initialization before the loop.
- Apply the pattern to every line, not just the first few.
- Check the counter after each conditional decision.
- Report the final count only after the loop ends.
What do you think happens?
A five-line file contains two lines beginning with X. If the counter starts at 0 and increases only when the pattern ^X matches, what should the final count be?
Reveal answer
Answer: 2
The source trace shows two matching lines: X-Priority: 1 and X-Mailer: Outlook. The other three lines leave the counter unchanged.
The complete mental model
- A grep simulator reads a file line by line and tests every line against a user-supplied regular expression.
- Initialize the match counter before the loop.
- Use the conditional to increment the counter only when the current line matches.
- Strip each line before testing it and use re.search rather than re.match for the simulator described here.
- Trace the current line, match result, and counter to debug the program.
Key Takeaways
- The program's loop processes every file line, and its conditional decides whether that line contributes to the count.
- The counter must be initialized before iteration and reported after iteration.
- Tracing each line and counter value makes regex-based matching errors easier to locate.
- Common implementation problems include resetting the counter, retaining newline characters, using re.match instead of re.search, and stopping or reporting too early.