남은 약점 2가지(원본 보존, 순환 순회 범위)를 집중 연습하고, 두 패턴을 결합한 종합 문제까지 도전한다.
COS Pro에서 자주 나오는 패턴이다. 대소문자 무시하고 비교하되, 결과는 원래 형태 그대로 돌려줘야 하는 문제.
핵심 원칙은 간단하다:
for w in words:
if w.lower().startswith(prefix_lower): # 비교할 때만 변환
result.append(w) # 결과엔 원본
주의할 점은 변환한 리스트를 따로 만들어서 순회하면 안 된다는 것이다.
# 잘못된 패턴
cleaned_words = [w.lower() for w in words]
for word in cleaned_words: # 여기서 이미 원본을 잃음
result = word # 소문자 버전이 저장됨
# 올바른 패턴
for word in words: # 원본 리스트를 순회
if word.lower() == ...: # 비교만 변환
result = word # 원본 저장
디버깅 문제에서 cleaned_words라는 변수가 만들어져 있었는데, isalpha()로 알파벳만 세는 로직에서는 사실 대소문자 변환 자체가 불필요했다. 이런 함정용 변수를 코드에 넣어두고 순회 대상을 바꾸게 만드는 유형이 시험에 나올 수 있으니, 코드 전체를 비판적으로 읽는 습관이 중요하다.
평균 이상인 학생을 점수 내림차순으로 반환하되, 동명이인(대소문자 무시)은 먼저 나온 학생만 포함하는 함수를 작성했다.
처음에는 정렬 후 중복 제거를 했더니, 점수가 높은 쪽이 먼저 처리되어 원래 순서의 이름이 아닌 다른 대소문자 버전이 남았다.
# 문제가 되는 순서
students_sorted = sorted(students, key=lambda x: x[1], reverse=True)
# ("ALICE", 90)이 ("Alice", 85)보다 먼저 → "ALICE"가 남음
# 올바른 순서: 중복 제거(원본 순서) → 필터 → 정렬
"먼저 나온"이라는 기준은 원본 리스트 순서를 의미하므로, 정렬 전에 중복 제거를 해야 한다.
수업 중 질문: "코드가 마음에 안 드는데 더 효율적이고 깔끔한 방법 추천해줘"
→ 두 가지 개선 포인트를 배웠다:
| 기존 | 개선 | 이유 |
|---|---|---|
| for 루프로 합계 계산 | sum(s for _, s in students) | 한 줄로 평균 계산 |
| 리스트 컴프리헨션으로 매번 중복 체크 | set()으로 체크 | O(n) → O(1) 탐색 |
특히 set을 이용한 중복 체크는 시험에서도 자주 쓰이는 최적화 패턴이다.
# 최종
def above_average_students(students):
avg = sum(s for _, s in students) / len(students)
seen = set()
unique = []
for name, score in students:
if name.lower() not in seen and score >= avg:
seen.add(name.lower())
unique.append((name, score))
return [name for name, score in sorted(unique, key=lambda x: x[1], reverse=True)]
원형 문자열에서 길이 k인 부분 문자열을 탐색할 때:
extended = text * 2
for i in range(len(text)): # len(extended)-k 아님!
sub = extended[i:i+k]
text * 2로 확장해도, 원래 문자열의 시작 위치는 len(text)개뿐이다. len(extended) - k를 쓰면 중복 탐색이 발생한다.
| 비교 | range | text="dacb", k=3 |
|---|---|---|
len(text) | range(4) → 0,1,2,3 | 정확히 4개 위치 |
len(extended)-k | range(5) → 0,1,2,3,4 | i=4는 "cbd"로 i=2와 중복 |
마지막 문제는 원형 배치된 학생 이름 리스트에서 연속 k명의 이름을 이어붙인 문자열이 사전순으로 가장 빠른 그룹을 찾아 원래 이름 리스트로 반환하는 함수였다.
두 약점을 한 번에 적용해야 하는 종합 문제:
def smallest_circular_group(names, k):
names_extended = names * 2 # 순환 처리
names_lowered = [name.lower() for name in names_extended] # 비교용 변환
result = "".join(names_lowered[:k])
result_idx = 0
for i in range(1, len(names)): # 순회 범위: len(names)
sub = "".join(names_lowered[i:k+i])
if sub < result:
result = sub
result_idx = i
return names_extended[result_idx: result_idx+k] # 반환은 원본
names_lowered로 비교하고 names_extended에서 꺼내서 원본 보존len(names)로 정확히 제한result_idx로 위치를 추적해서 원본 리스트에서 슬라이싱참고: 디버깅용 print()는 시험 제출 전에 반드시 삭제해야 한다. 감점 요인이 될 수 있다.