20260508 오늘의 학습: 전 영역 워밍업 11문제

Yesol Lee·2026년 5월 8일

COS Python

목록 보기
26/30

지난 학습 요약

지난 세션(4/28)에서는 90도 회전 공식과 약수 함수 슬림화로 3문제 전부 1차 정답을 받았다. 8일 공백에도 arr[n-1-j][i] 공식을 정확히 떠올리며 정착 신호 확인. 함수 작성 4세션 연속 자력 정답으로 추세를 굳혔다.

오늘 수업 계획

시험까지 일주일 남은 시점에서 약 10일 만에 다시 책상 앞에 앉았다. 바로 90분 타이머 모의고사로 들어가는 건 무리라 판단해, 전 영역을 한 문제씩 가볍게 노출하는 워밍업 트랙을 짰다. 총 11문제. 자료형/조건/반복 → 리스트/문자열/딕셔너리/집합 → 정렬/함수/예외 → 스택/이진탐색/수학/구현 순서로, 지금까지 배운 모든 개념을 자연스럽게 한 번씩 점검한다. 컨디션 회복이 1차 목표, 90분 타이머 진입 가능 여부 진단이 2차 목표.


학습 내용 정리

1. 반복문 + 조건 (sum_multiples_of_3)

a부터 b까지(둘 다 포함) 정수 중 3의 배수의 합을 반환. 음수 입력도 처리해야 한다.

def sum_multiples_of_3(a, b):
    total = 0
    for i in range(a, b+1):
        if i % 3 == 0:
            total += i
    return total

한 줄 컴프리헨션 형태도 익혀두자 — 시험 빈칸으로 자주 나오는 형태.

return sum(i for i in range(a, b+1) if i % 3 == 0)

수업 중 질문: 테스트케이스 (-5, 5)의 기대값이 왜 3이지? -3 + 0 + 3이면 0 아닌가?

학습자가 첫 문제부터 출제자 검산을 시작했다. 정확한 지적이고 내 계산 실수였다. 정정: -3 + 0 + 3 = 0이 맞다. 참고로 Python에서 -3 % 3 == 0, 0 % 3 == 0 둘 다 성립하므로 n % 3 == 0 조건 하나로 음수까지 잘 잡힌다. 검증 습관이 10일 공백에도 살아있다는 좋은 신호.


2. 리스트 슬라이싱 + 컴프리헨션 (filter_reverse)

리스트를 뒤집은 후 k 이상인 값만 필터링.

def filter_reverse(nums, k):
    return [i for i in nums if i >= k][::-1]

인사이트: filter와 reverse는 교환 가능

문제는 "뒤집은 후 필터"였는데 풀이는 "필터한 후 뒤집기"다. 결과는 같다 — 둘 다 상대 순서를 보존하는 연산이라 교환 법칙이 성립하기 때문. 그리고 필터로 먼저 줄인 뒤 뒤집는 게 작업량이 더 적어서 살짝 효율적이기까지 하다.

[i for i in nums if i >= k][::-1]   # 효율적
[i for i in nums[::-1] if i >= k]   # 문제 그대로

메모해둘 패턴: 두 연산의 순서를 바꿔도 결과가 같은지 자문하는 습관. 시험에서 빈칸 답이 두 형태 모두 정답인 경우가 종종 있다.


3. 문자열 회문 판별 (is_palindrome)

대소문자와 공백을 무시하고 회문 판별.

def is_palindrome(s):
    word = s.replace(" ", "").lower()
    return word == word[::-1]

2단계 공식: (1) 정규화(공백 제거 + 소문자), (2) word == word[::-1] 비교 한 줄. 회문 패턴은 손에 박힌 상태.

사이드 노트: 만약 숫자나 구두점도 무시해야 하는 강한 회문 문제라면 re.sub(r'[^a-z0-9]', '', word) 패턴이 필요하다. 오늘은 워밍업이라 안 갔지만 응용 문제에서 떠올릴 수 있게 익혀둔다.


