학습 목표
이번 주 목표는 자료구조 4개를 모두 외우는 것이 아니라, 먼저 아래 4가지를 분명히 구분하는 것입니다.
list는 순서 있는 여러 값을 번호(index)로 읽는다.tuple은 list와 비슷하지만 만든 뒤 값을 바꾸지 않는다.dict는 번호가 아니라 key 이름으로 값을 찾는다.set은 중복 제거와 포함 여부 확인에 쓴다.append,pop,get,add처럼 지금 꼭 필요한 기본 동작을 사용할 수 있다.for반복문으로list/tuple/dict를 순회하며 값을 읽고 간단한 누적·출력을 할 수 있다.- 문제를 보고 “이건 list / tuple / dict / set 중 무엇을 써야 하는가?”를 설명할 수 있다.
1) 왜 자료구조가 필요한가?
자료구조는 데이터를 정리해서 담는 방식입니다.
flowchart TB A[같은 데이터] --> B[어떻게 담는가] B --> C[읽기 쉬운가] B --> D[수정이 쉬운가] B --> E[버그가 줄어드는가]
예를 들어 학생 이름 100명을 다룬다고 해 봅시다.
- 순서대로 출력하려면
list - 학번으로 바로 찾으려면
dict - 중복 이름을 제거하려면
set
즉, 데이터는 같아도 담는 방식이 다르면 코드가 달라집니다.
2) 값을 꺼내는 방식부터 구분하기
처음 배우는 학생이 가장 먼저 구분해야 하는 것은 “이 구조에서 값을 어떻게 읽는가” 입니다.
| 구조 | 값을 읽는 방식 | 예시 | 이 구조가 답하는 질문 |
|---|---|---|---|
list | index(번호) | scores[0] | “첫 번째 값은?” |
tuple | index(번호) | point[1] | “두 번째 값은?” |
dict | key(이름표) | sensor["temp"] | “temp 값은?” |
set | 포함 여부 | "ok" in tags | “이 값이 들어 있나?” |
3) list — 가장 먼저 익힐 구조
list는 순서가 있는 여러 값을 담는 가장 기본적인 자료구조입니다.
list 만들기와 읽기
values = [12.4, 11.8, 12.9, 13.1, 12.2]print(values[0]) # 첫 번째 값print(values[-1]) # 마지막 값| 인덱스 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 값 | 12.4 | 11.8 | 12.9 | 13.1 | 12.2 |
| 음수 인덱스 | -5 | -4 | -3 | -2 | -1 |
values[0]→ 첫 번째 값values[-1]→ 마지막 값
슬라이싱으로 여러 값 한 번에 보기
values = [12.4, 11.8, 12.9, 13.1, 12.2]print(values[1:4])출력:
[11.8, 12.9, 13.1]슬라이싱 규칙은 다음 한 줄로 기억하면 됩니다.
start <= index < end
즉, values[1:4]는 1번부터 시작해서 4번 직전까지 가져옵니다.
list는 수정할 수 있다
values = [12.4, 11.8, 12.9]values[1] = 12.0print(values) # [12.4, 12.0, 12.9]list에서 지금 꼭 익힐 동작
| 동작 | 코드 | 의미 |
|---|---|---|
| 읽기 | values[0] | 번호로 값 읽기 |
| 구간 읽기 | values[1:3] | 여러 값 한 번에 가져오기 |
| 수정 | values[1] = 12.0 | 기존 값 바꾸기 |
| 맨 끝 추가 | values.append(14.0) | 값 1개 추가 |
| 맨 끝 꺼내기 | values.pop() | 마지막 값을 꺼내며 삭제 |
| 길이 확인 | len(values) | 값이 몇 개인지 확인 |
values = [12.4, 11.8, 12.9]values.append(14.0)last = values.pop()print(values) # [12.4, 11.8, 12.9]print(last) # 14.04) tuple — list와 비슷하지만 바꾸지 않는 묶음
tuple은 순서가 있는 값 묶음이지만, 만든 뒤에는 값을 바꾸지 않습니다.
tuple은 언제 쓰나?
- 좌표
(x, y) - 날짜
(2026, 4, 9) - RGB 색
(255, 0, 0) - “읽기만 하고 수정하지 않을 값 묶음”
point = (3, 5)print(point[0])print(point[1])# point[0] = 7 # TypeErrorlist와 tuple의 차이
| 구조 | 읽기 | 수정 | 어울리는 상황 |
|---|---|---|---|
list | 가능 | 가능 | 계속 바뀌는 데이터 |
tuple | 가능 | 불가 | 고정된 값 묶음 |
tuple에서 기억할 점
- 읽는 방법은 list와 같다. →
point[0] - 하지만 값 변경은 안 된다.
- 처음 배우는 단계에서는 “읽기 전용 list 비슷한 것” 으로 이해해도 충분하다.
5) dict — 이름표(key)로 찾는 구조
dict는 값을 번호로 찾지 않고, key 이름으로 찾는 자료구조입니다.
sensor = {"id": "A01", "temp": 23.4, "status": "ok"}print(sensor["id"])print(sensor["temp"])dict 기본 읽기 / 추가 / 수정
sensor = {"id": "A01", "temp": 23.4}
print(sensor["id"]) # 읽기sensor["status"] = "ok" # 새 key 추가sensor["temp"] = 24.0 # 기존 key 값 수정print(sensor)없는 key는 get()으로 안전하게 읽기
sensor = {"id": "A01", "temp": 23.4}print(sensor.get("owner", "미등록"))key가 있는지 먼저 확인하기
sensor = {"id": "A01", "temp": 23.4}
if "temp" in sensor: print(sensor["temp"])dict에서 지금 꼭 익힐 동작
| 동작 | 코드 | 의미 |
|---|---|---|
| key로 읽기 | sensor["temp"] | key 이름으로 값 읽기 |
| 추가/수정 | sensor["status"] = "ok" | key:value 넣기 |
| 안전 조회 | sensor.get("owner", "미등록") | 없으면 기본값 반환 |
| 존재 확인 | "temp" in sensor | 이 key가 있는지 확인 |
| 길이 확인 | len(sensor) | key가 몇 개인지 확인 |
6) set — 중복 제거와 포함 여부 확인
set은 중복 없는 값 모음입니다.
tags = {"ok", "warn"}tags.add("offline")print("ok" in tags)중복 제거에 가장 자주 쓴다
raw_tags = ["ok", "warn", "ok", "ok", "offline"]unique_tags = set(raw_tags)print(unique_tags)print(sorted(unique_tags))set(raw_tags)→ 중복 제거"ok" in unique_tags→ 포함 여부 확인sorted(unique_tags)→ 보기 좋게 정렬해서 출력
set에서 지금 꼭 익힐 동작
| 동작 | 코드 | 의미 |
|---|---|---|
| 생성 | set(["ok", "ok", "warn"]) | 중복 제거 |
| 추가 | tags.add("offline") | 값 1개 추가 |
| 포함 검사 | "ok" in tags | 값이 있는지 확인 |
| 정렬 출력 | sorted(tags) | 순서 있게 출력 |
| 길이 확인 | len(tags) | 고유 값 개수 확인 |
7) 자료구조를 반복문과 함께 읽기
4주차의 for문은 range()와 함께 썼습니다.
이번 주부터는 숫자 범위 대신 자료구조 자체를 반복할 수 있어야 합니다.
| 반복 대상 | 기본 형태 | 반복마다 꺼내는 값 |
|---|---|---|
list | for value in values: | 각 원소 |
tuple | for value in point: | 각 원소 |
dict | for key in sensor: | 각 key |
dict.items() | for key, value in sensor.items(): | (key, value) 쌍 |
A. list 반복: 값을 하나씩 읽어 평균 준비하기
temperatures = [23.1, 22.8, 24.0, 23.7]total = 0
for temp in temperatures: print("current:", temp) total += temp
average = total / len(temperatures)print("average:", average)temp에는 list의 원소가 하나씩 순서대로 들어갑니다.range(len(temperatures))를 쓰지 않아도, 값 자체를 바로 꺼낼 수 있습니다.- 이런 방식이 읽기 전용 순회에서는 더 자연스럽고 실수가 적습니다.
B. tuple 반복: 고정된 값 묶음을 차례대로 읽기
point = (3, 5)
for value in point: print("coord:", value)- tuple도 list처럼
for문으로 순회할 수 있습니다. - 단, tuple은 읽기만 하고 수정하지 않습니다.
C. dict 반복: key만 돌거나, key와 value를 함께 돌기
sensor = {"A01": "OK", "A02": "WARN", "A03": "OK"}
for sensor_id in sensor: print(sensor_id, sensor[sensor_id])for sensor_id in sensor:는 key를 하나씩 꺼냅니다.- value가 필요하면
sensor[sensor_id]로 다시 읽습니다.
sensor = {"A01": "OK", "A02": "WARN", "A03": "OK"}
for sensor_id, status in sensor.items(): print(sensor_id, status)items()는(key, value)쌍을 꺼냅니다.- 그래서 출력·집계·검사에서는
items()가 더 편한 경우가 많습니다.
8) 한눈에 비교하기
이제 4개 구조를 다시 비교해 보면 더 이해가 쉽습니다.
| 구조 | 순서 | 수정 가능 | 값을 읽는 방식 | 가장 먼저 떠올릴 사용처 |
|---|---|---|---|---|
list | 있음 | 가능 | index | 여러 값을 순서대로 저장 |
tuple | 있음 | 불가 | index | 고정된 값 묶음 |
dict | 있음* | 가능 | key | 이름으로 값 찾기 |
set | index 순서 없음 | 가능 | 포함 여부 | 중복 제거 |
* Python 3.7+에서는 삽입 순서를 유지합니다.
9) 한 번 더 보면 좋은 method 참고표
아래는 지금 당장 외우기보다, 필요할 때 찾아보는 2차 참고용입니다.
list 참고 method
| method | 의미 | 예시 |
|---|---|---|
insert(i, x) | 원하는 위치에 삽입 | nums.insert(1, 15) |
extend(seq) | 여러 값 이어 붙이기 | nums.extend([50, 60]) |
remove(x) | 값이 같은 첫 원소 삭제 | nums.remove(20) |
sort() | 리스트 자체 정렬 | nums.sort() |
count(x) | 값 개수 세기 | nums.count(10) |
index(x) | 값의 위치 찾기 | nums.index(30) |
dict 참고 method
| method | 의미 | 예시 |
|---|---|---|
keys() | key들 보기 | sensor.keys() |
values() | value들 보기 | sensor.values() |
items() | (key, value) 쌍 보기 | sensor.items() |
update(other) | 여러 key를 한꺼번에 반영 | sensor.update({"status": "ok"}) |
pop(key) | key를 꺼내며 삭제 | sensor.pop("temp") |
set 참고 method
| method | 의미 | 예시 |
|---|---|---|
update(seq) | 여러 값 추가 | tags.update(["ok", "hold"]) |
remove(x) | 값 삭제 (없으면 에러) | tags.remove("warn") |
discard(x) | 값 삭제 (없어도 조용히 통과) | tags.discard("warn") |
10) 잘못된 예와 올바른 예
A. dict 없는 key를 바로 읽기
Bad
lookup = {"A01": 23.4}print(lookup["A03"]) # KeyErrorGood
print(lookup.get("A03", "missing"))
if "A03" in lookup: print(lookup["A03"])else: print("A03 없음")B. list 복사 착각하기
Bad
a = [1, 2, 3]b = ab[0] = 99print(a) # [99, 2, 3]flowchart LR
subgraph "b = a (참조 공유)"
direction TB
varA1["변수 a"] --> list1["[1, 2, 3]"]
varB1["변수 b"] --> list1
end
subgraph "b = a.copy() (독립 복사)"
direction TB
varA2["변수 a"] --> list2["[1, 2, 3]"]
varB2["변수 b"] --> list3["[1, 2, 3]"]
end Good
a = [1, 2, 3]b = a.copy()b[0] = 99print(a) # [1, 2, 3]11) 자료구조 디버깅 습관
- 자료형부터 확인:
print(type(x), x) - 길이 확인:
print(len(x)) - dict는 key 읽기 전
in또는get()확인 - set은
sorted()로 출력해 보기 - list 복사는
a.copy()로 했는지 확인
예제 1
예제 1 줄별 해설
- list를 만들고 첫 값과 마지막 값을 읽습니다.
- 슬라이싱으로 여러 값을 한 번에 가져옵니다.
- list는 값을 수정하고
append()로 새 값을 추가할 수 있습니다. - tuple은 index로 읽을 수 있지만 수정은 하지 않습니다.
예제 2
예제 2 줄별 해설
sensor는 key와 value로 이루어진 dict입니다.sensor["id"],sensor["temp"]처럼 key 이름으로 값을 읽습니다.sensor["status"] = "warn"은 기존 값 수정입니다.sensor["location"] = "lab-1"은 새 key 추가입니다.get()은 없는 key를 안전하게 읽을 때 씁니다."temp" in sensor는 key 존재 여부를 확인합니다.
예제 3
예제 3 줄별 해설
- 원본은 중복이 있는 list입니다.
set(tags)로 중복을 제거합니다.- set은 순서가 일정하지 않을 수 있으므로
sorted()로 출력합니다. add()로 값을 하나 추가할 수 있습니다.in으로 포함 여부를 확인할 수 있습니다.
예제 4
예제 4 줄별 해설
for temp in temperatures:는 list 원소를 순서대로 꺼냅니다.total += temp로 누적하면 평균 계산 준비가 됩니다.- dict는
items()를 사용하면 key와 value를 함께 꺼낼 수 있습니다. - 이 패턴은 다음 주 파싱 결과를 dict/list로 다룰 때 바로 이어집니다.
자주 나오는 실수 요약표
| 실수 | 증상 | 이유 | 해결 |
|---|---|---|---|
| tuple을 list처럼 수정 | TypeError | tuple은 수정 불가 | 수정이 필요하면 list 사용 |
| 슬라이싱 끝값 포함으로 착각 | 결과가 한 칸 다름 | 끝 index는 포함되지 않음 | start <= i < end 기억 |
| dict 없는 key 즉시 조회 | KeyError | 방어 코드 없음 | get() 또는 if key in dict |
| set에 순서 기대 | 출력 순서가 달라 보임 | set은 순서형 아님 | sorted(set_data) 사용 |
b = a를 복사로 착각 | 원본까지 바뀜 | 같은 list를 가리킴 | a.copy() 사용 |
실습 문제 · 직접 코딩
문제: 점수 5개가 들어 있는 list에서 최고점, 최저점, 평균을 계산해 출력하세요.
해야 할 일
highest,lowest,average변수를 만드세요.- 세 값을 모두 출력하세요.
힌트
max(scores),min(scores),sum(scores),len(scores)를 사용할 수 있습니다.- 평균은
sum(scores) / len(scores)입니다.
문제: 좌표를 나타내는 tuple 3개가 주어졌을 때, 각 tuple의 x값(첫 번째 값)만 한 줄씩 출력하세요.
해야 할 일
point1,point2,point3의 x값을 각각 출력하세요.
힌트
- tuple의 첫 번째 값은 index
0입니다. - 예:
point1[0]
문제: 장비 상태를 저장한 dict에서 특정 장비의 상태를 읽고, 없는 key는 안전하게 처리하세요.
해야 할 일
A02의 상태를 출력하세요.A04의 상태를 읽되, 없으면"미등록"을 출력하세요.A03key가 dict 안에 있는지도 출력하세요.
힌트
- 있는 key는
sensor_status["A02"]처럼 읽을 수 있습니다. - 없는 key는
get()을 쓰는 편이 안전합니다. - 존재 여부는
"A03" in sensor_status로 검사합니다.
문제: 중복된 장비 ID 목록에서 고유한 ID만 남기고, 정렬된 결과와 개수를 출력하세요.
해야 할 일
sensor_ids를 set으로 바꿔 중복을 제거하세요.- 정렬된 고유 ID 목록을 출력하세요.
- 고유 ID 개수도 출력하세요.
힌트
unique_ids = set(sensor_ids)sorted(unique_ids)len(unique_ids)
문제: 장비 상태 dict를 반복문으로 순회해 WARN 상태 장비만 출력하세요.
해야 할 일
- 반복문으로 key와 value를 하나씩 읽으세요.
- 상태가
"WARN"인 장비 ID만 출력하세요. - 마지막에
WARN장비 수를 함께 출력하세요.
힌트
for sensor_id, status in sensor_status.items():- 조건문은
if status == "WARN": - 개수는
count += 1로 누적할 수 있습니다.
문제: 단어 list에서 각 단어가 몇 번 나왔는지 dict로 세어 보세요.
해야 할 일
- 반복문을 사용해
countdict에 단어별 등장 횟수를 저장하세요. count전체를 출력하세요.radar와noise의 횟수를 각각 출력하세요.
힌트
- 단어가 이미 있으면 1 증가시키고, 없으면 1로 시작하면 됩니다.
- 예:
if word in count:
C와 비교: 배열/struct와 Python 자료구조는 이렇게 다릅니다
| 항목 | C | Python | 기억할 점 |
|---|---|---|---|
| 순차 데이터 | 배열 int a[3] | list / tuple | Python은 크기와 타입이 더 유연하다 |
| 레코드 데이터 | struct | dict | Python은 key 문자열로 바로 접근 |
| 중복 제거 | 직접 구현 | set | Python은 집합이 기본 제공 |
| 수정 가능성 | 선언 방식에 따라 다름 | list/dict/set 수정 가능, tuple 불가 | 선택이 문법에 드러난다 |
struct Sensor { char id[10]; double temp;};
struct Sensor s = {"A01", 23.4};printf("%s %.1f\n", s.id, s.temp);sensor = {"id": "A01", "temp": 23.4}print(sensor["id"], sensor["temp"])