Goal Parser Interpretation: String Replacement Patterns
You are given a string command that consists of "G", "()", and "(al)" in some order. The Goal Parser interprets "G" as the string "G", "()" as "o", and "(al)" as "al". Return the interpreted string after parsing all tokens from left to right.
This is LeetCode 1678: Goal Parser Interpretation, and it is a clean exercise in sequential string parsing with fixed token rules.
Why this problem matters
Goal Parser Interpretation teaches you the core pattern behind many string transformation problems: scan through the input, identify tokens, and map each token to an output. This exact technique appears in template engines, compilers, and protocol parsers. The problem isolates the token-matching step so you can practice it without the complexity of nested structures or ambiguous grammars.
It also reinforces the habit of thinking about what the next character tells you. When you see "G", you know the token immediately. When you see "(", you need to peek ahead to decide between "()" and "(al)". This "lookahead" idea is fundamental in parsing and shows up in problems involving expression evaluation, decoding, and pattern matching.
The key insight
There are only three possible tokens, and they never overlap or conflict. The character at position i determines which token you are looking at:
- If
command[i]is"G", the token is"G"(length 1). - If
command[i]is"("andcommand[i+1]is")", the token is"()"(length 2). - If
command[i]is"("andcommand[i+1]is"a", the token is"(al)"(length 4).
You never need to backtrack. A single lookahead character after "(" resolves the ambiguity completely.
The solution
def interpret(command: str) -> str:
result = []
i = 0
while i < len(command):
if command[i] == "G":
result.append("G")
i += 1
elif command[i + 1] == ")":
result.append("o")
i += 2
else:
result.append("al")
i += 4
return "".join(result)
We walk through the string with a pointer i. At each position, we check the current character. If it is "G", we append "G" and advance by 1. If we are at "(" and the next character is ")", we matched "()", so we append "o" and advance by 2. Otherwise, we matched "(al)", so we append "al" and advance by 4. At the end, we join the collected pieces into a single string.
You can also solve this with command.replace("()", "o").replace("(al)", "al"), which is a one-liner. The character-by-character approach is worth learning because it generalizes to problems where built-in replace does not work, such as when tokens can nest or overlap.
Visual walkthrough
Step 1: Match "G"
Position 0: matched G → append "G"
Result so far: G
Step 2: Match "()"
Position 1: matched () → append "o"
Result so far: Go
Step 3: Match "(al)"
Position 3: matched (al) → append "al"
Result so far: Goal
Each step highlights the current token in the input string and shows the accumulated result. The parser never revisits a character, so it processes the entire input in a single left-to-right pass.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Character-by-character parsing | O(n) | O(n) |
| String replace (two passes) | O(n) | O(n) |
Time: The pointer i moves forward by at least 1 on every iteration, so the while loop runs at most n times where n is the length of the command string. Each iteration does O(1) work.
Space: The result list stores at most n characters. The join at the end creates a new string of at most n characters. Total: O(n).
The building blocks
1. Index-based string scanning
The core technique is maintaining an index i and advancing it by different amounts depending on what token you match. This is the same pattern used in problems like decoding encoded strings, parsing mathematical expressions, and processing escape sequences.
i = 0
while i < len(s):
if s[i] == some_char:
i += 1
else:
i += token_length
2. One-character lookahead
When the current character is ambiguous (it could start multiple tokens), you peek at the next character to resolve it. Here, "(" could begin "()" or "(al)", but checking command[i+1] immediately tells you which one. This is safe because the problem guarantees the input is well-formed.
if command[i + 1] == ")":
result.append("o")
i += 2
else:
result.append("al")
i += 4
Edge cases
- All
"G"characters: the input is something like"GGG". Each character is its own token. The output equals the input. - All
"()"tokens: the input is"()()()". Every pair maps to"o", producing"ooo". - All
"(al)"tokens: the input is"(al)(al)". Each group maps to"al", producing"alal". - Single token: the input is just
"G","()", or"(al)". The parser handles each correctly in one iteration. - Mixed tokens in any order: the token boundaries never overlap, so the parser works regardless of ordering.
From understanding to recall
You have seen how a simple index pointer and a one-character lookahead solve this problem cleanly. The logic is minimal, but under interview pressure, it is easy to fumble the index arithmetic. Should i advance by 2 or 3 for "()"? Is the lookahead at i+1 or i+2 for the "(al)" case?
Spaced repetition locks these details into muscle memory. You write the scanning loop from scratch, check yourself, and repeat at growing intervals. After a few rounds, the index advances flow out automatically. You do not need to reason through each case. You just write it.
Related posts
- Reverse String - Another foundational string manipulation problem that builds comfort with index-based traversal
- Find the Index of the First Occurrence in a String - String searching with a scanning pointer, the natural next step after simple token matching
- Count and Say - Sequential string generation using the same scan-and-build pattern with lookahead logic
CodeBricks breaks Goal Parser Interpretation into its index-scanning and lookahead building blocks, then drills them independently with spaced repetition. You type each piece from scratch until the pattern is automatic. When a string parsing problem shows up in your interview, you do not hesitate. You just write it.