20260412 오늘의 학습: 약점 집중 연습 2회차

Yesol Lee·2026년 4월 12일

COS Python

목록 보기
17/30

지난 학습 요약

  • 약점 집중 연습 1회차에서 슬라이딩 윈도우 완전 정착 확인
  • 문자열 순환 text*2 패턴 정착, 순회 범위 구분은 추가 연습 필요
  • 데이터 변환 후 원본 소실 실수 1건 발견 — 반복 노출 필요 판정

오늘 수업 계획

남은 약점 2가지(원본 보존, 순환 순회 범위)를 집중 연습하고, 두 패턴을 결합한 종합 문제까지 도전한다.


학습 내용 정리

1. 원본 보존 패턴 — "비교는 변환, 반환은 원본"

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()로 알파벳만 세는 로직에서는 사실 대소문자 변환 자체가 불필요했다. 이런 함정용 변수를 코드에 넣어두고 순회 대상을 바꾸게 만드는 유형이 시험에 나올 수 있으니, 코드 전체를 비판적으로 읽는 습관이 중요하다.

2. 중복 제거 순서의 중요성

평균 이상인 학생을 점수 내림차순으로 반환하되, 동명이인(대소문자 무시)은 먼저 나온 학생만 포함하는 함수를 작성했다.

처음에는 정렬 후 중복 제거를 했더니, 점수가 높은 쪽이 먼저 처리되어 원래 순서의 이름이 아닌 다른 대소문자 버전이 남았다.

# 문제가 되는 순서
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)]

3. 순환 순회 범위 복습 — len(text)가 정답

원형 문자열에서 길이 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를 쓰면 중복 탐색이 발생한다.

비교rangetext="dacb", k=3
len(text)range(4) → 0,1,2,3정확히 4개 위치
len(extended)-krange(5) → 0,1,2,3,4i=4는 "cbd"로 i=2와 중복

4. 종합 문제 — 순환 + 원본 보존

마지막 문제는 원형 배치된 학생 이름 리스트에서 연속 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()는 시험 제출 전에 반드시 삭제해야 한다. 감점 요인이 될 수 있다.


오늘의 결과

  • 약점 연습 5문제 전부 1차 정답 — 원본 보존, 순환 범위 모두 정착 판정
  • 주요 약점 모두 해소 완료, 내일부터 프로그래머스/백준 문제 풀이로 전환
profile
문서화를 좋아하는 개발자

0개의 댓글