B2.1.2 · Programming fundamentals
Extracting and manipulating substrings
A string is built out of smaller pieces. This statement is about pulling out exactly the piece you need, then using it to build, correct, or replace what's already there.
Explainer
GreenLine Bus Co. issues every booking as one string of text, printed straight onto the ticket: Isla Mitchell|24B|0930|RTN. A human reads that at a glance, name, route, time, return trip. A program cannot. It has to pull the route number out on its own to show it on the departure board, pull the time out to sort tickets by departure, and sometimes go back in and fix one piece, like correcting a route after a platform change, without disturbing the rest of the string. All of that starts with the same skill: taking a smaller piece out of a bigger string.
A substring is any sequence of consecutive characters taken from within a larger string. "24B" is a substring of the booking string above; so is "Isla Mitchell", and so is the single character "|". The original string is never changed by taking one, a substring is always a fresh, separate piece of text.
The statement asks for two different skills, in this order. Extracting means identifying where a substring starts and ends, then reading it out, a purely read-only operation. Manipulating means doing something with what you extracted: altering it (changing its case or content), concatenating it onto other text to build something new, or replacing it back into the original string.
Every operation on this page, slicing, find(), replace(), upper(), produces a brand new string. None of them edit the original characters directly, in either Python or Java, both languages treat strings as immutable. That is why every example on this page reassigns the result to a variable: ticket = ticket.replace(old, new) keeps the new version only because it was written back into ticket, the call on its own changes nothing.
Explainer
Five operations, in Python and Java
The syllabus names extracting plus three kinds of manipulating, altering, concatenating, replacing, and every one of them shows up in the worked example below. Both languages are examinable, so read each row across.
| Operation | What it does | Python | Java |
|---|---|---|---|
| Extract by position | Take characters from a start index up to, but not including, an end index | ticket[4:7] | ticket.substring(4, 7) |
| Extract to the end | Take every character from a start index onward | ticket[4:] | ticket.substring(4) |
| Search for a substring | Return the index of the first match, or a not-found signal | ticket.find("|") → index or -1 | ticket.indexOf("|") → index or -1 |
| Replace ONE character | Produce a new string with every occurrence of a single character swapped | ticket.replace(old, new) | ticket.replace('e', '@') |
| Replace MULTIPLE characters | Produce a new string with every match of a whole piece of text swapped | ticket.replace(old, new) | ticket.replaceAll("is", "will be") |
| Concatenate | Join smaller pieces of text into one longer string | a + b | a + b |
| Alter (change case) | Produce a new string with different capitalisation | ticket.lower() | ticket.toLowerCase() |
A slice's end index is exclusive: ticket[4:7] takes the characters at positions 4, 5 and 6, never position 7. Java's substring(begin, end) follows the identical rule, so the two columns above always take the same three characters for the same numbers. And find()/indexOf() return -1, not an error, when the substring genuinely isn't there, code that assumes it was always found will silently use position -1 and slice the wrong thing.
Python's replace() swaps every occurrence of a piece of text, whether it's one character or many, in a single function. Java splits this into two: replace() for a single character, and replaceAll() when the thing being swapped is more than one character. Reaching for plain replace() in Java when the target is a whole word is a real, easy mistake to make.
Concatenation has more than one form worth recognising too. + is the most direct, but Python also offers f-strings for building a message out of several pieces at once: f"{name} - Route {route} at {time}" produces exactly the same result as chaining + between each piece, just without the repeated operators.
Worked example
Parsing a booking code, one field at a time
The booking string has four fields separated by three | characters, and the fields are different lengths each time (a passenger's name is never a fixed number of characters), so fixed positions won't work. The program has to search for each delimiter before it can slice.
find() is not enough
Calling ticket.find("|") three times in a row with no change would return the exact same position three times, the first one, every time. Each later call has to start searching just past the previous match, which is what the second argument to find(sub, start) is for.
Step 1. The raw booking string arrives. Nothing has been extracted yet, it is one piece of text with no structure the program can see.
Step 2. Find the three delimiter positions. Each call searches from just after the last match, so the same character further along the string is never re-found by mistake.
Step 3. Slice out the four fields. Each slice's end is exclusive, so ticket[:p1] stops right before the first |, and never includes it.
Step 4. Concatenate a friendly summary line. The four extracted pieces, plus some fixed text, are joined into one new string with +. None of the original variables change.
Step 5. Replace the route after a platform change. replace(old, new) swaps every occurrence of the old substring, then the result is written back into ticket, since replace() on its own would just be thrown away.
Step 6. Alter the trip type for a printed label. lower() returns a new string in lower case; the original trip_type is untouched unless the result is reassigned to it.
Try it yourself
Run the booking-code parser
The same program as the steps above, live and editable. Two experiments are waiting at the bottom: change the passenger's name to something longer and confirm the rest still parses correctly, then try searching for a delimiter that isn't in the string and see what find() actually returns.
Running in offline fallback mode. The full Python engine couldn't load (blocked school network, or no internet), so this console switched automatically to a small hand-written engine. It supports slicing, find(), replace(), upper() and lower() on strings, exactly what this page needs, but it isn't full Python.
Check your understanding
A string code = "R12-450". What does code.find("-") return, and what does code[:code.find("-")] then produce?
Common mistake
B2.1.2-style substring questionsRecurringMarkers report two things going wrong most often on questions that chain several string operations together: substring extraction itself applied with the wrong indices or boundaries, and the extracted pieces then joined back together in the wrong order, or with a piece dropped entirely, when building the final result.
A specific version of the joining mistake is reusing find() with no start position once a delimiter appears more than once in the string. Every call after the first returns the SAME first match again, so every field after the first gets built from the wrong positions, and the pieces joined at the end come out in the wrong order or missing a piece.
The boundary mistake is usually the exclusive-end rule: treating a slice's end index as inclusive. ticket[4:7] is three characters, at positions 4, 5 and 6, not four, and a solution that is off by one in the end index silently drops or duplicates the character right at the boundary.
Answer frame that scores: work out every delimiter position first, always passing the previous position onward as the next search's start → slice with the exclusive-end rule firmly in mind → reassign the result of replace()/upper()/lower() back to a variable, since none of them change the original string on their own.
What the bug looks like: p2 = ticket.find("|") written the exact same way as p1, with no second argument. p2 ends up equal to p1, so ticket[p1+1:p2] slices a string with its start AFTER its end, and produces an empty string instead of the route code.
What it should look like: every find() call after the first passes previous_position + 1 as its own start argument, so the search genuinely moves forward through the string instead of finding the same character again.
Revision questions
Past-paper style. Write an answer, then reveal the marking points and mark yourself.
[2]Distinguish between extracting a substring and replacing a substring.
Marking points
评分要点
- Extracting a substring reads part of a string by position and produces a new string, leaving the original unchanged.
- Replacing a substring produces a new string in which occurrences of one piece of text are swapped for another; the original is still unchanged unless the result is reassigned.
- 提取子串按位置读取字符串的一部分,产生一个新字符串,原字符串不变。
- 替换子串产生一个新字符串,其中某段文本的出现被换成另一段;除非把结果赋回,否则原字符串仍然不变。
[3]A string code = "SCR14-A7-2130" holds a screen number, seat and showtime separated by dashes. Construct code to extract all three fields.
Marking points
评分要点
- Split code on the dash character to get a list of three fields, or slice by index.
- screen = the part before the first dash (code[0:5] or fields[0]).
- seat = the middle part between the dashes (fields[1]).
- showtime = the part after the last dash (fields[2]).
- 用短横字符对 code 做 split,得到三个字段的列表,或按索引切片。
- screen = 第一个短横之前的部分(code[0:5] 或 fields[0])。
- seat = 两个短横之间的中间部分(fields[1])。
- showtime = 最后一个短横之后的部分(fields[2])。
[4]Explain why ticket.replace("24B", "31A") on its own does not change the value stored in ticket, and state what must be done instead.
Marking points
评分要点
- Strings are immutable, so ticket.replace(...) cannot change the string ticket points to; it can only build and return a new string.
- On its own the returned new string is discarded because nothing uses it.
- To keep the change, assign the result back: ticket = ticket.replace(24B, 31A).
- 字符串不可变,所以 ticket.replace(...) 不能改变 ticket 所指向的字符串;它只能构建并返回一个新字符串。
- 单独使用时,返回的新字符串因为没有东西使用它而被丢弃。
- 要保留改动,把结果赋回:ticket = ticket.replace(24B, 31A)。
Flashcards
Active recall for this statement’s key terms.
Keep this statement’s practice with you
Free account: unlock every revision question and flashcard for B2.1.2, and every other IB CS 2027 statement.