4. 딕셔너리 / Counter — 가장 많이 등장한 글자 (most_char)

가장 많이 등장한 글자와 횟수를 튜플로. 동률이면 먼저 나온 글자 우선.

풀이 A — Counter 한 줄

from collections import Counter

def most_char(s):
    return Counter(s).most_common(1)[0]

동률 처리가 자동으로 되는 이유

  • Counterdict 상속이라 삽입 순서를 보존 (Python 3.7+)
  • most_common(n)은 내부적으로 heapq.nlargest를 쓰는데, 이건 동률일 때 입력 순서를 유지하는 안정 정렬
  • 그래서 "먼저 등장한 글자"가 자동으로 앞에 옴

most_common(1)[0]은 거의 공식처럼 외워도 된다.

수업 중 질문: dict.get() 방식으로도 풀어보다가 막혔다. 이 코드는 왜 에러나?

tmp_dict = {}
for c in s:
    tmp_dict[c] = tmp_dict.get(c, 0) + 1
return max(tmp_dict, key=lambda x: x[1])   # IndexError 발생

원인은 max(tmp_dict, ...)가 딕셔너리에서 key만 꺼낸다는 것. 그래서 lambda x: x[1]x는 글자 한 개('a', 'p' 등)가 되고, 한 글자짜리 문자열에 x[1]을 하면 IndexError가 난다.

수정: .items()를 넘긴다.

return max(tmp_dict.items(), key=lambda x: x[1])

이렇게 하면 x('a', 1) 같은 튜플이 되고 x[1]이 횟수가 된다. 그리고 동률 처리는 Counter 버전과 동일 — dict.items()가 삽입 순서로 순회하고 max는 먼저 본 max를 반환하므로 "먼저 등장한 글자"가 자동으로 우선된다.

자료구조와 순회 패턴 정리

표현순회 시 꺼내는 것
for x in dictkey만
for x in dict.values()value만
for x in dict.items()(key, value) 튜플

max / min / sorted / for 모두 같은 규칙. 한 번 의식해두면 비슷한 에러 다시 안 난다.


5. 집합(set) 교집합 (common_chars)

두 문자열에 모두 등장하는 글자를 알파벳순으로.

def common_chars(a, b):
    return sorted(set(a) & set(b))

집합 연산자 4가지 (시험 빈출)

기호의미예시 (A={1,2,3}, B={2,3,4})
A & B교집합 (intersection){2, 3}
A \| B합집합 (union){1, 2, 3, 4}
A - B차집합 (difference){1}
A ^ B대칭차 (symmetric difference){1, 4}

슬림화 체크 — 신규 약점 자연 노출

처음 작성한 풀이는 sorted(list(set(a) & set(b))) 였다. list() 호출이 불필요하다. sorted()는 어떤 iterable이든 받아서 항상 list를 반환하기 때문.

이게 4/27에 새로 잡힌 약점인 "불필요 변환/분기 추가" 패턴이다. 자연 노출에서 1회 발생. 의식하면 다음번엔 안 나올 가능성 높음.

함수 작성 후 자기 점검 한 마디:

"이 변환/분기 정말 필요한가? 자료구조나 호출 결과를 그대로 넘기면 사라지지 않나?"


6. 다중 정렬 (sort_students)

(이름, 점수) 튜플 리스트를 점수 내림차순, 동점이면 이름 오름차순으로 정렬.

def sort_students(students):
    return sorted(students, key=lambda x: (-x[1], x[0]))

튜플 key의 동작 원리

key=lambda x: (-x[1], x[0])
        ┌─────┐  ┌────┐
        점수    이름
        내림    오름
  1. 첫 원소 -x[1]로 먼저 비교 → 음수로 만들어서 큰 점수가 앞에
  2. 첫 원소가 같으면 둘째 원소 x[0](이름)으로 오름차순 비교
  3. 튜플은 lexicographic 비교라 자동으로 1차 → 2차 정렬이 작동

→ 음수 트릭으로 내림차순 + 두 번째 키로 동률 처리. COS Pro 빈출 1순위 패턴.


