포스트

[이제와서 시작하는 Python 마스터하기 #5] 리스트, 튜플, 딕셔너리 정복하기

[이제와서 시작하는 Python 마스터하기 #5] 리스트, 튜플, 딕셔너리 정복하기

🍳 5분만에 만드는 레스토랑 주문 시스템

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def restaurant_order_system():
    """자료구조를 활용한 레스토랑 주문 시스템"""

    # 메뉴 (딕셔너리)
    menu = {
        "한식": {
            "비빔밥": 9000,
            "된장찌개": 8000,
            "김치찌개": 7500,
            "불고기": 12000
        },
        "중식": {
            "짜장면": 8000,
            "짬뽕밥": 9000,
            "탕수육": 12000,
            "까슐포": 9500
        },
        "음료": {
            "콜라": 2000,
            "사이다": 2000,
            "맥주": 4000,
            "": 0
        }
    }

    # 주문 리스트
    orders = []

    # 테이블별 주문 (튜플로 저장)
    table_orders = [
        (1, ["비빔밥", "콜라"]),
        (2, ["짜장면", "탕수육", "사이다", "맥주"]),
        (3, ["불고기", "김치찌개", "콜라", "콜라"])
    ]

    print("🍳 레스토랑 주문 시스템\n")

    # 주문 처리
    for table_num, items in table_orders:
        table_total = 0
        order_detail = {"table": table_num, "items": [], "total": 0}

        print(f"🏎️ 테이블 {table_num}번 주문:")
        for item in items:
            # 모든 카테고리에서 아이템 검색
            for category, dishes in menu.items():
                if item in dishes:
                    price = dishes[item]
                    table_total += price
                    order_detail["items"].append((item, price))
                    print(f"  - {item}: {price:,}")
                    break

        order_detail["total"] = table_total
        orders.append(order_detail)
        print(f"  합계: {table_total:,}\n")

    # 전체 통계
    total_sales = sum(order["total"] for order in orders)
    most_expensive_table = max(orders, key=lambda x: x["total"])

    # 인기 메뉴 분석 (리스트 컴프리헨션)
    all_items = [item for table, items in table_orders for item in items]
    popular_items = {}
    for item in all_items:
        popular_items[item] = popular_items.get(item, 0) + 1

    print("📊 영업 통계:")
    print(f"💰 총 매출: {total_sales:,}")
    print(f"🎆 최고 매출 테이블: {most_expensive_table['table']}번 ({most_expensive_table['total']:,}원)")
    print(f"🏆 가장 인기 메뉴: {max(popular_items, key=popular_items.get)} ({max(popular_items.values())}개)")

    return orders

# 실행
# restaurant_order_system()

📚 Python의 핵심 자료구조

Python에서 데이터를 효율적으로 관리하려면 자료구조를 잘 이해해야 합니다. 이번 포스트에서는 가장 많이 사용되는 세 가지 자료구조를 완벽히 마스터해보겠습니다.

graph TD
    A[Python 자료구조] --> B[리스트<br/>List]
    A --> C[튜플<br/>Tuple]
    A --> D[딕셔너리<br/>Dictionary]
    
    B --> B1[가변<br/>Mutable]
    B --> B2[순서 있음<br/>Ordered]
    B --> B3[중복 허용<br/>Duplicates OK]
    
    C --> C1[불변<br/>Immutable]
    C --> C2[순서 있음<br/>Ordered]
    C --> C3[중복 허용<br/>Duplicates OK]
    
    D --> D1[가변<br/>Mutable]
    D --> D2[순서 있음<br/>Ordered<br/>(3.7+)]
    D --> D3[키 중복 불가<br/>Unique Keys]

[!TIP] 초보자를 위한 비유: 자료구조는 그릇입니다!

  • 리스트(List): 아무거나 다 담을 수 있는 장바구니 (순서대로 담기고, 뺄 수도 있음)
  • 튜플(Tuple): 한 번 포장하면 못 뜯는 선물 세트 (내용물 변경 불가)
  • 딕셔너리(Dictionary): 이름표가 붙은 약 서랍 (이름표(Key)로 내용물(Value)을 찾음)

📋 리스트 (List)

📊 실전 예제: 주식 포트폴리오 관리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def stock_portfolio_manager():
    """리스트를 활용한 주식 포트폴리오 관리"""

    # 포트폴리오 리스트
    portfolio = [
        ["삼성전자", 100, 70000],  # [종목명, 수량, 매수가]
        ["SK하이닉스", 50, 120000],
        ["NAVER", 30, 350000],
        ["카카오", 80, 45000]
    ]

    # 현재가 (딕셔너리로 관리하면 더 효율적이지만 예제를 위해)
    current_prices = [68000, 125000, 380000, 48000]

    print("📊 주식 포트폴리오 현황\n")
    print(f"{'\uc885\ubaa9':^12} {'\uc218\ub7c9':>6} {'\ub9e4\uc218\uac00':>10} {'\ud604\uc7ac\uac00':>10} {'\ud3c9\uac00\uc190\uc775':>12} {'\uc218\uc775\ub960':>8}")
    print("-" * 70)

    total_invested = 0
    total_current = 0
    profits = []  # 수익금 리스트

    for i, stock in enumerate(portfolio):
        name, qty, buy_price = stock
        current_price = current_prices[i]

        invested = qty * buy_price
        current_value = qty * current_price
        profit = current_value - invested
        profit_rate = (profit / invested) * 100

        total_invested += invested
        total_current += current_value
        profits.append(profit)

        # 수익/손실에 따라 이모지
        emoji = "📈" if profit > 0 else "📉" if profit < 0 else "➡️"

        print(f"{name:12} {qty:6d} {buy_price:10,} {current_price:10,} {profit:+12,.0f} {profit_rate:+7.1f}% {emoji}")

    # 통계
    total_profit = total_current - total_invested
    total_profit_rate = (total_profit / total_invested) * 100

    print("-" * 70)
    print(f"{'\ud569\uacc4':12} {' ':6} {total_invested:10,} {total_current:10,} {total_profit:+12,.0f} {total_profit_rate:+7.1f}%")

    # 분석
    print(f"\n📈 최고 수익 종목: {portfolio[profits.index(max(profits))][0]} (+{max(profits):,.0f}원)")
    print(f"📉 최대 손실 종목: {portfolio[profits.index(min(profits))][0]} ({min(profits):,.0f}원)")

    # 정렬된 포트폴리오 (수익률 기준)
    sorted_portfolio = sorted(zip(portfolio, profits),
                             key=lambda x: x[1], reverse=True)

    print("\n🏆 수익률 TOP 3:")
    for i, (stock, profit) in enumerate(sorted_portfolio[:3], 1):
        print(f"  {i}. {stock[0]}: {profit:+,.0f}")

# 실행
# stock_portfolio_manager()

리스트는 Python에서 가장 많이 사용되는 자료구조로, 여러 값을 순서대로 저장할 수 있습니다.

리스트 생성과 기본 연산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 리스트 생성
empty_list = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True, None]  # 다양한 타입 가능

# list() 함수로 생성
chars = list("Python")  # ['P', 'y', 't', 'h', 'o', 'n']
range_list = list(range(5))  # [0, 1, 2, 3, 4]

# 인덱싱과 슬라이싱
fruits = ["apple", "banana", "orange", "grape", "kiwi"]
print(fruits[0])      # apple (첫 번째 요소)
print(fruits[-1])     # kiwi (마지막 요소)
print(fruits[1:3])    # ['banana', 'orange'] (슬라이싱)
print(fruits[:3])     # ['apple', 'banana', 'orange']
print(fruits[2:])     # ['orange', 'grape', 'kiwi']
print(fruits[::2])    # ['apple', 'orange', 'kiwi'] (스텝)
print(fruits[::-1])   # ['kiwi', 'grape', 'orange', 'banana', 'apple'] (역순)

# 리스트 수정
fruits[0] = "watermelon"  # 요소 변경
fruits[1:3] = ["mango", "peach"]  # 슬라이스 변경

리스트 메서드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 추가 메서드
numbers = [1, 2, 3]
numbers.append(4)           # 끝에 추가: [1, 2, 3, 4]
numbers.insert(1, 1.5)      # 특정 위치 삽입: [1, 1.5, 2, 3, 4]
numbers.extend([5, 6])      # 여러 요소 추가: [1, 1.5, 2, 3, 4, 5, 6]

# append vs extend 차이
list1 = [1, 2, 3]
list1.append([4, 5])        # [1, 2, 3, [4, 5]] - 리스트 자체가 추가
list2 = [1, 2, 3]
list2.extend([4, 5])        # [1, 2, 3, 4, 5] - 요소들이 추가

# 삭제 메서드
items = ["a", "b", "c", "d", "e"]
removed = items.pop()       # 마지막 요소 제거 및 반환: 'e'
removed = items.pop(1)      # 인덱스 1의 요소 제거: 'b'
items.remove("c")           # 첫 번째 'c' 제거
items.clear()               # 모든 요소 제거

# del 키워드
numbers = [1, 2, 3, 4, 5]
del numbers[2]              # 인덱스 2 삭제: [1, 2, 4, 5]
del numbers[1:3]            # 슬라이스 삭제: [1, 5]

# 검색과 정렬
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(numbers.index(4))     # 2 (4의 인덱스)
print(numbers.count(1))     # 2 (1의 개수)

numbers.sort()              # 오름차순 정렬 (원본 수정)
print(numbers)              # [1, 1, 2, 3, 4, 5, 6, 9]

numbers.sort(reverse=True)  # 내림차순 정렬
numbers.reverse()           # 순서 뒤집기

# sorted() 함수 (원본 유지)
original = [3, 1, 4, 1, 5]
sorted_list = sorted(original)  # 원본 유지
print(original)             # [3, 1, 4, 1, 5]
print(sorted_list)          # [1, 1, 3, 4, 5]

리스트 컴프리헨션 심화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 기본 패턴
squares = [x**2 for x in range(10)]

# 조건 포함
even_squares = [x**2 for x in range(10) if x % 2 == 0]

# 다중 조건
numbers = [x for x in range(20) if x % 2 == 0 if x % 3 == 0]  # 6의 배수

# 중첩 반복
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row]
print(flattened)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# 조건부 표현식
result = [x if x > 0 else 0 for x in [-1, 2, -3, 4]]
print(result)  # [0, 2, 0, 4]

