포스트

[Python 100일 챌린지] Day 19 - 딕셔너리 컴프리헨션

[Python 100일 챌린지] Day 19 - 딕셔너리 컴프리헨션

여러 줄의 딕셔너리 생성 코드를 단 1줄로! 리스트 컴프리헨션에 이어 딕셔너리 컴프리헨션을 마스터해봅시다. 키-값 쌍을 간결하고 우아하게 다루는 Pythonic한 방법으로, 데이터 변환과 필터링에 필수적인 문법입니다. (24분 완독 ⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식


🎯 학습 목표 1: 딕셔너리 컴프리헨션 이해하기

딕셔너리 컴프리헨션이란?

딕셔너리 컴프리헨션 = 딕셔너리를 한 줄로 간결하게 생성하는 문법

리스트 컴프리헨션의 딕셔너리 버전으로, 키-값 쌍을 효율적으로 만들 수 있습니다.

1
2
3
4
5
6
7
8
9
# 전통적인 방법 (5줄)
squares = {}
for i in range(5):
    squares[i] = i ** 2
print(squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# 딕셔너리 컴프리헨션 (1줄)
squares = {i: i**2 for i in range(5)}
print(squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

3가지 핵심 장점

장점 설명 예시
간결성 여러 줄을 한 줄로 압축 5줄 → 1줄
가독성 의도가 명확하게 드러남 “각 숫자를 키로, 제곱을 값으로”
성능 일반 반복문보다 빠름 약 20-30% 빠름

사용하면 좋은 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ✅ 데이터 변환
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
grades = {name: "A" if score >= 90 else "B"
          for name, score in scores.items()}

# ✅ 필터링
high_scores = {name: score for name, score in scores.items()
               if score >= 80}

# ✅ 딕셔너리 생성
fruits = ["apple", "banana", "grape"]
lengths = {fruit: len(fruit) for fruit in fruits}

# ✅ 키-값 뒤집기
original = {"a": 1, "b": 2, "c": 3}
reversed_dict = {v: k for k, v in original.items()}

사용하지 않는 게 좋은 경우

1
2
3
4
5
6
7
8
9
10
11
12
# ❌ 복잡한 로직 (가독성 저하)
complex = {
    k: v * 2 if v > 10 else v / 2 if v < 5 else v + 1
    for k, v in data.items()
    if k.startswith('prefix_') and len(k) > 10
}
# 이런 경우는 일반 for문이 더 읽기 쉬움

# ❌ 중첩된 변환
nested = {k1: {k2: v2 * 2 for k2, v2 in v1.items()}
          for k1, v1 in data.items()}
# 중첩은 이해하기 어려움

🎯 학습 목표 2: 기본 문법 패턴 익히기

기본 문법 구조

1
2
3
4
5
6
7
8
# 기본 형태
{키_표현식: 값_표현식 for 변수 in 반복가능객체}

# 조건 추가
{키_표현식: 값_표현식 for 변수 in 반복가능객체 if 조건}

# 값 변환 (if-else)
{: (값1 if 조건 else 값2) for 변수 in 반복가능객체}

패턴 1: 숫자 범위로 딕셔너리 생성

1
2
3
4
5
6
7
8
9
10
11
# 숫자와 제곱
squares = {i: i**2 for i in range(5)}
print(squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# 숫자와 세제곱
cubes = {i: i**3 for i in range(1, 6)}
print(cubes)  # {1: 1, 2: 8, 3: 27, 4: 64, 5: 125}

# 10의 배수
multiples = {i: i*10 for i in range(1, 11)}
print(multiples)  # {1: 10, 2: 20, ..., 10: 100}

패턴 2: 리스트에서 딕셔너리 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 과일 이름과 길이
fruits = ["apple", "banana", "grape", "kiwi"]
lengths = {fruit: len(fruit) for fruit in fruits}
print(lengths)
# {'apple': 5, 'banana': 6, 'grape': 5, 'kiwi': 4}

# 이름과 대문자 변환
names = ["alice", "bob", "charlie"]
upper_names = {name: name.upper() for name in names}
print(upper_names)
# {'alice': 'ALICE', 'bob': 'BOB', 'charlie': 'CHARLIE'}

# 단어와 첫 글자
words = ["Python", "Java", "Ruby"]
first_chars = {word: word[0] for word in words}
print(first_chars)
# {'Python': 'P', 'Java': 'J', 'Ruby': 'R'}

패턴 3: enumerate() 활용

1
2
3
4
5
6
7
8
9
10
# 인덱스를 키로, 값을 값으로
fruits = ["apple", "banana", "grape"]
indexed = {i: fruit for i, fruit in enumerate(fruits)}
print(indexed)
# {0: 'apple', 1: 'banana', 2: 'grape'}

# 1부터 시작하는 인덱스
numbered = {i+1: fruit for i, fruit in enumerate(fruits)}
print(numbered)
# {1: 'apple', 2: 'banana', 3: 'grape'}

패턴 4: zip() 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
# 두 리스트를 딕셔너리로
keys = ["name", "age", "city"]
values = ["Alice", 25, "Seoul"]
person = {k: v for k, v in zip(keys, values)}
print(person)
# {'name': 'Alice', 'age': 25, 'city': 'Seoul'}

# 학생 이름과 점수
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
grade_dict = {name: score for name, score in zip(names, scores)}
print(grade_dict)
# {'Alice': 85, 'Bob': 92, 'Charlie': 78}

패턴 5: 문자열 활용

1
2
3
4
5
6
7
8
9
10
# 알파벳과 인덱스
alphabet = {chr(97+i): i for i in range(26)}
print(alphabet)
# {'a': 0, 'b': 1, 'c': 2, ..., 'z': 25}

# 문자와 ASCII 코드
chars = "ABC"
ascii_codes = {char: ord(char) for char in chars}
print(ascii_codes)
# {'A': 65, 'B': 66, 'C': 67}

🎯 학습 목표 3: 조건문 활용하기

if 조건 (필터링)

딕셔너리 컴프리헨션 끝에 if 조건을 추가하여 특정 항목만 포함할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 짝수의 제곱만
evens = {i: i**2 for i in range(10) if i % 2 == 0}
print(evens)
# {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

# 홀수의 세제곱만
odds = {i: i**3 for i in range(10) if i % 2 == 1}
print(odds)
# {1: 1, 3: 27, 5: 125, 7: 343, 9: 729}

# 3의 배수만
threes = {i: i for i in range(20) if i % 3 == 0}
print(threes)
# {0: 0, 3: 3, 6: 6, 9: 9, 12: 12, 15: 15, 18: 18}

기존 딕셔너리 필터링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 점수 80 이상만
scores = {"Alice": 95, "Bob": 78, "Charlie": 88, "David": 65}
high_scores = {name: score for name, score in scores.items()
               if score >= 80}
print(high_scores)
# {'Alice': 95, 'Charlie': 88}

# 5글자 이상 과일만
fruits = {"apple": 5, "banana": 6, "kiwi": 4, "grape": 5}
long_fruits = {fruit: length for fruit, length in fruits.items()
               if length >= 5}
print(long_fruits)
# {'apple': 5, 'banana': 6, 'grape': 5}

# 양수만
numbers = {"a": -5, "b": 3, "c": -2, "d": 7}
positives = {k: v for k, v in numbers.items() if v > 0}
print(positives)
# {'b': 3, 'd': 7}

if-else (값 변환)

조건에 따라 다른 값을 할당할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 짝수/홀수 구분
numbers = {i: ("짝수" if i % 2 == 0 else "홀수")
           for i in range(5)}
print(numbers)
# {0: '짝수', 1: '홀수', 2: '짝수', 3: '홀수', 4: '짝수'}

# 점수를 등급으로 변환
scores = {"Alice": 95, "Bob": 78, "Charlie": 88, "David": 65}
grades = {name: ("A" if score >= 90 else
                 "B" if score >= 80 else
                 "C" if score >= 70 else "D")
          for name, score in scores.items()}
print(grades)
# {'Alice': 'A', 'Bob': 'C', 'Charlie': 'B', 'David': 'D'}

# 온도를 상태로 변환
temps = {"Seoul": 25, "Busan": 30, "Jeju": 28}
status = {city: ("뜨거움" if temp >= 30 else
                 "따뜻함" if temp >= 20 else "추움")
          for city, temp in temps.items()}
print(status)
# {'Seoul': '따뜻함', 'Busan': '뜨거움', 'Jeju': '따뜻함'}

복잡한 조건

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 짝수이면서 3의 배수
special = {i: i**2 for i in range(20)
           if i % 2 == 0 and i % 3 == 0}
print(special)
# {0: 0, 6: 36, 12: 144, 18: 324}

# 특정 문자로 시작하는 이름만
names = {"Alice": 25, "Bob": 30, "Anna": 28, "Alex": 35}
a_names = {name: age for name, age in names.items()
           if name.startswith("A")}
print(a_names)
# {'Alice': 25, 'Anna': 28, 'Alex': 35}

# 길이가 5 이상이고 모음으로 시작
fruits = ["apple", "banana", "kiwi", "orange", "grape"]
vowel_fruits = {fruit: len(fruit) for fruit in fruits
                if len(fruit) >= 5 and fruit[0] in "aeiou"}
print(vowel_fruits)
# {'apple': 5, 'orange': 6}

🎯 학습 목표 4: 딕셔너리 변환 기법

키-값 뒤집기 (Reverse)

딕셔너리의 키와 값을 바꾸는 패턴입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 기본 뒤집기
original = {"a": 1, "b": 2, "c": 3}
reversed_dict = {v: k for k, v in original.items()}
print(reversed_dict)
# {1: 'a', 2: 'b', 3: 'c'}

# 학생 이름 ↔ 번호
students = {"Alice": 1, "Bob": 2, "Charlie": 3}
numbers = {num: name for name, num in students.items()}
print(numbers)
# {1: 'Alice', 2: 'Bob', 3: 'Charlie'}

# 도시 ↔ 국가
cities = {"Seoul": "Korea", "Tokyo": "Japan", "Paris": "France"}
countries = {country: city for city, country in cities.items()}
print(countries)
# {'Korea': 'Seoul', 'Japan': 'Tokyo', 'France': 'Paris'}

주의: 값이 중복되면 마지막 값만 남습니다!

1
2
3
4
5
# 중복 값 문제
original = {"a": 1, "b": 1, "c": 2}
reversed_dict = {v: k for k, v in original.items()}
print(reversed_dict)
# {1: 'b', 2: 'c'}  # "a"는 사라짐!

키 또는 값 변환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 키를 대문자로 변환
data = {"name": "Alice", "age": 25, "city": "Seoul"}
upper_keys = {k.upper(): v for k, v in data.items()}
print(upper_keys)
# {'NAME': 'Alice', 'AGE': 25, 'CITY': 'Seoul'}

# 값을 문자열로 변환
numbers = {"a": 1, "b": 2, "c": 3}
strings = {k: str(v) for k, v in numbers.items()}
print(strings)
# {'a': '1', 'b': '2', 'c': '3'}

# 키에 접두사 추가
scores = {"math": 85, "english": 92, "science": 78}
prefixed = {f"score_{k}": v for k, v in scores.items()}
print(prefixed)
# {'score_math': 85, 'score_english': 92, 'score_science': 78}

딕셔너리 병합 및 필터링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 두 딕셔너리 병합
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = {**dict1, **dict2}
print(merged)
# {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# 특정 키만 추출
person = {"name": "Alice", "age": 25, "city": "Seoul", "country": "Korea"}
basic_info = {k: v for k, v in person.items() if k in ["name", "age"]}
print(basic_info)
# {'name': 'Alice', 'age': 25}

# 특정 키 제외
filtered = {k: v for k, v in person.items() if k != "country"}
print(filtered)
# {'name': 'Alice', 'age': 25, 'city': 'Seoul'}

중첩 딕셔너리 다루기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 중첩 딕셔너리의 특정 값 추출
students = {
    "Alice": {"math": 85, "english": 92},
    "Bob": {"math": 78, "english": 88},
    "Charlie": {"math": 95, "english": 82}
}

# 수학 점수만 추출
math_scores = {name: scores["math"]
               for name, scores in students.items()}
print(math_scores)
# {'Alice': 85, 'Bob': 78, 'Charlie': 95}

# 평균 점수 계산
averages = {name: sum(scores.values()) / len(scores)
            for name, scores in students.items()}
print(averages)
# {'Alice': 88.5, 'Bob': 83.0, 'Charlie': 88.5}

🎯 학습 목표 5: 실전 활용 패턴

패턴 1: 단어 빈도수 계산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 단어 빈도수
text = "hello world hello python world hello"
words = text.split()

# 방법 1: count() 사용 (간단하지만 느림)
frequency = {word: words.count(word) for word in set(words)}
print(frequency)
# {'hello': 3, 'world': 2, 'python': 1}

# 방법 2: get() 사용 (효율적)
frequency = {}
for word in words:
    frequency[word] = frequency.get(word, 0) + 1
print(frequency)
# {'hello': 3, 'world': 2, 'python': 1}

패턴 2: 그룹화 (Grouping)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 학생들을 등급별로 그룹화
students = {"Alice": "A", "Bob": "B", "Charlie": "A", "David": "C"}

# 등급별 학생 리스트
grade_groups = {}
for name, grade in students.items():
    if grade not in grade_groups:
        grade_groups[grade] = []
    grade_groups[grade].append(name)
print(grade_groups)
# {'A': ['Alice', 'Charlie'], 'B': ['Bob'], 'C': ['David']}

# 딕셔너리 컴프리헨션 활용
grades = set(students.values())
grade_groups = {grade: [name for name, g in students.items() if g == grade]
                for grade in grades}
print(grade_groups)
# {'A': ['Alice', 'Charlie'], 'B': ['Bob'], 'C': ['David']}

패턴 3: 데이터 정규화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 점수 정규화 (0-100 → 0-1)
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
max_score = max(scores.values())
normalized = {name: score / max_score for name, score in scores.items()}
print(normalized)
# {'Alice': 0.924, 'Bob': 1.0, 'Charlie': 0.848}

# 가격 할인 적용
prices = {"apple": 1000, "banana": 500, "grape": 2000}
discount = 0.1  # 10% 할인
discounted = {item: int(price * (1 - discount))
              for item, price in prices.items()}
print(discounted)
# {'apple': 900, 'banana': 450, 'grape': 1800}

패턴 4: 데이터 검증

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 유효한 이메일만 필터링
emails = {
    "user1": "alice@example.com",
    "user2": "bob@invalid",
    "user3": "charlie@test.com"
}
valid_emails = {k: v for k, v in emails.items() if "@" in v and "." in v}
print(valid_emails)
# {'user1': 'alice@example.com', 'user3': 'charlie@test.com'}

# 범위 내 값만 유지
data = {"a": 5, "b": 15, "c": 25, "d": 35}
in_range = {k: v for k, v in data.items() if 10 <= v <= 30}
print(in_range)
# {'b': 15, 'c': 25}

패턴 5: 환경 설정 파싱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 환경 변수 파싱
env_vars = [
    "DB_HOST=localhost",
    "DB_PORT=5432",
    "DB_NAME=mydb"
]

config = {var.split("=")[0]: var.split("=")[1] for var in env_vars}
print(config)
# {'DB_HOST': 'localhost', 'DB_PORT': '5432', 'DB_NAME': 'mydb'}

# 타입 변환 포함
config_with_types = {
    var.split("=")[0]: int(var.split("=")[1])
                       if var.split("=")[1].isdigit()
                       else var.split("=")[1]
    for var in env_vars
}
print(config_with_types)
# {'DB_HOST': 'localhost', 'DB_PORT': 5432, 'DB_NAME': 'mydb'}

💡 실전 팁 & 주의사항

Tip 1: items() vs keys() vs values()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data = {"a": 1, "b": 2, "c": 3}

# ✅ items() - 키와 값 모두 필요
result1 = {k: v*2 for k, v in data.items()}
print(result1)  # {'a': 2, 'b': 4, 'c': 6}

# ✅ keys() - 키만 필요 (생략 가능)
result2 = {k: len(k) for k in data.keys()}
result3 = {k: len(k) for k in data}  # 동일
print(result2)  # {'a': 1, 'b': 1, 'c': 1}

# ✅ values() - 값만 필요 (딕셔너리로는 불가능, 리스트로)
result4 = [v*2 for v in data.values()]
print(result4)  # [2, 4, 6]

Tip 2: 중복 키 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 중복 키가 있으면 마지막 값이 유지됨
items = [("a", 1), ("b", 2), ("a", 3)]
result = {k: v for k, v in items}
print(result)
# {'a': 3, 'b': 2}  # "a"의 값이 3으로 덮어씌워짐

# 중복 키의 값을 리스트로 모으기
result_list = {}
for k, v in items:
    if k in result_list:
        result_list[k].append(v)
    else:
        result_list[k] = [v]
print(result_list)
# {'a': [1, 3], 'b': [2]}

Tip 3: 기본값 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# get() 활용
keys = ["a", "b", "c", "d"]
data = {"a": 1, "c": 3}

# ❌ KeyError 발생 가능
# result = {k: data[k] for k in keys}  # KeyError!

# ✅ get()으로 기본값 설정
result = {k: data.get(k, 0) for k in keys}
print(result)
# {'a': 1, 'b': 0, 'c': 3, 'd': 0}

# ✅ 존재하는 키만 포함
result = {k: data[k] for k in keys if k in data}
print(result)
# {'a': 1, 'c': 3}

Tip 4: 복잡한 표현식 분리하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ❌ 가독성 나쁨
complex = {k: v*2 if v > 10 else v/2 if v < 5 else v+1
           for k, v in data.items()}

# ✅ 함수로 분리
def transform_value(v):
    if v > 10:
        return v * 2
    elif v < 5:
        return v / 2
    else:
        return v + 1

simple = {k: transform_value(v) for k, v in data.items()}

Tip 5: 성능 고려사항

1
2
3
4
5
6
7
8
9
10
11
12
# ❌ count()는 매번 전체 리스트를 순회 (O(n²))
words = ["apple", "banana", "apple", "grape", "apple"]
frequency = {word: words.count(word) for word in set(words)}

# ✅ 딕셔너리로 한 번만 순회 (O(n))
frequency = {}
for word in words:
    frequency[word] = frequency.get(word, 0) + 1

# ✅ Counter 사용 (가장 효율적)
from collections import Counter
frequency = dict(Counter(words))

Tip 6: None 값 처리

1
2
3
4
5
6
7
8
9
10
11
data = {"a": 1, "b": None, "c": 3, "d": None}

# None이 아닌 값만 포함
filtered = {k: v for k, v in data.items() if v is not None}
print(filtered)
# {'a': 1, 'c': 3}

# None을 0으로 대체
replaced = {k: (v if v is not None else 0) for k, v in data.items()}
print(replaced)
# {'a': 1, 'b': 0, 'c': 3, 'd': 0}

🧪 연습 문제

문제 1: 단어 빈도 분석 및 필터링 시스템

과제: 텍스트에서 단어의 빈도를 분석하고, 다양한 기준으로 필터링하는 프로그램을 작성하세요. 딕셔너리 컴프리헨션의 여러 패턴을 활용합니다.

초기 데이터:

1
2
3
4
5
text = """
Python is easy Python is powerful Python is fun
Learn Python today Start your Python journey
Python programming Python development
"""

요구사항:

  1. 모든 단어를 소문자로 변환하고 빈도수 계산하기
  2. 빈도수가 2회 이상인 단어만 필터링하기
  3. 빈도수를 내림차순으로 정렬한 딕셔너리 만들기
  4. 각 문자의 빈도수 계산하기 (공백, 줄바꿈 제외)
  5. 단어 길이별 그룹화하기: {길이: [단어들], ...}
  6. 특정 문자로 시작하는 단어만 추출하기 (예: ‘p’로 시작)
  7. 단어와 그 길이를 매핑하는 딕셔너리 만들기
💡 힌트
  • 단어 분리: split()
  • 소문자 변환: lower()
  • 빈도수 계산: count() 또는 딕셔너리 누적
  • 필터링: {k: v for k, v in dict.items() if 조건}
  • 정렬: sorted(dict.items(), key=lambda x: x[1], reverse=True)
  • 그룹화: 길이를 키로, 단어 리스트를 값으로
✅ 정답
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
text = """
Python is easy Python is powerful Python is fun
Learn Python today Start your Python journey
Python programming Python development
"""

print("===== 단어 빈도 분석 시스템 =====\n")

# 1. 전처리: 소문자 변환 및 단어 분리
clean_text = text.lower().strip()
words = clean_text.split()

print(f"원본 텍스트:\n{text}")
print(f"\n전처리 후 단어 수: {len(words)}")
print(f"고유 단어 수: {len(set(words))}\n")

# 2. 단어 빈도수 계산
word_freq = {word: words.count(word) for word in set(words)}
print("1. 전체 단어 빈도수:")
for word, count in sorted(word_freq.items()):
    print(f"   '{word}': {count}")
print()

# 3. 빈도수 2회 이상인 단어만 필터링
frequent_words = {word: count for word, count in word_freq.items() if count >= 2}
print("2. 빈도수 2회 이상인 단어:")
for word, count in sorted(frequent_words.items(), key=lambda x: x[1], reverse=True):
    print(f"   '{word}': {count}")
print()

# 4. 빈도수 내림차순 정렬 (Top 5)
sorted_freq = dict(sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:5])
print("3. 빈도수 TOP 5:")
for i, (word, count) in enumerate(sorted_freq.items(), 1):
    print(f"   {i}위: '{word}' ({count}회)")
print()

# 5. 문자 빈도수 (공백, 줄바꿈 제외)
clean_chars = clean_text.replace(" ", "").replace("\n", "")
char_freq = {char: clean_chars.count(char) for char in set(clean_chars)}
sorted_char_freq = dict(sorted(char_freq.items(), key=lambda x: x[1], reverse=True)[:10])
print("4. 문자 빈도수 TOP 10:")
for char, count in sorted_char_freq.items():
    print(f"   '{char}': {count}")
print()

# 6. 단어 길이별 그룹화
words_by_length = {}
for word in set(words):
    length = len(word)
    if length not in words_by_length:
        words_by_length[length] = []
    words_by_length[length].append(word)

# 딕셔너리 컴프리헨션 버전
words_by_length_comp = {
    length: [w for w in set(words) if len(w) == length]
    for length in sorted(set(len(w) for w in words))
}

print("5. 단어 길이별 그룹화:")
for length in sorted(words_by_length_comp.keys()):
    word_list = sorted(words_by_length_comp[length])
    print(f"   {length}글자 ({len(word_list)}개): {', '.join(word_list)}")
print()

# 7. 'p'로 시작하는 단어만 추출
p_words = {word: word_freq[word] for word in word_freq if word.startswith('p')}
print("6. 'p'로 시작하는 단어:")
for word, count in sorted(p_words.items(), key=lambda x: x[1], reverse=True):
    print(f"   '{word}': {count}")
print()

# 8. 단어와 길이 매핑
word_length = {word: len(word) for word in set(words)}
print("7. 단어와 길이 매핑 (알파벳 순):")
for word in sorted(word_length.keys())[:10]:
    print(f"   '{word}': {word_length[word]}글자")
print(f"   ... (총 {len(word_length)}개 단어)")
print()

# 추가: 통계
print("8. 전체 통계:")
total_chars = len(clean_chars)
avg_word_length = sum(word_length.values()) / len(word_length)
longest_word = max(word_length.items(), key=lambda x: x[1])
shortest_word = min(word_length.items(), key=lambda x: x[1])

print(f"   총 문자 수: {total_chars}")
print(f"   평균 단어 길이: {avg_word_length:.1f}글자")
print(f"   가장 긴 단어: '{longest_word[0]}' ({longest_word[1]}글자)")
print(f"   가장 짧은 단어: '{shortest_word[0]}' ({shortest_word[1]}글자)")
print(f"   'python' 출현 비율: {word_freq.get('python', 0) / len(words) * 100:.1f}%")

출력:

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
===== 단어 빈도 분석 시스템 =====

원본 텍스트:

Python is easy Python is powerful Python is fun
Learn Python today Start your Python journey
Python programming Python development


전처리 후 단어 수: 17개
고유 단어 수: 12개

1. 전체 단어 빈도수:
   'development': 1회
   'easy': 1회
   'fun': 1회
   'is': 3회
   'journey': 1회
   'learn': 1회
   'powerful': 1회
   'programming': 1회
   'python': 7회
   'start': 1회
   'today': 1회
   'your': 1회

2. 빈도수 2회 이상인 단어:
   'python': 7회
   'is': 3회

3. 빈도수 TOP 5:
   1위: 'python' (7회)
   2위: 'is' (3회)
   3위: 'learn' (1회)
   4위: 'start' (1회)
   5위: 'today' (1회)

4. 문자 빈도수 TOP 10:
   'o': 10회
   'n': 9회
   'y': 7회
   't': 6회
   'p': 6회
   'r': 6회
   'h': 5회
   'e': 5회
   's': 4회
   'a': 4회

5. 단어 길이별 그룹화:
   2글자 (1개): is
   3글자 (1개): fun
   4글자 (2개): easy, your
   5글자 (3개): learn, start, today
   6글자 (1개): python
   7글자 (2개): journey, powerful
   11글자 (2개): development, programming

6. 'p'로 시작하는 단어:
   'python': 7회
   'powerful': 1회
   'programming': 1회

7. 단어와 길이 매핑 (알파벳 순):
   'development': 11글자
   'easy': 4글자
   'fun': 3글자
   'is': 2글자
   'journey': 7글자
   'learn': 5글자
   'powerful': 8글자
   'programming': 11글자
   'python': 6글자
   'start': 5글자
   ... (총 12개 단어)

8. 전체 통계:
   총 문자 수: 97개
   평균 단어 길이: 6.2글자
   가장 긴 단어: 'development' (11글자)
   가장 짧은 단어: 'is' (2글자)
   'python' 출현 비율: 41.2%

문제 2: 학생 성적 데이터 변환 및 분석

과제: 학생 성적 데이터를 다양한 형태로 변환하고 분석하는 프로그램을 작성하세요. 중첩 딕셔너리와 복잡한 변환 패턴을 활용합니다.

초기 데이터:

1
2
3
4
5
6
7
students = {
    "홍길동": {"korean": 85, "english": 92, "math": 78},
    "김철수": {"korean": 90, "english": 88, "math": 95},
    "이영희": {"korean": 78, "english": 85, "math": 90},
    "박민수": {"korean": 95, "english": 90, "math": 88},
    "정수진": {"korean": 67, "english": 75, "math": 72}
}

요구사항:

  1. 각 학생의 평균 점수를 계산하여 딕셔너리로 만들기
  2. 평균 80점 이상인 학생만 필터링하기
  3. 학점 판정 딕셔너리 만들기 (90 이상 A, 80 이상 B, 70 이상 C, 나머지 D)
  4. 과목별 최고 점수 학생 찾기: {"korean": ("이름", 점수), ...}
  5. 학생별 상세 정보 딕셔너리 만들기: 평균, 학점, 석차 포함
  6. 과목별 평균 점수 계산하기
  7. 키와 값을 바꾼 딕셔너리 만들기 (역변환)
💡 힌트
  • 평균 계산: sum(딕셔너리.values()) / len(딕셔너리)
  • 중첩 딕셔너리 접근: student["korean"]
  • 최댓값 찾기: max(딕셔너리.items(), key=lambda x: x[1])
  • 학점 판정: if-elif-else 체이닝
  • 역변환: {v: k for k, v in dict.items()}
✅ 정답
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
students = {
    "홍길동": {"korean": 85, "english": 92, "math": 78},
    "김철수": {"korean": 90, "english": 88, "math": 95},
    "이영희": {"korean": 78, "english": 85, "math": 90},
    "박민수": {"korean": 95, "english": 90, "math": 88},
    "정수진": {"korean": 67, "english": 75, "math": 72}
}

print("===== 학생 성적 데이터 변환 및 분석 =====\n")

# 1. 각 학생의 평균 점수
averages = {
    name: sum(scores.values()) / len(scores)
    for name, scores in students.items()
}
print("1. 학생별 평균 점수:")
for name, avg in sorted(averages.items(), key=lambda x: x[1], reverse=True):
    print(f"   {name}: {avg:.1f}")
print()

# 2. 평균 80점 이상인 학생만 필터링
high_performers = {
    name: avg for name, avg in averages.items()
    if avg >= 80
}
print("2. 평균 80점 이상 학생:")
for name, avg in sorted(high_performers.items(), key=lambda x: x[1], reverse=True):
    print(f"   {name}: {avg:.1f}")
print()

# 3. 학점 판정
grades = {
    name: ("A" if avg >= 90 else
           "B" if avg >= 80 else
           "C" if avg >= 70 else
           "D")
    for name, avg in averages.items()
}
print("3. 학생별 학점:")
for name, grade in sorted(grades.items()):
    print(f"   {name}: {grade}학점 (평균 {averages[name]:.1f}점)")
print()

# 4. 과목별 최고 점수 학생
subjects = ["korean", "english", "math"]
subject_names = {"korean": "국어", "english": "영어", "math": "수학"}
top_students = {
    subject: max(
        ((name, scores[subject]) for name, scores in students.items()),
        key=lambda x: x[1]
    )
    for subject in subjects
}
print("4. 과목별 최고 점수:")
for subject, (name, score) in top_students.items():
    print(f"   {subject_names[subject]}: {name} ({score}점)")
print()

# 5. 학생별 상세 정보 (평균, 학점, 석차)
# 석차 계산
sorted_averages = sorted(averages.items(), key=lambda x: x[1], reverse=True)
ranks = {name: i+1 for i, (name, _) in enumerate(sorted_averages)}

detailed_info = {
    name: {
        "평균": averages[name],
        "학점": grades[name],
        "석차": ranks[name],
        "과목별": students[name]
    }
    for name in students
}
print("5. 학생별 상세 정보:")
for name in sorted(detailed_info.keys(), key=lambda x: ranks[x]):
    info = detailed_info[name]
    print(f"   {name} [{info['석차']}등]:")
    print(f"     평균: {info['평균']:.1f}점, 학점: {info['학점']}")
    print(f"     국어: {info['과목별']['korean']}점, "
          f"영어: {info['과목별']['english']}점, "
          f"수학: {info['과목별']['math']}")
print()

# 6. 과목별 평균 점수
subject_averages = {
    subject: sum(students[name][subject] for name in students) / len(students)
    for subject in subjects
}
print("6. 과목별 평균 점수:")
for subject, avg in subject_averages.items():
    print(f"   {subject_names[subject]}: {avg:.1f}")
print()

# 7. 평균 점수를 키로, 이름을 값으로 역변환 (같은 평균이면 리스트로)
# 주의: 평균이 같으면 덮어쓰이므로, 실전에서는 리스트로 처리 필요
reversed_averages = {
    round(avg, 1): name
    for name, avg in averages.items()
}
print("7. 평균 점수 → 이름 역변환:")
for avg in sorted(reversed_averages.keys(), reverse=True):
    print(f"   {avg}점: {reversed_averages[avg]}")
print()

# 추가: 통계
print("8. 전체 통계:")
overall_avg = sum(averages.values()) / len(averages)
max_student = max(averages.items(), key=lambda x: x[1])
min_student = min(averages.items(), key=lambda x: x[1])

grade_counts = {"A": 0, "B": 0, "C": 0, "D": 0}
for grade in grades.values():
    grade_counts[grade] += 1

print(f"   전체 평균: {overall_avg:.1f}")
print(f"   최고 점수: {max_student[0]} ({max_student[1]:.1f}점)")
print(f"   최저 점수: {min_student[0]} ({min_student[1]:.1f}점)")
print(f"   학점 분포:")
for grade in ["A", "B", "C", "D"]:
    if grade_counts[grade] > 0:
        print(f"     {grade}학점: {grade_counts[grade]}")

출력:

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
===== 학생 성적 데이터 변환 및 분석 =====

1. 학생별 평균 점수:
   박민수: 91.0점
   김철수: 91.0점
   홍길동: 85.0점
   이영희: 84.3점
   정수진: 71.3점

2. 평균 80점 이상 학생:
   박민수: 91.0점
   김철수: 91.0점
   홍길동: 85.0점
   이영희: 84.3점

3. 학생별 학점:
   김철수: A학점 (평균 91.0점)
   박민수: A학점 (평균 91.0점)
   이영희: B학점 (평균 84.3점)
   정수진: C학점 (평균 71.3점)
   홍길동: B학점 (평균 85.0점)

4. 과목별 최고 점수:
   국어: 박민수 (95점)
   영어: 홍길동 (92점)
   수학: 김철수 (95점)

5. 학생별 상세 정보:
   김철수 [1등]:
     평균: 91.0점, 학점: A
     국어: 90점, 영어: 88점, 수학: 95점
   박민수 [1등]:
     평균: 91.0점, 학점: A
     국어: 95점, 영어: 90점, 수학: 88점
   홍길동 [3등]:
     평균: 85.0점, 학점: B
     국어: 85점, 영어: 92점, 수학: 78점
   이영희 [4등]:
     평균: 84.3점, 학점: B
     국어: 78점, 영어: 85점, 수학: 90점
   정수진 [5등]:
     평균: 71.3점, 학점: C
     국어: 67점, 영어: 75점, 수학: 72점

6. 과목별 평균 점수:
   국어: 83.0점
   영어: 86.0점
   수학: 84.6점

7. 평균 점수 → 이름 역변환:
   91.0점: 박민수
   85.0점: 홍길동
   84.3점: 이영희
   71.3점: 정수진

8. 전체 통계:
   전체 평균: 84.5점
   최고 점수: 김철수 (91.0점)
   최저 점수: 정수진 (71.3점)
   학점 분포:
     A학점: 2명
     B학점: 2명
     C학점: 1명

🎯 연습 문제 핵심 포인트

문제 1에서 배운 것:

  • 기본 패턴: {키: 값 for 변수 in 반복자}
  • 조건 필터링: {k: v for k, v in dict.items() if 조건}
  • 변환과 집계: 빈도수, 길이, 그룹화
  • 정렬된 딕셔너리: sorted() + dict()
  • 문자열 처리: split(), lower(), count() 조합

문제 2에서 배운 것:

  • 중첩 딕셔너리 처리: 값이 딕셔너리인 구조
  • 복잡한 계산: 평균, 학점, 석차
  • 역변환 패턴: 키와 값 교환
  • 다중 기준 변환: 여러 조건을 동시에 적용
  • 실전 데이터 구조: 학생 관리 시스템 패턴

📝 오늘 배운 내용 정리

  1. 기본 문법: {키: 값 for 변수 in 반복가능객체} 로 딕셔너리 생성
  2. 조건 필터링: if 조건으로 특정 항목만 포함, if-else로 값 변환
  3. 딕셔너리 변환: items(), keys(), values()로 다양한 변환 패턴
  4. 키-값 뒤집기: 딕셔너리의 키와 값을 간단하게 교환
  5. 실전 패턴: 빈도수 계산, 그룹화, 정규화, 검증, CSV 파싱 등
  6. 성능: 리스트 컴프리헨션과 유사하게 20-30% 빠른 성능
  7. 주의사항: 중복 키 처리, None 값, 복잡한 표현식 분리, get() 활용

🔗 관련 자료


📚 이전 학습

Day 18: 리스트 컴프리헨션 ⭐⭐

어제는 리스트 컴프리헨션으로 간결하게 리스트를 만드는 방법을 배웠습니다!

📚 다음 학습

Day 20: 미니 프로젝트: 영어 단어장 ⭐⭐⭐

Phase 2의 마지막 날! 지금까지 배운 딕셔너리, 리스트, 컴프리헨션을 활용해 실전 영어 단어장 프로젝트를 완성합니다!


“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀

Day 19/100 Phase 2: 자료형 마스터하기 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.