연습 18

류한선·2025년 7월 6일

실기연습-2

목록 보기
94/95

아주 좋은 질문이야!
실기 시험에서도 언어별 연산자의 "같은 기호, 다른 의미" 때문에 헷갈리기 정말 쉬워.
아래에 자바(Java), C언어(C), 파이썬(Python) 세 언어에서 대표적인 연산자들이 어떻게 다른 의미로 쓰이는지 정리해줄게.


✅ 1. / 나눗셈 연산자

언어연산자의미예시결과
Python/실수 나눗셈 (항상 float 반환)5 / 22.5
Java/정수끼리 나누면 정수 (소수점 버림)5 / 22
C/정수끼리 나누면 정수 (소수점 버림)5 / 22

🧠 정리:

  • Python은 항상 실수 결과 (float)
  • Java, C는 피연산자가 int면 결과도 int (소수점 버림)

✅ 2. // 연산자

언어연산자의미예시결과
Python//정수 나눗셈 (몫)5 // 22
Java없음없음 (쓰면 문법 오류)--
C없음없음--

🧠 정리:

  • //파이썬 전용이야.
  • 자바나 C에선 5 / 2가 이미 정수 나눗셈이므로 따로 // 연산자는 필요 없음.

✅ 3. % 나머지 연산자

언어연산자의미예시결과
Python%나머지5 % 21
Java%나머지5 % 21
C%나머지5 % 21

🧠 정리:

  • 세 언어 모두 %나머지 연산자로 동일하게 동작함.

✅ 4. ** 거듭제곱 연산자

언어연산자의미예시결과
Python**거듭제곱2 ** 38
Java없음 (Math.pow() 사용)Math.pow(2, 3)8.0
C없음 (pow() 함수 사용)pow(2, 3)8.0

🧠 정리:

  • **는 파이썬 전용.
  • 자바/C에서는 함수를 써야 한다.

✅ 5. == vs is 비교 연산자

언어연산자의미주의점
Python==값 비교[] == [] ➝ True
is객체 동일성 비교[] is [] ➝ False (다른 객체)
Java==기본 타입 값 비교 or 참조 주소 비교문자열 비교는 equals() 써야 정확
C==값 비교포인터 비교는 주소 비교임

🧠 정리:

  • Python의 is는 자바에서 ==와 같고,
  • Python의 ==는 자바의 equals()와 비슷함.

✅ 마무리 요약표 (완전 핵심만!)

연산자PythonJava/C차이 요약
/실수 나눗셈정수끼리면 정수 결과⚠️ 주의
//정수 나눗셈없음Python만
%나머지나머지동일
**거듭제곱함수 사용Python만 연산자
==값 비교값 or 주소비슷하지만 주의
is객체 비교==(자바의 주소)다름

def modify_and_return(lst):
    lst2 = lst[:]
    lst2[0].append(99)
    return lst, lst2

a = [[1, 2], [3, 4]]
b, c = modify_and_return(a)
print("a =", a)
print("b =", b)
print("c =", c)

좋아! 이 문제는 **리스트의 얕은 복사(shallow copy)**와 함수 인자 전달 방식을 시험하는 트랩 문제야.
우선 정답부터 말하면:


✅ 정답: B

a = [[1, 2, 99], [3, 4]]  
b = [[1, 2, 99], [3, 4]]  
c = [[1, 2, 99], [3, 4]]

이제 한 줄씩 완전히 해부해서 설명할게.

각 줄에서:

  • 역할
  • 왜 그 위치에 있어야 하는지
  • 실행 순서에서 어떤 일이 일어나는지
  • 값 변화 예시와 시각화

다 다룰게.


🔍 코드 해부 시작