# 2차원 리스트 생성
grid = [[0 for _ in range(5)] for _ in range(3)]
# [[0, 0, 0, 0, 0],
#  [0, 0, 0, 0, 0],
#  [0, 0, 0, 0, 0]]

🔒 튜플 (Tuple)

튜플은 불변(immutable) 시퀀스로, 한 번 생성되면 수정할 수 없습니다.

튜플 생성과 특징

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 튜플 생성
empty_tuple = ()
single = (1,)  # 콤마 필수! (1)은 정수 1
numbers = (1, 2, 3, 4, 5)
mixed = (1, "hello", 3.14)

# 괄호 없이도 가능 (패킹)
coordinates = 10, 20, 30
print(type(coordinates))  # <class 'tuple'>

# 언패킹
x, y, z = coordinates
print(x, y, z)  # 10 20 30

# 확장 언패킹 (Python 3)
first, *middle, last = (1, 2, 3, 4, 5)
print(first)   # 1
print(middle)  # [2, 3, 4]
print(last)    # 5

# 튜플은 불변
# numbers[0] = 10  # TypeError!

# 하지만 내부의 가변 객체는 수정 가능
tuple_with_list = ([1, 2], [3, 4])
tuple_with_list[0].append(3)  # OK
print(tuple_with_list)  # ([1, 2, 3], [3, 4])