7. 함수 (기본값) + 예외 처리 (safe_get)

nums[idx]를 안전하게 반환. 범위를 벗어나면 default 반환.

def safe_get(nums, idx, default=None):
    try:
        return nums[idx]
    except IndexError:
        return default

수업 중 질문: try/except 구문이 바로 안 떠올라서 옛날 자료를 찾아봤다. 나도 모르게 try-catch로 생각하고 있더라.

Java 출신이라 자연스러운 슬립이고, 7차 학습 이후 한 달 반 만의 자연 노출이라 더 그렇다. 로직은 완전히 손에 있는데 문법 키워드만 슬쩍 미끄러진 상태. 다시 굳히는 차원에서 매핑 정리.

Java → Python try/except 매핑

try:
    risky()
except IndexError as e:    # catch → except, (X) → as
    handle(e)
except (KeyError, ValueError):   # 여러 개 잡기 (튜플)
    handle()
else:                       # try가 예외 없이 끝났을 때만 (Java엔 없음)
    print("성공")
finally:
    cleanup()
JavaPython메모
trytry동일
catch (Exception e)except Exception as e키워드/문법 다름
finallyfinally동일
❌ 없음elsetry가 성공했을 때만 실행 (보너스 절)

시험 함정 포인트:

  • return이 try에 있어도 finally는 반드시 실행된다
  • elsetry가 예외 없이 끝났을 때만, except보다 먼저 위치

음수 인덱스가 자동 처리되는 점

nums[-1]은 마지막 원소를 정상 반환하고, 범위를 벗어난 음수(nums[-10] for len=3)에서만 IndexError를 던진다. C/Java라면 음수 인덱스 자체에서 별도 분기를 짜야 했을 텐데 Python은 한 줄에 처리됨.


8. 스택 — 다종 괄호 매칭 (is_balanced)

( ) [ ] { } 6가지 괄호의 짝과 순서를 모두 검사.

def is_balanced(s):
    pairs = {")": "(", "]": "[", "}": "{"}
    stack = []
    for c in s:
        if c in pairs.values():
            stack.append(c)
        elif c in pairs:
            if not stack or stack[-1] != pairs[c]:
                return False
            stack.pop()
    return not stack

정착 약점 두 개가 자연스럽게 등장

19차에서 학습한 두 가지가 다시 자연 재노출에서 안정적으로 나왔다.

  • 무엇 저장: 여는 괄호를 스택에 쌓는다
  • 빈 스택 체크: not stack 단락평가로 stack[-1] 호출을 보호

3가지 False 조건도 모두 처리:
1. 빈 스택에서 닫는 괄호 등장 (not stack)
2. 짝이 안 맞음 (stack[-1] != pairs[c])
3. 끝났는데 스택에 남은 여는 괄호 (return not stack)

마이크로 최적화 (시험엔 영향 없음)

c in pairs.values()는 매번 values 순회라 O(n)이고, c in pairs는 dict 해시라 O(1). 문자열 상수로 꺼내두면 더 빠르고 시험 답안 같은 느낌:

opens = "([{"
pairs = {")": "(", "]": "[", "}": "{"}
for c in s:
    if c in opens:
        stack.append(c)
    elif c in pairs:
        if not stack or stack[-1] != pairs[c]:
            return False
        stack.pop()

정렬된 리스트에서 target 인덱스 반환. 없으면 -1.

def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

디테일 4가지 — 시험 빈출 함정 포인트

  • while left <= right (등호 포함, 원소 1개 케이스 처리)
  • (left + right) // 2 (정수 나눗셈, /면 float이 되어 인덱싱 에러)
  • mid + 1 / mid - 1 (무한루프 방지)
  • 빈 리스트 자동 처리 (right = -1이라 루프 안 들어감)

/ vs // 디버깅 함정은 22차에서 직접 잡아본 문제. 그 경험이 자산이 됐다.


10. 수학 — 소수 판별 √n 최적화 (is_prime)

