S1Q2 · Encode by Shifting Ends¶
⚡ Quick Reference
Function: encode_by_shifting_ends(s: str) -> str
Core idea: shift the first character forward and the last backward by 1 in ASCII, with guards for 'z' (first) and 'a' (last).
def encode_by_shifting_ends(s: str) -> str:
first = s[0] if s[0] == 'z' else chr(ord(s[0]) + 1)
last = s[-1] if s[-1] == 'a' else chr(ord(s[-1]) - 1)
if len(s) == 1:
return first
return first + s[1:-1] + last
Key rules:
- First char: +1 ASCII (forward), unless it's 'z' → keep unchanged
- Last char: -1 ASCII (backward), unless it's 'a' → keep unchanged
- Middle characters: unchanged
- Length 1: only the first rule applies (first == last, use first's rule)
Problem Statement¶
Problem
Write a function encode_by_shifting_ends(s) that shifts the first character forward and the last character backward by one ASCII position, keeping the middle unchanged. Exception: 'z' as first or 'a' as last stays unchanged.
Examples:
"abc"
"bbb"
"hello"
"ielln"
"zoo"
"zon"
"a"
"a"
"za"
"za"
Understanding the problem¶
Three parts of the string get different treatment:
| Part | Rule |
|---|---|
s[0] (first) |
chr(ord(s[0]) + 1) unless s[0] == 'z' |
s[1:-1] (middle) |
Unchanged |
s[-1] (last) |
chr(ord(s[-1]) - 1) unless s[-1] == 'a' |
Edge cases:
- Length 1: s[0] and s[-1] point to the same character. Apply only the first-char rule (forward shift / 'z' guard).
- Length 2: s[1:-1] is an empty string - no middle.
ord() and chr()
ord('a') = 97, ord('z') = 122. Each letter is one ASCII step apart. chr(ord(c) + 1) moves forward one letter, chr(ord(c) - 1) moves backward. Guards prevent going out of the a-z range.
Tracing all examples¶
"abc" → "bbb"
first: 'a' → chr(97+1) = 'b'
middle: (none - only 1 char between ends... wait, len=3)
s[1:-1] = 'b' → unchanged
last: 'c' → chr(99-1) = 'b'
Result: 'b' + 'b' + 'b' = "bbb" ✓
"hello" → "ielln"
first: 'h' → chr(104+1) = 'i'
middle: s[1:-1] = 'ell' → unchanged
last: 'o' → chr(111-1) = 'n'
Result: 'i' + 'ell' + 'n' = "ielln" ✓
"zoo" → "zon"
first: 'z' → 'z' is 'z' → keep 'z'
middle: s[1:-1] = 'o' → unchanged
last: 'o' → chr(111-1) = 'n'
Result: 'z' + 'o' + 'n' = "zon" ✓
"a" (length 1) → "a"
first == last == 'a'
Apply first-char rule: 'a' != 'z' → chr(97+1) = 'b'?
Wait - 'a' as first → shift forward to 'b'. But output is "a".
Re-reading: "a" → "a". For length 1, the single character is both first and last. The last-char rule says if s[-1] == 'a' keep unchanged. Since 'a' == 'a', keep it. For a single-character string, the last-char rule takes precedence (or both rules conflict and 'a' guard wins).
"za" → "za"
Solution approaches¶
def encode_by_shifting_ends(s: str) -> str:
# Encode first character
if s[0] == 'z':
first = 'z'
else:
first = chr(ord(s[0]) + 1)
# Single character - apply 'a' guard
if len(s) == 1:
return s[0] if s[0] == 'a' else first
# Encode last character
if s[-1] == 'a':
last = 'a'
else:
last = chr(ord(s[-1]) - 1)
# Combine
middle = s[1:-1]
return first + middle + last
def encode_by_shifting_ends(s: str) -> str:
shift_up = lambda c: c if c == 'z' else chr(ord(c) + 1)
shift_down = lambda c: c if c == 'a' else chr(ord(c) - 1)
if len(s) == 1:
return shift_down(s[0]) # 'a' guard wins for single char
return shift_up(s[0]) + s[1:-1] + shift_down(s[-1])
Named lambdas for each shift direction - shift_up with 'z' guard, shift_down with 'a' guard. Clean and reusable.
Key takeaways¶
ord() and chr() for ASCII shifting
ord(c) converts a character to its ASCII code; chr(n) converts back. chr(ord(c) + 1) moves one letter forward, chr(ord(c) - 1) moves one letter backward.
s[1:-1] for the middle slice
For strings of length 1 or 2, s[1:-1] returns an empty string - concatenating it adds nothing. This means the same formula works for all lengths ≥ 2 without special-casing short strings.
Length-1 edge case needs explicit handling
When len(s) == 1, the same character is both first and last. The two rules conflict - the 'a' guard (last-char rule) takes precedence, returning the character unchanged if it's 'a'.