[!TIP] 언제 튜플을 써야 하나요?

“이 데이터는 절대 변하면 안 돼!”라고 확신할 때 튜플을 쓰세요. 예: 좌표(위도, 경도), RGB 색상값(255, 255, 255), 일주일의 요일 등. 실수로 데이터를 바꾸는 것을 막아주기 때문에 안전한 코드를 짤 수 있습니다.

튜플 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 함수에서 여러 값 반환
def get_min_max(numbers):
    return min(numbers), max(numbers)

min_val, max_val = get_min_max([3, 1, 4, 1, 5, 9])
print(f"최소: {min_val}, 최대: {max_val}")  # 최소: 1, 최대: 9

# 값 교환 (스왑)
a, b = 10, 20
a, b = b, a  # 튜플 언패킹으로 간단히 교환
print(a, b)  # 20 10

# 딕셔너리의 키로 사용 (불변이므로 가능)
locations = {
    (37.5, 127.0): "서울",
    (35.2, 129.1): "부산",
    (35.8, 128.6): "대구"
}

# 네임드 튜플 (collections.namedtuple)
from collections import namedtuple

# Point 클래스 생성
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p.x, p.y)  # 10 20
print(p[0], p[1])  # 10 20 (인덱스로도 접근 가능)

# Person 네임드 튜플
Person = namedtuple('Person', 'name age city')
person = Person('김철수', 30, '서울')
print(f"{person.name}{person.city}에 사는 {person.age}살입니다.")

