
처음에 코드를 쓰다가 멈췄다.
논리는 맞는 것 같은데, 실행하면 이상한 결과가 나왔다.
"A man, a plan, a canal: Panama"를 넣었더니 False가 나왔다.
회문이 맞는데 왜 False가 나오지?
원인을 찾는 데 생각보다 시간이 걸렸다. 딱 한 글자 차이였다.
문제 소개
문자열이 주어진다. 이 문자열이 회문인지 판별하는 함수를 작성하는 것.
단, 대소문자는 구분하지 않고, 알파벳과 숫자가 아닌 문자는 무시한다.
| 항목 | 내용 |
|---|---|
| 입력 | 임의의 문자열 s |
| 출력 | True 또는 False |
| 핵심 도구 | isalnum(), lower(), 리스트 컴프리헨션, [::-1] |
예제 입출력은 이렇다.
# 입력 / 출력
"A man, a plan, a canal: Panama" → True
"race a car" → False
"Was it a car or a cat I saw?" → True
"Madam" → True
일단 뼈대부터 채웠다
주어진 뼈대 코드는 이랬다.
def is_palindrome(s):
pass
cleand = "".join([c.lower() for c in s if s.isalnum()])
pass
return cleand == cleand[::-1]
논리 흐름은 읽혔다.
알파벳·숫자만 남기고 → 소문자로 변환 → 뒤집어서 비교.
근데 실행하면 모든 입력에 True가 나왔다.
버그가 4개 있었다
하나씩 찾았다.
① pass 두 줄
pass는 "아무것도 안 함" 키워드다. 코드가 없는 블록에 임시로 채워두는 자리표시자.
구현이 다 됐으면 지우면 된다. 동작에 영향은 없지만, 남겨두면 읽는 사람이 혼란스럽다.
② s.isalnum() → c.isalnum() (핵심 버그)
이게 모든 입력에 True가 나온 원인이었다.
# 문제의 코드
[c.lower() for c in s if s.isalnum()]
# ^
# s 전체를 검사하고 있다
for c in s는 문자열을 한 글자씩 꺼내서 c에 담는다.
근데 조건에서 s.isalnum()을 쓰면 문자열 전체를 검사한다.
| 코드 | 검사 대상 | "A man!" 에서 결과 |
|---|---|---|
s.isalnum() |
문자열 전체 | False (공백·! 포함) |
c.isalnum() |
현재 문자 하나 | "A"→True, " "→False, "!"→False |
s.isalnum()이 False면 조건이 항상 False다.
리스트에 아무것도 안 담긴다 → cleaned가 빈 문자열 "" → "" == ""는 True.
그래서 어떤 입력을 넣어도 True가 나왔던 거다.
③ 변수명 오타 — cleand → cleaned
뼈대 코드에 cleand로 적혀 있었다. cleaned로 통일해야 NameError가 안 난다.
최종 코드
BEFORE와 AFTER를 나란히 놓으면 이렇다.
|
BEFORE — 버그 있는 코드
|
AFTER — 수정된 코드
|
바뀐 건 딱 두 가지다.s.isalnum() → c.isalnum(), 그리고 pass 삭제, 변수명 통일.
코드가 하는 일을 한 줄씩 보면
s = "A man, a plan, a canal: Panama"
# 1단계 — 알파벳·숫자만 + 소문자로
# c="A" → isalnum=True → "a"
# c=" " → isalnum=False → 제외
# c="m" → isalnum=True → "m"
# c="," → isalnum=False → 제외
# ...
cleaned = "".join([c.lower() for c in s if c.isalnum()])
# "amanaplanacanalpanama"
# 2단계 — 뒤집어서 비교
cleaned[::-1] # "amanaplanacanalpanama"
return cleaned == cleaned[::-1] # True
[ : : -1] 이 뭔가
파이썬 슬라이싱은 text[start:end:step] 형태다.step을 -1로 주면 뒤에서 앞으로 한 글자씩 이동한다. 문자열 전체가 뒤집힌다.
"python"[::-1] # "nohtyp"
"racecar"[::-1] # "racecar" ← 회문이니까 동일
return cleaned == cleaned[::-1] 의 반환 타입
==은 항상 bool을 반환한다.if cleaned == cleaned[::-1]: return True 같이 쓸 필요 없다. 비교 결과 자체를 바로 return하면 된다.
전체 테스트
tests = [
("A man, a plan, a canal: Panama", True),
("race a car", False),
("Was it a car or a cat I saw?", True),
("Madam", True),
]
for s, expected in tests:
result = is_palindrome(s)
status = "PASS" if result == expected else "FAIL"
print(f'{status} | "{s}" → {result}')
# PASS | "A man, a plan, a canal: Panama" → True
# PASS | "race a car" → False
# PASS | "Was it a car or a cat I saw?" → True
# PASS | "Madam" → True
시간복잡도
| 단계 | 복잡도 | 이유 |
|---|---|---|
| 정제 (컴프리헨션) | O(n) | s 전체를 한 번 순회 |
| 뒤집기 ([::-1]) | O(n) | cleaned 길이만큼 복사 |
| 비교 (==) | O(n) | 두 문자열 앞→뒤 비교 |
| 전체 | O(n) |
[::-1]은 뒤집은 문자열을 새로 만드니까 O(n) 공간도 추가로 쓴다.
투 포인터로 짜면 O(1) 공간으로 줄일 수 있다. 다음 풀이에서 해볼 것 같다.
마무리
버그의 원인은 s와 c 한 글자 차이였다.
for c in s라고 쓴 순간, 지금 다루는 건 c다.
조건도, 변환도, 모두 c에 적용해야 한다.s를 건드리는 순간 한 글자가 아니라 문자열 전체를 상대하게 된다.
루프 변수가 무엇인지 항상 의식하면서 쓰는 것.
이번 문제에서 가져가는 습관이다.
다음 문제로 넘어갔다.
🌿
'크래프톤 정글 > 정글에서 문제풀기' 카테고리의 다른 글
| [정글 베이직 6] 백 트래킹 (1) | 2026.03.09 |
|---|---|
| [정글 베이직 5] 재귀 함수 완성 (0) | 2026.03.08 |
| [정글 베이직 4] 두 수의 합 완성 (0) | 2026.03.08 |
| [정글 베이직 1] 리스트 컴프리헨션 — 파이썬답게 쓴다는 것 (0) | 2026.03.07 |
| [정글 베이직 2] 배열을 돌린다는 것 — (i, j) → (j, n-1-i) (0) | 2026.03.07 |