아주 좋은 질문이야!
실기 시험에서도 언어별 연산자의 "같은 기호, 다른 의미" 때문에 헷갈리기 정말 쉬워.
아래에 자바(Java), C언어(C), 파이썬(Python) 세 언어에서 대표적인 연산자들이 어떻게 다른 의미로 쓰이는지 정리해줄게.
/ 나눗셈 연산자| 언어 | 연산자 | 의미 | 예시 | 결과 |
|---|---|---|---|---|
| Python | / | 실수 나눗셈 (항상 float 반환) | 5 / 2 | 2.5 |
| Java | / | 정수끼리 나누면 정수 (소수점 버림) | 5 / 2 | 2 |
| C | / | 정수끼리 나누면 정수 (소수점 버림) | 5 / 2 | 2 |
🧠 정리:
float)int면 결과도 int (소수점 버림)// 연산자| 언어 | 연산자 | 의미 | 예시 | 결과 |
|---|---|---|---|---|
| Python | // | 정수 나눗셈 (몫) | 5 // 2 | 2 |
| Java | 없음 | 없음 (쓰면 문법 오류) | - | - |
| C | 없음 | 없음 | - | - |
🧠 정리:
//는 파이썬 전용이야.5 / 2가 이미 정수 나눗셈이므로 따로 // 연산자는 필요 없음.% 나머지 연산자| 언어 | 연산자 | 의미 | 예시 | 결과 |
|---|---|---|---|---|
| Python | % | 나머지 | 5 % 2 | 1 |
| Java | % | 나머지 | 5 % 2 | 1 |
| C | % | 나머지 | 5 % 2 | 1 |
🧠 정리:
%는 나머지 연산자로 동일하게 동작함.** 거듭제곱 연산자| 언어 | 연산자 | 의미 | 예시 | 결과 |
|---|---|---|---|---|
| Python | ** | 거듭제곱 | 2 ** 3 | 8 |
| Java | 없음 (Math.pow() 사용) | Math.pow(2, 3) | 8.0 | |
| C | 없음 (pow() 함수 사용) | pow(2, 3) | 8.0 |
🧠 정리:
**는 파이썬 전용.== vs is 비교 연산자| 언어 | 연산자 | 의미 | 주의점 |
|---|---|---|---|
| Python | == | 값 비교 | [] == [] ➝ True |
is | 객체 동일성 비교 | [] is [] ➝ False (다른 객체) | |
| Java | == | 기본 타입 값 비교 or 참조 주소 비교 | 문자열 비교는 equals() 써야 정확 |
| C | == | 값 비교 | 포인터 비교는 주소 비교임 |
🧠 정리:
is는 자바에서 ==와 같고,==는 자바의 equals()와 비슷함.| 연산자 | Python | Java/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)**와 함수 인자 전달 방식을 시험하는 트랩 문제야.
우선 정답부터 말하면:
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는 원본 리스트의 참조를 전달받는다.
lst는 a와 같은 객체를 바라보고 있음. 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부분이야:
count_map 만들기 (defaultdict + 슬라이싱)backup에서 [:]와 내부 리스트 수정 (얕은 복사)Counter로 빈도수 세기 (Counter + 중첩 반복문)students = [
("Kim", 90),
("Lee", 85),
("Kim", 95),
("Lee", 80),
("Park", 88),
]
"Ki", "Le", "Pa")process(data)from collections import defaultdict, Counter
def process(data):
data는 students를 전달받음defaultdict와 Counter를 사용 count_map = defaultdict(list)
"Ki" → [90, 95] for name, score in data:
count_map[name[:2]].append(score)
"Ki", "Le", "Pa"count_map = {
'Ki': [90, 95],
'Le': [85, 80],
'Pa': [88]
}
sorted_data = sorted(data, key=lambda x: (x[1], x[0]))
sorted_data = [
('Lee', 80),
('Lee', 85),
('Park', 88),
('Kim', 90),
('Kim', 95)
]
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]
]
freq = Counter()
for group in backup:
for v in group:
freq[v] += 1
[90, 95, -999, 85, 80, 88]
freq = Counter({
90: 1,
95: 1,
-999: 1,
85: 1,
80: 1,
88: 1
})
result = []
for key in sorted(count_map.keys()):
total = sum(count_map.get(key, []))
result.append((key, total))
"Ki", "Le", "Pa""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() | x는 None |
원본을 보존하고 싶을 때 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)
count_map = defaultdict(list)count_map은 키가 없을 경우 자동으로 빈 리스트를 생성해주는 딕셔너리야count_map = {
'Ki': [90, 95],
'Le': [85, 80],
'Pa': [88]
}
각 value는 각각의 리스트 객체를 가리키고 있어
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']는 동일한 리스트 객체를 가리킴
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] 이런 식으로 왜 -를 붙이는지에 대한 부분이야. 이것도 정보처리기사 실기나 실무 코딩 문제에서 자주 트랩으로 나오는 부분이라 꼭 알고 넘어가야 해.
sorted(data, key=lambda x: x[1])
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)] ← 오름차순파이썬에서는 reverse=True 옵션으로 내림차순 정렬을 할 수도 있고,
또는 람다 함수 내부에서 음수를 붙여서 강제로 내림차순을 구현할 수도 있어.
reverse=True 사용sorted(data, key=lambda x: x[1], reverse=True)
결과: [("A", 3), ("C", 2), ("B", 1)] ← 내림차순
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] → 작은 게 먼저 오니까 큰 값이 먼저 나옴data = [("Kim", 90), ("Lee", 85), ("Kim", 95), ("Lee", 80)]
sorted(data, key=lambda x: (x[1], x[0]))
의미:
x[1] → 점수 오름차순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)]
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] | 역순 정렬 |