📕 딕셔너리 (Dictionary)

딕셔너리는 키-값 쌍으로 데이터를 저장하는 자료구조입니다.

딕셔너리 생성과 접근

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 딕셔너리 생성
empty_dict = {}
person = {"name": "김파이썬", "age": 25, "city": "서울"}
scores = dict(math=90, english=85, science=88)

# 키를 통한 접근
print(person["name"])     # 김파이썬
print(person.get("age"))  # 25

# get() 메서드의 장점
print(person.get("phone"))           # None (KeyError 없음)
print(person.get("phone", "없음"))   # 없음 (기본값 지정)

# 요소 추가/수정
person["phone"] = "010-1234-5678"  # 추가
person["age"] = 26                 # 수정

# 여러 요소 업데이트
person.update({"email": "kim@python.com", "age": 27})

# 삭제
del person["phone"]
removed = person.pop("email", None)  # 값 반환하며 삭제
person.clear()  # 모든 요소 삭제

[!WARNING] 딕셔너리 키(Key)의 조건

딕셔너리의 키(Key)는 반드시 불변(Immutable)이어야 합니다.

  • 가능: 정수, 실수, 문자열, 튜플
  • 불가능: 리스트, 딕셔너리 (값이 변할 수 있어서 키로 쓸 수 없음)

my_dict = {[1, 2]: "value"} ➡️ 에러 발생! (TypeError)

딕셔너리 메서드와 순회

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 딕셔너리 메서드
student = {
    "name": "이영희",
    "scores": {"math": 95, "english": 88},
    "grade": "A"
}

# 키, 값, 아이템 가져오기
print(student.keys())    # dict_keys(['name', 'scores', 'grade'])
print(student.values())  # dict_values(['이영희', {...}, 'A'])
print(student.items())   # dict_items([('name', '이영희'), ...])

# 순회
for key in student:
    print(f"{key}: {student[key]}")

for key, value in student.items():
    print(f"{key}: {value}")

# 중첩 딕셔너리 접근
print(student["scores"]["math"])  # 95

# setdefault() - 키가 없으면 추가
hobbies = student.setdefault("hobbies", [])
hobbies.append("독서")
print(student["hobbies"])  # ['독서']

딕셔너리 컴프리헨션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 기본 패턴
squares = {x: x**2 for x in range(5)}
print(squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# 조건 포함
even_squares = {x: x**2 for x in range(10) if x % 2 == 0}

# 키-값 변환
items = [("a", 1), ("b", 2), ("c", 3)]
dict_from_list = {k: v for k, v in items}

# 기존 딕셔너리 변환
prices = {"apple": 1000, "banana": 500, "orange": 800}
discounted = {item: price * 0.8 for item, price in prices.items()}

# 딕셔너리 병합 (Python 3.9+)
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}
merged = dict1 | dict2  # {'a': 1, 'b': 3, 'c': 4}

# 이전 버전에서의 병합
merged = {**dict1, **dict2}

🔄 자료구조 간 변환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 리스트 ↔ 튜플
list_data = [1, 2, 3, 4, 5]
tuple_data = tuple(list_data)
back_to_list = list(tuple_data)

# 리스트/튜플 → 딕셔너리
pairs = [("a", 1), ("b", 2), ("c", 3)]
dict_from_pairs = dict(pairs)

