인덱싱과 슬라이싱, 그리고 동적 데이터 구조 다루기
게임 속 인벤토리의 10개 아이템을 10개의 변수로 만든다면?
하나로 묶인 데이터 그룹을 활용하면 대량의 데이터를 손쉽게 관리할 수 있습니다.
규칙: 음수 인덱스는 자동으로 + 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]) # 고앙중당분
주의: 지정한 끝 인덱스 번호 자체는 결과 범위에 포함되지 않습니다.
간격이 음수이면 이동 방향이 오른쪽에서 왼쪽으로 바뀝니다.
시작/끝을 생략하면 자동으로 맨 끝에서 맨 앞까지 전체 범위가 설정됩니다.
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] (뒤에서 두 칸씩)
간격의 부호가 방향을 결정합니다. 양수 -> 왼쪽에서 오른쪽, 음수 -> 오른쪽에서 왼쪽.
inventory = ["단검", "방패", "물약"]
inventory[0] = "장검"
print(inventory) # ['장검', '방패', '물약']
메모리 주소는 유지되며 그 자리의 데이터만 교체됩니다.
player_class = "마법사"
# player_class[0] = "전" # TypeError 발생!
# 슬라이싱으로 새 문자열 조립
player_class = "전" + player_class[1:] # "전법사"
기존 메모리 값은 유지되고, 새로운 문자열 객체가 새 메모리에 생성됩니다.
1. 메모리 공유 최적화 (Interning)
파이썬은 동일한 문자열을 공유하여 메모리를 아낍니다. 문자열이 가변적이라면 한 곳에서 값을 변경할 때 이를 공유하는 다른 변수들도 원치 않게 같이 바뀌어 버립니다.
2. 해시값 안정성 (사물함 이름표 비유)
딕셔너리는 키의 고유한 주소표인 해시값을 기준으로 빠르게 데이터를 보관하고 찾아냅니다.
따라서 딕셔너리의 키는 반드시 불변 객체여야 합니다. 가변 리스트를 키로 쓰면 에러가 발생합니다.
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] items = ["칼", "방패"]
# print(items[2]) # 유효 범위는 0, 1까지!
# 해결: len(items)로 사전에 크기를 체크
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 |