def modify_and_return(lst):
  • 🔧 역할: lst를 받아서 복사하고 일부 수정한 뒤 두 리스트를 리턴하는 함수.

  • ⛓️ 이 위치 이유: 함수 정의

  • 🧠 중요 포인트: lst원본 리스트의 참조를 전달받는다.

    • 즉, lsta와 같은 객체를 바라보고 있음.

    lst2 = lst[:]
  • 🔧 역할: 리스트 lst얕은 복사본lst2로 만든다.

  • 📦 얕은 복사란?

    • 바깥 리스트만 복사됨
    • 안쪽 리스트는 여전히 같은 객체를 참조
  • 🔍 예시:

    lst = [[1,2], [3,4]]
    lst2 = lst[:]

    이때 객체 구조는 이렇게 된다:

    lst      ──▶ [[1,2], [3,4]]
                 ↑      ↑
    lst2     ──▶ [│     │]
                 └──────┘ 같은 리스트를 가리킴 (얕은 복사)

    lst2[0].append(99)
  • 🔧 역할: lst2[0] 리스트에 99를 추가한다.

  • 📍 이게 왜 함정이냐면?

    • lst2[0]lst[0]은 같은 리스트를 바라보고 있음!
    • 따라서 lst2[0].append(99)lst[0]에도 영향을 준다.
  • ⚙️ 실제 실행 결과:

    lst  = [[1, 2, 99], [3, 4]]
    lst2 = [[1, 2, 99], [3, 4]]

    return lst, lst2
  • 🔧 역할: 변경된 두 리스트를 반환
  • 🧠 여기서 주의: lst, lst2는 바깥 리스트는 다르지만, 내부 요소는 공유하고 있음.

a = [[1, 2], [3, 4]]
  • 🔧 역할: 원본 리스트
  • 이 리스트는 함수 안으로 전달될 때 lst로 복사되는 것이 아니라 참조로 전달

b, c = modify_and_return(a)
  • 🔧 역할: 함수 실행 결과를 각각 b, c에 저장

  • 💡 b = lst, c = lst2 를 뜻함

  • 📍 결과:

    a = [[1, 2, 99], [3, 4]]
    b = [[1, 2, 99], [3, 4]]
    c = [[1, 2, 99], [3, 4]]

print("a =", a)
print("b =", b)
print("c =", c)
  • 출력 결과는:

    a = [[1, 2, 99], [3, 4]]
    b = [[1, 2, 99], [3, 4]]
    c = [[1, 2, 99], [3, 4]]
  • 왜냐면 모두 동일한 내부 리스트들을 공유하고 있기 때문이야


from collections import defaultdict, Counter

def process(data):
    count_map = defaultdict(list)
    for name, score in data:
        count_map[name[:2]].append(score)

    sorted_data = sorted(data, key=lambda x: (x[1], x[0]))
    
    backup = list(count_map.values())[:]
    backup[0].append(-999)

    freq = Counter()
    for group in backup:
        for v in group:
            freq[v] += 1

    result = []
    for key in sorted(count_map.keys()):
        total = sum(count_map.get(key, []))
        result.append((key, total))
    
    return result, sorted_data, freq

students = [
    ("Kim", 90),
    ("Lee", 85),
    ("Kim", 95),
    ("Lee", 80),
    ("Park", 88),
]

r1, r2, r3 = process(students)
print("Result 1:", r1)
print("Result 2:", r2)
print("Result 3:", r3.most_common(2))

좋아! 이 문제는 실기 시험에서 나올 수 있는 트랩을 5개 이상 섞어놓은 고급 문제야.
지금부터 한 줄도 빠짐없이 디버깅 형식으로 해설할게.


🔍 전체 흐름 요약

우리가 해석할 코드는 크게 3부분이야:

  1. count_map 만들기 (defaultdict + 슬라이싱)
  2. backup에서 [:]와 내부 리스트 수정 (얕은 복사)
  3. Counter로 빈도수 세기 (Counter + 중첩 반복문)

💣 준비: 데이터 확인

students = [
    ("Kim", 90),
    ("Lee", 85),
    ("Kim", 95),
    ("Lee", 80),
    ("Park", 88),
]
  • 총 5명
  • 이름 앞 2글자 기준으로 나눌 예정 ("Ki", "Le", "Pa")

🧩 함수 시작: process(data)

from collections import defaultdict, Counter

def process(data):
  • 함수 정의, data는 students를 전달받음
  • defaultdictCounter를 사용

✅ 1단계: 그룹화

    count_map = defaultdict(list)
  • 역할: 이름 앞 2글자로 그룹 나누기
  • 예: "Ki" → [90, 95]
    for name, score in data:
        count_map[name[:2]].append(score)
  • name[:2] → "Ki", "Le", "Pa"
  • 최종 결과:
count_map = {
    'Ki': [90, 95],
    'Le': [85, 80],
    'Pa': [88]
}

✅ 2단계: 정렬

    sorted_data = sorted(data, key=lambda x: (x[1], x[0]))
  • 의미: 점수 기준 오름차순, 동점일 땐 이름 기준
  • 정렬된 결과:
sorted_data = [
    ('Lee', 80),
    ('Lee', 85),
    ('Park', 88),
    ('Kim', 90),
    ('Kim', 95)
]

✅ 3단계: 리스트 복사 트랩 구간

    backup = list(count_map.values())[:]

🧠 지금 이 줄은 왜 트랩인가?

  • count_map.values()는 아래 리스트를 반환함:
[[90, 95], [85, 80], [88]]
  • [:]는 바깥 리스트만 복사하는 얕은 복사
  • 즉, backup[0], count_map['Ki']는 같은 리스트를 참조함

❗ 트랩 발생

    backup[0].append(-999)
  • backup[0][90, 95]
  • 여기 -999를 추가하면, 원본인 count_map['Ki']에도 영향
  • 최종 구조:
count_map = {
    'Ki': [90, 95, -999],
    'Le': [85, 80],
    'Pa': [88]
}
backup = [
    [90, 95, -999],
    [85, 80],
    [88]
]

✅ 4단계: Counter로 값 빈도 세기

    freq = Counter()
    for group in backup:
        for v in group:
            freq[v] += 1
  • 순서대로 값 하나씩 센다
  • backup 안에 있는 모든 점수:
[90, 95, -999, 85, 80, 88]
  • 빈도 결과:
freq = Counter({
    90: 1,
    95: 1,
    -999: 1,
    85: 1,
    80: 1,
    88: 1
})

✅ 5단계: 결과 1 - 각 그룹 합치기

    result = []
    for key in sorted(count_map.keys()):
        total = sum(count_map.get(key, []))
        result.append((key, total))
  • key 정렬: "Ki", "Le", "Pa"
  • sum 결과:
"Ki"90 + 95 + (-999) = **-814**
"Le"85 + 80 = 165  
"Pa"88
  • ❗ 여기서 마이너스 값이 나오는 이유는 -999가 원본에도 영향을 줬기 때문!

❗ 문제와 보기 차이 발견

우리 위에서 Result 1을 예상했는데 보기에는 185로 돼 있었지?

이건 말이 안 됨!

  • 만약 -999원본에 영향 없었으면 "Ki" 총점은 90 + 95 = 185
  • 그런데 지금은 -999가 들어가서 "Ki"-814

즉, 보기에 오류가 있어
→ 보기 A~D 전부 틀린 거야 ❗


✅ 실제 출력값 정리

Result 1: [('Ki', -814), ('Le', 165), ('Pa', 88)]
Result 2: [('Lee', 80), ('Lee', 85), ('Park', 88), ('Kim', 90), ('Kim', 95)]
Result 3: [(90, 1), (95, 1)]  # 가장 큰 값 2개

🔚 최종 요약 복습 (이 문제에서 배울 점)

항목트랩 내용
list(count_map.values())[:]얕은 복사! 내부 리스트는 공유
append(-999)원본 리스트에도 영향
defaultdict자동 생성되어 key 없을 때도 문제 안 남
sorted(data, key=lambda x: (x[1], x[0]))점수 오름차순, 이름 보조 정렬
Counter리스트 평탄화 없이 잘 순회됨

정확해! 너가 말한 내용은 아주 중요하고, 실기나 실무에서 헷갈리기 쉬운 sorted() vs sort() 차이를 핵심 잘 짚었어. 아래에 완전히 정리해줄게.


sorted() vs sort()의 차이

항목sorted()list.sort()
리턴값새 리스트 리턴리턴값 없음 (None)
원본 영향영향 없음원본을 직접 정렬
사용 가능 대상모든 iterable (tuple, str, dict.keys() 등)리스트 전용
체이닝 가능성O (새 리스트를 반환하니까)X (리턴값이 None)

📌 예제로 차이 보기

arr = [3, 1, 2]

sorted_arr = sorted(arr)   # 새로운 정렬된 리스트 반환
print("arr =", arr)        # ➜ [3, 1, 2] (원본 그대로)
print("sorted_arr =", sorted_arr)  # ➜ [1, 2, 3]