# zip()을 사용한 변환
keys = ["name", "age", "city"]
values = ["박민수", 28, "인천"]
person = dict(zip(keys, values))

# 딕셔너리 → 리스트/튜플
dict_data = {"x": 10, "y": 20, "z": 30}
keys_list = list(dict_data.keys())
values_tuple = tuple(dict_data.values())
items_list = list(dict_data.items())

# 중첩 구조 평탄화
nested = [[1, 2], [3, 4], [5, 6]]
flattened = [item for sublist in nested for item in sublist]

💡 실전 예제

1. 학생 성적 관리 시스템

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
class StudentGradeManager:
    """학생 성적 관리 시스템"""
    
    def __init__(self):
        self.students = {}
    
    def add_student(self, student_id, name):
        """학생 추가"""
        if student_id in self.students:
            return False
        self.students[student_id] = {
            "name": name,
            "scores": {},
            "attendance": []
        }
        return True
    
    def add_score(self, student_id, subject, score):
        """과목별 점수 추가"""
        if student_id not in self.students:
            return False
        self.students[student_id]["scores"][subject] = score
        return True
    
    def add_attendance(self, student_id, date, status):
        """출석 기록 추가"""
        if student_id not in self.students:
            return False
        self.students[student_id]["attendance"].append({
            "date": date,
            "status": status  # "present", "absent", "late"
        })
        return True
    
    def get_average_score(self, student_id):
        """평균 점수 계산"""
        if student_id not in self.students:
            return None
        scores = self.students[student_id]["scores"].values()
        return sum(scores) / len(scores) if scores else 0
    
    def get_attendance_rate(self, student_id):
        """출석률 계산"""
        if student_id not in self.students:
            return None
        attendance = self.students[student_id]["attendance"]
        if not attendance:
            return 0
        present_count = sum(1 for a in attendance if a["status"] == "present")
        return (present_count / len(attendance)) * 100
    
    def get_top_students(self, n=3):
        """상위 n명의 학생"""
        student_averages = []
        for sid, data in self.students.items():
            avg = self.get_average_score(sid)
            if avg > 0:
                student_averages.append((data["name"], avg))
        
        student_averages.sort(key=lambda x: x[1], reverse=True)
        return student_averages[:n]
    
    def generate_report(self, student_id):
        """학생 성적표 생성"""
        if student_id not in self.students:
            return None
        
        student = self.students[student_id]
        avg_score = self.get_average_score(student_id)
        attendance_rate = self.get_attendance_rate(student_id)
        
        report = f"""
=== 성적표 ===
학번: {student_id}
이름: {student['name']}

과목별 점수:
"""
        for subject, score in student['scores'].items():
            report += f"  - {subject}: {score}\n"
        
        report += f"""
평균 점수: {avg_score:.1f}점
출석률: {attendance_rate:.1f}%
        """
        return report

# 사용 예시
manager = StudentGradeManager()

# 학생 추가
manager.add_student("2024001", "김철수")
manager.add_student("2024002", "이영희")
manager.add_student("2024003", "박민수")

# 점수 입력
students_scores = {
    "2024001": {"국어": 85, "수학": 90, "영어": 78},
    "2024002": {"국어": 92, "수학": 88, "영어": 95},
    "2024003": {"국어": 78, "수학": 85, "영어": 82}
}

for sid, scores in students_scores.items():
    for subject, score in scores.items():
        manager.add_score(sid, subject, score)

# 출석 기록
manager.add_attendance("2024001", "2024-03-01", "present")
manager.add_attendance("2024001", "2024-03-02", "present")
manager.add_attendance("2024001", "2024-03-03", "late")

# 상위 학생 조회
print("=== 상위 3명 ===")
for name, avg in manager.get_top_students():
    print(f"{name}: 평균 {avg:.1f}")

# 성적표 출력
print(manager.generate_report("2024001"))