def is_prime(n):
    if n <= 1:
        return False
    i = 2
    while i * i <= n:
        if n % i == 0:
            return False
        i += 1
    return True

수업 중 질문: range(2, int(n**0.5) + 1) 이 형태는 뭐지?

while 형태와 완전히 같은 범위를 도는 for 형태다. 메커니즘만 다르다.

두 형태 비교

# 정수 연산만 사용 (안전)
i = 2
while i * i <= n:
    ...
    i += 1

# 동등한 for 형태 (sqrt 직접 계산)
for i in range(2, int(n**0.5) + 1):
    ...

조각조각 뜯어보면 (n=25 기준):

표현의미
n ** 0.55.0√n 계산 (float 반환)
int(...)5소수점 버림 (양수에선 floor)
+ 16range 끝값은 미포함이라 +1
range(2, 6)2, 3, 4, 5i가 2~5까지 순회

어느 쪽이 좋은가

형태장점단점
while i*i <= n정수 연산만 사용 (안전)for문 한 줄로 못 끝냄
range(2, int(n**0.5)+1)for문 한 줄로 깔끔float 거치므로 아주 큰 수에서 정밀도 함정 가능

→ COS Pro 입력 크기에서는 둘 다 정답. 빈칸으로 어느 쪽이 나와도 헷갈리지 않게 둘 다 알아둔다.


11. 구현 — 90도 시계방향 회전 (rotate)

N×N 행렬을 시계방향으로 90도 돌린 새 리스트를 반환.

def rotate(arr):
    n = len(arr)
    rotated = [[0] * n for _ in range(n)]
    for i in range(n):
        for j in range(n):
            rotated[j][n - 1 - i] = arr[i][j]
    return rotated

두 가지 매핑 스타일 (둘 다 정답)

rotated[j][n-1-i] = arr[i][j]   # 원본 → 회전 매핑 (이번 풀이)
rotated[i][j] = arr[n-1-j][i]   # 회전 → 원본 매핑 (4/28 학습)

루프를 도는 관점이 다를 뿐, 결과는 동일.

수업 중 질문: 이건 시계방향이지? 만약 반대로(반시계) 돌린다면?

회전 4종 매핑 정리

방향원본 → 회전 매핑회전 → 원본 매핑zip 한 줄형
CW (시계)rotated[j][n-1-i] = arr[i][j]rotated[i][j] = arr[n-1-j][i]zip(*arr[::-1])
CCW (반시계)rotated[n-1-j][i] = arr[i][j]rotated[i][j] = arr[j][n-1-i]list(zip(*arr))[::-1]

시각적 기억법 — 외우는 것보다 안 까먹음

    원본            CW (시계)       CCW (반시계)
   1 2 3            7 4 1           3 6 9
   4 5 6     →      8 5 2           2 5 8
   7 8 9            9 6 3           1 4 7
  • CW: 첫 행(1,2,3)이 → 마지막 열로 (왼쪽 위 → 오른쪽 위)
  • CCW: 첫 행(1,2,3)이 → 첫 열을 거꾸로 (왼쪽 위 → 왼쪽 아래)

보너스 — 회전 패밀리 4종

  • 시계 90도: arr[n-1-j][i]
  • 반시계 90도: arr[j][n-1-i]
  • 180도: rotated[i][j] = arr[n-1-i][n-1-j] (양쪽 다 뒤집힘)
  • 전치(transpose, 대각선 대칭): rotated[i][j] = arr[j][i] 또는 zip(*arr)

CW 두 번 = 180도 = CCW 두 번. 회전 패밀리는 이 4가지가 전부다.


오늘의 결과

11문제 1차 정답률 11/11 (100%) — 약 10일 공백 후 컨디션 회복용 워밍업 완료. 자연 노출 약점 1건(불필요한 list() 변환)만 발생, 의식 후 다음 노출 정착 판정 예정. 90분 타이머 모의고사 진입 가능 신호 확인 — 다음 세션은 구름EDU 기출 4회차 풀타임 풀이.

profile
문서화를 좋아하는 개발자

0개의 댓글