arr.sort()                 # 원본 리스트를 직접 정렬
print("arr =", arr)        # ➜ [1, 2, 3]

🔍 디버깅 형식 요약

arr = [3, 1, 2]
  • 원본 리스트 선언
sorted_arr = sorted(arr)
  • 새로운 리스트 반환
  • sorted_arr만 정렬된 상태이고, arr은 그대로임
arr.sort()
  • arr 자체를 바꾼다

  • arr.sort()리턴값이 없다 → None
    따라서 이런 코드는 오류남:

    x = arr.sort()  # x는 None

💡 실전 실수 포인트

실수 유형잘못된 코드문제
정렬된 리스트를 변수에 담고 싶을 때 sort() 사용x = arr.sort()xNone
원본을 보존하고 싶을 때 sort() 사용arr.sort()원본이 바뀜
리스트가 아닌 값에 sort() 사용(1,3,2).sort()AttributeError 발생

🎯 실기 문제에서 이런 식으로 출제 가능:

data = [("Kim", 90), ("Lee", 85), ("Kim", 95)]
sorted(data)
print(data)

👉 이때 data는 그대로 유지
sorted()는 복사본을 만들기 때문


바로 그거야 — 정확히 "얕은 복사(shallow copy)" 때문이야.
그럼 이제 왜 -999가 원본에도 영향을 미쳤는지를 진짜 디버깅하듯 메모리 흐름 + 참조 구조로 완전 해부해줄게.


🔍 핵심 코드 복습

count_map = defaultdict(list)
for name, score in data:
    count_map[name[:2]].append(score)

backup = list(count_map.values())[:]
backup[0].append(-999)

🧠 지금 무슨 일이 벌어진 걸까?

🔸 1단계: count_map = defaultdict(list)

  • count_map은 키가 없을 경우 자동으로 빈 리스트를 생성해주는 딕셔너리야
count_map = {
    'Ki': [90, 95],
    'Le': [85, 80],
    'Pa': [88]
}

value는 각각의 리스트 객체를 가리키고 있어


🔸 2단계: list(count_map.values())[:]

이게 핵심이야!

🔍 이 코드가 뭘 의미하냐면:

  • count_map.values() → 내부 리스트들 ([[90,95], [85,80], [88]])
  • [:] → 바깥 리스트만 복사! (즉, 리스트를 얕게 복사)

👉 내부 리스트는 원래 리스트와 동일한 객체를 참조


💣 그 다음 줄에서 터짐

backup[0].append(-999)
  • backup[0] == count_map['Ki']
  • append()리스트 객체 자체를 수정하는 함수이므로
    count_map['Ki']도 함께 바뀌는 거야!

🔬 시각화: 객체 참조 구조

❗ 복사 전 (원본 구조)

count_map ──▶ {
   'Ki':  ──▶ [90, 95]
   'Le':  ──▶ [85, 80]
   'Pa':  ──▶ [88]
}

❗ 복사 후 backup = list(count_map.values())[:]

backup ──▶ [
   [90, 95],     ←─── same object as count_map['Ki']
   [85, 80],     ←─── same object as count_map['Le']
   [88]          ←─── same object as count_map['Pa']
]

🔁 즉, backup[0]count_map['Ki']동일한 리스트 객체를 가리킴


✅ append(-999) 이후

  • backup[0].append(-999)
  • 이 시점에 하나의 리스트 객체가 다음처럼 변함:
[90, 95][90, 95, -999]
  • 그걸 참조 중인 count_map['Ki']도 같이 바뀜

✨ 요약: 왜 원본에 영향이 갔는가?

원인설명
얕은 복사[:]는 바깥 리스트만 복사하고, 내부 객체는 참조를 공유
append() 사용리스트 객체 자체를 바꾸므로 참조된 곳 모두 영향
리스트는 가변 객체파이썬 리스트는 변경 가능한(mutable) 객체

🧠 복사하려면 어떻게 했어야 할까?

import copy
backup = copy.deepcopy(list(count_map.values()))
  • 이렇게 해야 완전한 독립된 복사본을 만들어서 -999가 원본에 영향을 주지 않음