2. 재고 관리 시스템

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
class InventoryManager:
    """재고 관리 시스템"""
    
    def __init__(self):
        self.inventory = {}  # {상품ID: {"name": 이름, "quantity": 수량, "price": 가격}}
        self.transactions = []  # 거래 기록
    
    def add_product(self, product_id, name, quantity=0, price=0):
        """상품 추가"""
        if product_id in self.inventory:
            return False
        self.inventory[product_id] = {
            "name": name,
            "quantity": quantity,
            "price": price,
            "history": []
        }
        return True
    
    def update_stock(self, product_id, quantity_change, transaction_type="adjustment"):
        """재고 수량 업데이트"""
        if product_id not in self.inventory:
            return False
        
        current = self.inventory[product_id]["quantity"]
        new_quantity = current + quantity_change
        
        if new_quantity < 0:
            return False  # 재고 부족
        
        self.inventory[product_id]["quantity"] = new_quantity
        
        # 거래 기록
        transaction = {
            "product_id": product_id,
            "type": transaction_type,  # "sale", "purchase", "adjustment"
            "quantity_change": quantity_change,
            "timestamp": self._get_timestamp()
        }
        self.transactions.append(transaction)
        self.inventory[product_id]["history"].append(transaction)
        
        return True
    
    def _get_timestamp(self):
        """현재 시간 반환"""
        from datetime import datetime
        return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    def sell_product(self, product_id, quantity):
        """상품 판매"""
        return self.update_stock(product_id, -quantity, "sale")
    
    def purchase_product(self, product_id, quantity):
        """상품 구매"""
        return self.update_stock(product_id, quantity, "purchase")
    
    def get_low_stock_products(self, threshold=10):
        """재고 부족 상품 목록"""
        low_stock = []
        for pid, data in self.inventory.items():
            if data["quantity"] < threshold:
                low_stock.append({
                    "id": pid,
                    "name": data["name"],
                    "quantity": data["quantity"]
                })
        return sorted(low_stock, key=lambda x: x["quantity"])
    
    def get_inventory_value(self):
        """전체 재고 가치 계산"""
        total_value = 0
        for data in self.inventory.values():
            total_value += data["quantity"] * data["price"]
        return total_value
    
    def get_product_report(self, product_id):
        """상품별 상세 리포트"""
        if product_id not in self.inventory:
            return None
        
        product = self.inventory[product_id]
        sales = sum(t["quantity_change"] for t in product["history"] 
                   if t["type"] == "sale")
        purchases = sum(t["quantity_change"] for t in product["history"] 
                       if t["type"] == "purchase")
        
        return {
            "name": product["name"],
            "current_stock": product["quantity"],
            "price": product["price"],
            "total_sales": abs(sales),
            "total_purchases": purchases,
            "stock_value": product["quantity"] * product["price"]
        }

# 사용 예시
inventory = InventoryManager()

# 상품 추가
inventory.add_product("P001", "노트북", 50, 1200000)
inventory.add_product("P002", "마우스", 100, 25000)
inventory.add_product("P003", "키보드", 75, 65000)

# 거래 시뮬레이션
inventory.sell_product("P001", 5)
inventory.sell_product("P002", 20)
inventory.purchase_product("P003", 30)

# 재고 부족 상품 확인
print("=== 재고 부족 상품 ===")
for item in inventory.get_low_stock_products(80):
    print(f"{item['name']}: {item['quantity']}")

# 재고 가치 계산
print(f"\n전체 재고 가치: {inventory.get_inventory_value():,}")

# 상품별 리포트
report = inventory.get_product_report("P001")
if report:
    print(f"\n=== {report['name']} 리포트 ===")
    print(f"현재 재고: {report['current_stock']}")
    print(f"총 판매: {report['total_sales']}")
    print(f"재고 가치: {report['stock_value']:,}")

