문자열·리스트·딕셔너리·집합 기초
1 / 12

문자열과 리스트 기초

여러 데이터를 모아서 보관하고 접근하기

인덱싱과 슬라이싱, 그리고 동적 데이터 구조 다루기

오늘의 수업 목표

  • 문자열(str)과 리스트(list)의 개념과 차이를 이해한다.
  • 인덱싱과 슬라이싱의 방 번호 규칙을 통해 원하는 구간을 추출한다.
  • 가변성(Mutable)과 불변성(Immutable)의 특성을 고려하여 코딩한다.
  • 주요 리스트 메서드를 사용하여 데이터를 조작한다.

데이터를 왜 묶어야 할까요?

게임 속 인벤토리의 10개 아이템을 10개의 변수로 만든다면?

  • 변수 개별 선언: item1, item2, item3... (비효율적)
  • 데이터 묶음 적용: items = ["칼", "방패", "포션"] (간결함)

하나로 묶인 데이터 그룹을 활용하면 대량의 데이터를 손쉽게 관리할 수 있습니다.

인덱싱: 특정 방 번호 가리키기

규칙: 음수 인덱스는 자동으로 + len(list)로 변환됩니다.
파이썬은 list[i]에서 i가 음수이면, 인덱싱 전에 자동으로 i = i + len(list)로 변환하여 처리합니다.

lst = [10, 20, 30, 40, 50]  # len(lst) == 5

lst[-1]   # 내부적으로: -1 + 5 = 4  ->  lst[4]  ->  50
lst[-2]   # 내부적으로: -2 + 5 = 3  ->  lst[3]  ->  40

nickname = "분당중앙고"
print(nickname[0])   # 분
print(nickname[-1])  # 고 (맨 뒤 글자)

음수 인덱스는 len(lst) - 1을 짧게 쓰는 문법 설탕(Syntactic Sugar)입니다.
시계 방향(양수)은 0, 1, 2… 순으로 전진하고, 반시계 방향(음수)은 -1, -2, -3… 순으로 역진하며 결국 같은 방을 가리킵니다.

양수 인덱스 원소 음수 인덱스
0 10 -5
1 20 -4
2 30 -3
3 -2
4 50 -1

슬라이싱: 구간 단위 추출

[시작 : 끝 : 간격]을 지정하여 새로운 리스트나 문자열을 잘라냅니다.

nickname = "분당중앙고"

# 1. 0번째부터 2번째 직전까지 (0, 1번방 추출)
print(nickname[0:2])   # 분당

# 2. 처음부터 3번째 직전까지
print(nickname[:3])    # 분당중

# 3. 간격을 조절하여 건너뛰며 추출
numbers = [10, 20, 30, 40, 50, 60]
print(numbers[::2])    # [10, 30, 50]

# 4. 전체를 거꾸로 뒤집기
print(nickname[::-1])  # 고앙중당분

주의: 지정한 끝 인덱스 번호 자체는 결과 범위에 포함되지 않습니다.

[::-1] 뒤집기 원리

간격이 음수이면 이동 방향이 오른쪽에서 왼쪽으로 바뀝니다.
시작/끝을 생략하면 자동으로 맨 끝에서 맨 앞까지 전체 범위가 설정됩니다.

nickname = "분당중앙고"
#  인덱스:   0    1    2    3    4
#  글자:    분   당   중   앙   고
#  간격 -1 -> 4(고) -> 3(앙) -> 2(중) -> 1(당) -> 0(분)

print(nickname[::-1])   # 고앙중당분

numbers = [1, 2, 3, 4, 5]
print(numbers[::-1])    # [5, 4, 3, 2, 1]
print(numbers[::-2])    # [5, 3, 1]  (뒤에서 두 칸씩)

간격의 부호가 방향을 결정합니다. 양수 -> 왼쪽에서 오른쪽, 음수 -> 오른쪽에서 왼쪽.

가변(Mutable) 리스트 vs 불변(Immutable) 문자열

리스트 — 같은 자리에서 값 수정

