A Creative Problem Solution#
Note
Source: Problem statement, incremental-development approach, and
test-first framing adapted from the C# edition
(basicstringops/problem-solving-replace.rst). The Python
implementation using find and slicing is an original adaptation
(the C# version used Substring and IndexOf).
The exercises so far have been straightforward: learn a method, apply it. Now we introduce a problem-solving exercise to practice combining what you know in a less obvious way.
The Problem#
Given a string such as "It was the best of times.", find and replace
the first occurrence of one substring ("best") with another
("worst"), producing "It was the worst of times.".
Python’s str.replace() already does this, so for this exercise we
will implement it ourselves using only indexing, slicing, and
find(). The goal is problem-solving practice, not reinventing the
standard library.
Function Signature and Tests First#
Before writing the body, define the interface and write test cases. This is a form of test-first development: write tests that describe what the function must do, then make them pass.
def string_replace(s, target, replacement):
"""Return s with the first occurrence of target replaced by replacement.
If target is not in s, return s unchanged.
"""
pass # to be implemented
def main():
print(string_replace("It was the best of times.", "best", "worst"))
# expected: It was the worst of times.
print(string_replace("abcabc", "bc", "X"))
# expected: aXabc
print(string_replace("hello", "xyz", "Q"))
# expected: hello (target not found)
print(string_replace("aaaa", "aa", "B"))
# expected: Baaa (only first occurrence)
if __name__ == '__main__':
main()
Running this with pass as the body will print None four times —
but the test structure is in place.
Building the Solution Incrementally#
A common mistake is to try to write the whole function at once. Instead, build it in small steps, testing after each one.
Step 1 — Find the location of target
We need to know where the target is. Use find():
i = s.find(target)
If target is not in s, find() returns -1. Handle that
case first:
def string_replace(s, target, replacement):
i = s.find(target)
if i == -1:
return s # nothing to replace
Now the third test already passes.
Step 2 — Visualize the pieces
For s = "It was the best of times." and target = "best":
Index: 0 1 2
0123456789012345678901234
s: It was the best of times.
find("best") returns 12. We need three pieces:
Everything before the target:
s[:i]→"It was the "The replacement itself:
replacement→"worst"Everything after the target:
s[i + len(target):]→" of times."
Step 3 — Concatenate the pieces
def string_replace(s, target, replacement):
i = s.find(target)
if i == -1:
return s
before = s[:i]
after = s[i + len(target):]
return before + replacement + after
All four tests should now pass. Run and verify.
Reflection#
A few lessons from this exercise:
Write tests first. They clarify the goal and let you check progress at each step.
Use concrete examples to find the right index arithmetic before writing general code.
Build incrementally. Handle the easy case (not found) first, then the main case.
``len(target)`` is what you need to skip past the target — not a literal
4or5.
Of course, in real code you would just write
s.replace(target, replacement, 1). But walking through the
implementation teaches you to think with indices, slicing, and
find() — all tools you will need again.