3. 텍스트 분석기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
class TextAnalyzer:
    """텍스트 분석 도구"""
    
    def __init__(self):
        self.stop_words = {"", "", "", "", "", "", "", "", "", ""}
    
    def analyze_text(self, text):
        """텍스트 종합 분석"""
        words = self._tokenize(text)
        
        return {
            "total_words": len(words),
            "unique_words": len(set(words)),
            "word_frequency": self._count_words(words),
            "top_words": self._get_top_words(words, 5),
            "sentence_count": text.count('.') + text.count('!') + text.count('?'),
            "avg_word_length": self._average_word_length(words),
            "longest_word": max(words, key=len) if words else "",
            "shortest_word": min(words, key=len) if words else ""
        }
    
    def _tokenize(self, text):
        """텍스트를 단어로 분리"""
        import re
        # 한글, 영어, 숫자만 추출
        words = re.findall(r'[가-힣]+|[a-zA-Z]+|[0-9]+', text.lower())
        # 불용어 제거
        return [w for w in words if w not in self.stop_words]
    
    def _count_words(self, words):
        """단어 빈도 계산"""
        frequency = {}
        for word in words:
            frequency[word] = frequency.get(word, 0) + 1
        return frequency
    
    def _get_top_words(self, words, n=5):
        """가장 많이 사용된 단어 n개"""
        frequency = self._count_words(words)
        sorted_words = sorted(frequency.items(), key=lambda x: x[1], reverse=True)
        return sorted_words[:n]
    
    def _average_word_length(self, words):
        """평균 단어 길이"""
        if not words:
            return 0
        return sum(len(word) for word in words) / len(words)
    
    def compare_texts(self, text1, text2):
        """두 텍스트 비교"""
        words1 = set(self._tokenize(text1))
        words2 = set(self._tokenize(text2))
        
        common_words = words1 & words2
        unique_to_text1 = words1 - words2
        unique_to_text2 = words2 - words1
        
        # 자카드 유사도 계산
        jaccard_similarity = len(common_words) / len(words1 | words2) if words1 or words2 else 0
        
        return {
            "common_words": list(common_words),
            "unique_to_text1": list(unique_to_text1),
            "unique_to_text2": list(unique_to_text2),
            "similarity": jaccard_similarity * 100
        }

# 사용 예시
analyzer = TextAnalyzer()

# 텍스트 분석
sample_text = """
Python은 매우 강력한 프로그래밍 언어입니다. 
Python은 쉽고 읽기 쉬운 문법을 가지고 있으며, 
다양한 분야에서 활용됩니다. 데이터 분석, 웹 개발, 
인공지능 등 Python이 사용되지 않는 곳이 거의 없습니다.
"""

result = analyzer.analyze_text(sample_text)
print("=== 텍스트 분석 결과 ===")
print(f"총 단어 수: {result['total_words']}")
print(f"고유 단어 수: {result['unique_words']}")
print(f"문장 수: {result['sentence_count']}")
print(f"평균 단어 길이: {result['avg_word_length']:.1f}")
print(f"가장 긴 단어: {result['longest_word']}")

print("\n가장 많이 사용된 단어 TOP 5:")
for word, count in result['top_words']:
    print(f"  - {word}: {count}")

# 텍스트 비교
text1 = "Python은 데이터 분석에 최적화된 언어입니다."
text2 = "Python은 웹 개발에도 많이 사용되는 언어입니다."

comparison = analyzer.compare_texts(text1, text2)
print(f"\n두 텍스트의 유사도: {comparison['similarity']:.1f}%")
print(f"공통 단어: {comparison['common_words']}")

⚠️ 초보자가 자주 하는 실수

1. 리스트 복사 실수

1
2
3
4
5
6
7
8
9
10
11
12
# ❌ 얕은 복사의 문제
list1 = [[1, 2], [3, 4]]
list2 = list1.copy()  # 또는 list1[:]
list2[0][0] = 99
print(list1)  # [[99, 2], [3, 4]] - 원본도 변경됨!

# ✅ 깊은 복사
import copy
list1 = [[1, 2], [3, 4]]
list2 = copy.deepcopy(list1)
list2[0][0] = 99
print(list1)  # [[1, 2], [3, 4]] - 원본 유지

2. 리스트 초기화 실수

1
2
3
4
5
6
7
8
9
# ❌ 같은 리스트 참조
matrix = [[0] * 3] * 3
matrix[0][0] = 1
print(matrix)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] - 모두 변경됨!

# ✅ 개별 리스트 생성
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 1
print(matrix)  # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

3. 딕셔너리 키 실수

1
2
3
4
5
6
7
# ❌ 변경 가능한 객체를 키로 사용
# dict_wrong = {[1, 2]: "value"}  # TypeError!