inventory = ["단검", "방패", "물약"]
inventory[0] = "장검"
print(inventory)  # ['장검', '방패', '물약']

메모리 주소는 유지되며 그 자리의 데이터만 교체됩니다.

문자열 — 새 메모리에 새 문자열 생성

player_class = "마법사"
# player_class[0] = "전"  # TypeError 발생!

# 슬라이싱으로 새 문자열 조립
player_class = "전" + player_class[1:]  # "전법사"

기존 메모리 값은 유지되고, 새로운 문자열 객체가 새 메모리에 생성됩니다.

왜 문자열은 불변일까?

1. 메모리 공유 최적화 (Interning)
파이썬은 동일한 문자열을 공유하여 메모리를 아낍니다. 문자열이 가변적이라면 한 곳에서 값을 변경할 때 이를 공유하는 다른 변수들도 원치 않게 같이 바뀌어 버립니다.

2. 해시값 안정성 (사물함 이름표 비유)
딕셔너리는 키의 고유한 주소표인 해시값을 기준으로 빠르게 데이터를 보관하고 찾아냅니다.

  • 이름표 "apple"의 분류번호가 10번이라 10번 사물함에 데이터를 보관함.
  • 이름표 내용을 "banana"로 수정함.
  • "banana"로 검색하면 25번 사물함을 찾아가므로 실패.
  • "apple"로 검색하면 10번 사물함을 찾아가지만, 사물함의 이름표가 "banana"로 변경되어 있으므로 일치하지 않아 실패.

따라서 딕셔너리의 키는 반드시 불변 객체여야 합니다. 가변 리스트를 키로 쓰면 에러가 발생합니다.

my_dict = {[1, 2]: "value"}
# TypeError: unhashable type: 'list'

리스트 추가 및 삭제 메서드

동적으로 크기가 늘어나거나 줄어들 때 사용하는 내장 함수들입니다.

inventory = ["장검", "방패"]

# 1. 맨 뒤에 추가
inventory.append("물약")  # ['장검', '방패', '물약']

# 2. 특정 인덱스에 삽입 (원래 요소들은 뒤로 밀림)
inventory.insert(1, "길드장검")  # ['장검', '길드장검', '방패', '물약']

# 3. 값의 이름으로 삭제
inventory.remove("방패")  # ['장검', '길드장검', '물약']

# 4. 맨 뒤 원소를 꺼내며 리스트에서 삭제
last_item = inventory.pop()  # '물약' 꺼내짐

리스트 통계 및 정렬 메서드

리스트의 정보를 파악하고 순서를 변경하는 도구입니다.

scores = [35, 95, 50, 95, 80]

# 1. 원소 개수 (길이) 구하기
print(len(scores))  # 5

# 2. 특정 원소의 빈도수 카운트
print(scores.count(95))  # 2

# 3. 오름차순 정렬
scores.sort()  # [35, 50, 80, 95, 95]

# 4. 내림차순 정렬
scores.sort(reverse=True)  # [95, 95, 80, 50, 35]

자주 발생하는 리스트 버그와 해결책

1. IndexError — 범위 초과

items = ["칼", "방패"]
# print(items[2])  # 유효 범위는 0, 1까지!
# 해결: len(items)로 사전에 크기를 체크

2. 얕은 복사 — 단순 대입의 함정

original = [1, 2, 3]
copied = original      # 주소만 복사!
copied.append(99)
print(original)  # [1, 2, 3, 99] (원본도 바뀜)

# 해결: 슬라이싱으로 독립 복사본 생성
copied = original[:]
copied.append(99)
print(original)  # [1, 2, 3]      (원본 안전)
print(copied)    # [1, 2, 3, 99]  (복사본만 변경)

오늘 배운 데이터 묶음 요약

구분 문자열(str) 리스트(list)
표현 방법 "분당중앙고" ["칼", "방패", "포션"]
데이터 수정 불가능 (Immutable) 가능 (Mutable)
인덱싱 / 슬라이싱 지원 지원
원소 추가 / 제거 불가능 append, pop, remove
ESC — 수업으로 돌아가기  ·  F — 전체화면