runtime
53 / 250
lesson 53 String Algorithms · intermediate

Find every match, then extract or replace

find() returns the position of a piece of text, or -1 when it isn't there; find(piece, start) looks again from a later position. Positions let you extract what sits between markers or rebuild the text around each match.

You'll be able to
  • Use find and check for -1 before slicing with the result
  • Find every occurrence by calling find again from just after the last match
  • Extract text between two markers, and rebuild text around matches
The idea

in answers *is it there?*. find answers *where?*: text.find("@") is the position of the first @, or -1 if there is none. Positions are what slicing needs, so find is how you cut a value out of a text: the domain after @, the order number after #.

The -1 is the trap. It is a valid slice position (it means *the last character*), so using it without checking silently gives a wrong piece instead of an error.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · Extract after a marker, safely
def domain(email):
    """Return the part after @ in lower case, or None if there is no @."""
    at = email.find("@")
    if at == -1:
        return None
    return email[at + 1:].strip().lower()

print(domain("jamal.walker62@iCloud.com"))
print(domain("not an email"))
note = "Refund for order #O104839 approved"
start = note.find("#")
print(note[start + 1:start + 8])
missing = "no order number here"
print(missing[missing.find("#") + 1:])

find takes an optional start position: text.find("fee", 10) searches from position 10 on. To find every occurrence, loop: find from 0, record the position, then find again from just after it, until find returns -1. With those positions you can rebuild the text: copy everything up to a match, add something new, and continue after the match. replace does the simplest version of this for you when the piece is exact.

example-2.py · Every mention of a word
def positions(text, piece):
    """Return every position where piece starts in text."""
    found = []
    start = text.find(piece)
    while start != -1:
        found.append(start)
        start = text.find(piece, start + len(piece))
    return found

complaint = "late fee charged, then another fee, fee waived?"
print(positions(complaint, "fee"))
print(positions(complaint, "refund"))
print(complaint.replace("fee", "FEE", 2))

When to use it: to pull a value out from between markers, to locate every match, and to change text around matches. Mistakes to avoid:

  • Slicing with a find result without checking for -1.
  • Restarting the search at the same position, which loops forever.
  • Forgetting that find and replace are case-sensitive; search a lowered copy when case shouldn't matter, but slice the original.
Review
  • find returns a position or -1; check for -1 before slicing.
  • find(piece, start) searches from start; loop from just after each match to find them all.
  • Search a lowered copy when case doesn't matter, but slice the original text.
  • Next: count characters and words, and compare texts by their counts.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Find every match, then extract or replace — String Algorithms · Runtime