# ✅ 불변 객체를 키로 사용
dict_right = {(1, 2): "value"}  # 튜플은 OK
dict_right = {"key": "value"}   # 문자열 OK
dict_right = {42: "value"}      # 숫자 OK

4. 딕셔너리 순회 중 수정

1
2
3
4
5
6
7
8
9
10
11
# ❌ 순회 중 딕셔너리 크기 변경
data = {"a": 1, "b": 2, "c": 3}
for key in data:
    if data[key] < 3:
        del data[key]  # RuntimeError!

# ✅ 복사본이나 리스트로 변환
data = {"a": 1, "b": 2, "c": 3}
for key in list(data.keys()):
    if data[key] < 3:
        del data[key]

5. get() 메서드 미사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ❌ KeyError 위험
counts = {}
word = "hello"
# counts[word] += 1  # KeyError!

# ✅ get() 메서드 사용
counts = {}
word = "hello"
counts[word] = counts.get(word, 0) + 1

# 또는 defaultdict 사용
from collections import defaultdict
counts = defaultdict(int)
counts[word] += 1  # 자동으로 0으로 초기화

🎯 핵심 정리

자료구조 선택 가이드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 리스트를 사용해야 할 때
# - 순서가 중요한 데이터
# - 인덱스로 접근이 필요한 경우
# - 데이터가 자주 변경되는 경우
shopping_cart = ["apple", "banana", "orange"]

# 튜플을 사용해야 할 때
# - 변경되지 않아야 하는 데이터
# - 딕셔너리의 키로 사용
# - 함수에서 여러 값을 반환
coordinates = (37.5665, 126.9780)  # 서울의 위도, 경도

# 딕셔너리를 사용해야 할 때
# - 키-값 쌍으로 데이터 관리
# - 빠른 검색이 필요한 경우
# - 데이터에 의미 있는 레이블이 필요한 경우
user_info = {"id": "user123", "name": "김파이썬", "age": 25}

성능 비교

연산 리스트 튜플 딕셔너리
인덱스 접근 O(1) O(1) -
키 접근 - - O(1)
추가(끝) O(1) 불가 O(1)
삽입(중간) O(n) 불가 O(1)
삭제 O(n) 불가 O(1)
검색 O(n) O(n) O(1)
메모리 중간 적음 많음

메모리 효율적인 사용

1
2
3
4
5
6
7
8
9
# 제너레이터 표현식 (메모리 효율적)
sum_of_squares = sum(x**2 for x in range(1000000))

# 리스트 컴프리헨션 (전체 리스트 생성)
squares_list = [x**2 for x in range(1000000)]  # 메모리 많이 사용

# 필요한 경우만 리스트로 변환
gen = (x**2 for x in range(10))
when_needed = list(gen)

🎓 파이썬 마스터하기 시리즈

📚 기초편 (1-7)

  1. Python 소개와 개발 환경 설정 완벽 가이드
  2. 변수, 자료형, 연산자 완벽 정리
  3. 조건문과 반복문 마스터하기
  4. 함수와 람다 완벽 가이드
  5. 리스트, 튜플, 딕셔너리 정복하기
  6. 문자열 처리와 정규표현식
  7. 파일 입출력과 예외 처리

🚀 중급편 (8-12)

  1. 클래스와 객체지향 프로그래밍
  2. 모듈과 패키지 관리
  3. 데코레이터와 제너레이터
  4. 비동기 프로그래밍 (async/await)
  5. 데이터베이스 연동하기

💼 고급편 (13-16)

  1. 웹 스크래핑과 API 활용
  2. 테스트와 디버깅 전략
  3. 성능 최적화 기법
  4. 멀티프로세싱과 병렬 처리

이전글: 함수와 람다 완벽 가이드 ⬅️ 현재글: 리스트, 튜플, 딕셔너리 정복하기 다음글: 문자열 처리와 정규표현식 ➡️


이번 포스트에서는 Python의 핵심 자료구조인 리스트, 튜플, 딕셔너리를 완벽히 정복했습니다. 다음 포스트에서는 문자열을 다루는 다양한 방법과 강력한 정규표현식에 대해 알아보겠습니다. Happy Coding! 🐍✨

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.