The Encoded Signal
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L4
- Asked in
- Fivetran
Problem
A device firmware emits letter-frequency telemetry as a compact code with no literal letters in it, and you expand it into counts for the 26 lowercase letters a through z. A single digit from 1 to 9 names one of the first nine letters (1 is a, 9 is i), while two digits immediately followed by # name a letter by position, so 10# is j and 26# is z. Either token may carry a parenthesized repeat count like 1(3), and a bare token counts once.
Letters folded into digits, counts hidden in the punctuation.
Example 1
Input:
encoded = "1(2)2(3)1324#26#(5)"
Output:
[3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,5]
Example 2
Input:
encoded = "1234"
Output:
[1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
Worked solution and explanation
What this really is
Strip the telemetry costume and this is a single-pass tokenizer with one branch that bites: at every cursor position you have to decide whether the next letter is one digit or two digits plus a #, and the only way to tell is to peek two characters ahead for that #. Anyone can map a digit to a letter. What separates people is committing to the right token width before they move the cursor. Read one digit when the token was really 24# and the entire rest of the string shears off by one, silently, with no crash to warn you.
The decision at every cursor
Step 1: Read the three token shapes
A single digit 1-9 is a letter a-i. Two digits followed by # are a letter j-z by position. A trailing (n) is a repeat count.
Step 2: Pick the token width before advancing
At each position, look two characters ahead. A # there means consume two digits and skip the #; otherwise consume one digit. This single check is what keeps the cursor aligned.
Step 3: Apply the count and accumulate
After the letter is known, if the next character is (, read up to the matching ) as a whole number and use it as the count, else the count is 1. Add that count into the letter's slot so repeats accumulate.
The solution
def the_encoded_signal(encoded: str):
freq = [0] * 26
i = 0
n = len(encoded)
while i < n:
if i + 2 < n and encoded[i + 2] == '#':
letter = int(encoded[i:i + 2]) - 1
i += 3
else:
letter = int(encoded[i]) - 1
i += 1
count = 1
if i < n and encoded[i] == '(':
close = encoded.index(')', i)
count = int(encoded[i + 1:close])
i = close + 1
freq[letter] += count
return freqDecide token width, then read an optional count, then accumulate.
- Greedy on the digit
- Treat each digit as a letter the instant you see it. The first digit of a
24#token becomes its own letter, the cursor lands on4#, and everything after it is off by one. The output is quietly wrong, never throwing. - Greedy on the hash
- Before consuming anything, peek two slots ahead. A
#there means the two digits here are one letter (j through z) and the cursor jumps 3; otherwise it is a single-digit letter and the cursor jumps 1. The width is settled before you advance.
Common follow-up questions
- If every letter token always carried an explicit count, how would the parser change? (Tests simplifying the tokenizer when one branch disappears.)
- How would you encode a 26-element frequency list back into this format? (Tests the inverse transform under the same rules.)
- What would you do if the stream could contain malformed tokens, like a `#` with only one preceding digit? (Tests defensive parsing and error reporting.)
- How would you extend this beyond a through z to an arbitrary alphabet? (Tests generalizing the position-to-symbol mapping.)