아주 좋아! 지금 너가 궁금해한 건 파이썬 람다 함수 + 정렬(sorted()/sort()), 그리고 오름차순/내림차순 기준을 어떻게 정하고, x[1], -x[1] 이런 식으로 왜 -를 붙이는지에 대한 부분이야. 이것도 정보처리기사 실기나 실무 코딩 문제에서 자주 트랩으로 나오는 부분이라 꼭 알고 넘어가야 해.


✅ 1. 람다 함수 정렬 기본 개념

sorted(data, key=lambda x: x[1])
  • 🔧 key 매개변수는 "어떤 기준으로 정렬할 것인지"를 정함
  • lambda x: x[1] ➜ 각 요소 x에서 두 번째 값을 기준으로 정렬한다는 뜻

📌 예시: 튜플 리스트 정렬

data = [("A", 3), ("B", 1), ("C", 2)]
sorted(data, key=lambda x: x[1])
  • 기준: x[1] → 즉 [3, 1, 2]
  • 결과: [("B", 1), ("C", 2), ("A", 3)]오름차순

✅ 2. 내림차순 정렬

파이썬에서는 reverse=True 옵션으로 내림차순 정렬을 할 수도 있고,
또는 람다 함수 내부에서 음수를 붙여서 강제로 내림차순을 구현할 수도 있어.


🧪 방법 1: reverse=True 사용

sorted(data, key=lambda x: x[1], reverse=True)

결과: [("A", 3), ("C", 2), ("B", 1)]내림차순


🧪 방법 2: lambda x: -x[1] 사용

sorted(data, key=lambda x: -x[1])

이것도 결과는 같음: [("A", 3), ("C", 2), ("B", 1)]

❗ 왜 -x[1]을 쓰면 내림차순이 되는 걸까?

  • 정렬 기준이 [3, 2, 1]-x[1]을 기준으로 하면 [-3, -2, -1] → 작은 게 먼저 오니까 큰 값이 먼저 나옴

✅ 3. 복합 기준 정렬

data = [("Kim", 90), ("Lee", 85), ("Kim", 95), ("Lee", 80)]
sorted(data, key=lambda x: (x[1], x[0]))
  • 의미:

    1. 첫 기준은 x[1] → 점수 오름차순
    2. 점수가 같으면 x[0] → 이름 오름차순
  • 결과:

[("Lee", 80), ("Lee", 85), ("Kim", 90), ("Kim", 95)]

🔁 내림차순 혼합 예시

sorted(data, key=lambda x: (-x[1], x[0]))
  • 의미:

    • 점수는 내림차순
    • 이름은 오름차순
  • 결과:

[("Kim", 95), ("Kim", 90), ("Lee", 85), ("Lee", 80)]

✅ 리스트 안에 리스트 (2차원 배열) 정렬 예시

arr = [[1, 3], [2, 1], [4, 2]]
sorted(arr, key=lambda x: x[1])
  • 기준: [3, 1, 2] → 정렬 후: [[2, 1], [4, 2], [1, 3]]

x[-1]처럼 음수 인덱스는 뭐야?

  • 파이썬은 음수 인덱스를 지원해
  • x[-1]맨 마지막 요소를 의미해
x = [10, 20, 30]
x[-1]   # ➜ 30
x[-2]   # ➜ 20

그래서 리스트가 [이름, 점수]라면, x[-1] == x[1]

✅ 보통 2차원 배열 정렬에서

arr.sort(key=lambda x: -x[-1])

은 "마지막 항목 기준으로 내림차순 정렬"을 의미해.


💡 실기에서 이런 문제로 나올 수 있어

arr = [["A", 10], ["B", 30], ["C", 20]]
arr.sort(key=lambda x: -x[1])
print(arr)

결과는?
[["B", 30], ["C", 20], ["A", 10]]


✅ 요약 정리표

정렬 기준코드 예시설명
오름차순key=lambda x: x[1]작은 값부터
내림차순key=lambda x: -x[1]큰 값부터
복합정렬key=lambda x: (x[1], x[0])1순위, 2순위
마지막 요소 기준key=lambda x: x[-1]리스트 끝값 기준
내림차 + 끝값 기준key=lambda x: -x[-1]역순 정렬

0개의 댓글