모두를 위한 파이썬(PY4E) [9. 딕셔너리] 강의

권혁준·2022년 4월 5일
0
post-thumbnail

Charles R. Severance (찰스 R. 세브란스)의 <모두를 위한 파이썬(PY4E)> 수강 내용
https://www.boostcourse.org/cs122/joinLectures/284901

9. 딕셔너리

1) 딕셔너리 개념 및 특징

학습목표

딕셔너리의 개념과 특징을 이해하고 딕셔너리를 생성하여 값에 접근할 수 있다.

핵심키워드

  • 컬랙션
  • 딕셔너리
  • 연관 배열

컬랙션(Collection)
리스트나 딕셔너리같은 변수를 가지는 상황이며 하나의 정보보다는 여러 개의 정보를 저장할 때 사용한다.

리스트(List)
리스트는 순서대로 정리된 컬랙션이다. 데이터를 추가하면 항상 리스트의 끝에 추가되고 -부터 n-1번 위치까지 순서대로 n개의 원소가 저장되어 있다.

딕셔너리(Dictionary)
리스트와 달리 딕셔너리에는 순서라는 것이 없다.
대신 키(key)라는 것이 존재한다. 마치 물건에 포스트잇으로 라벨을 붙이는 것과 비슷하다.
딕셔너리는 다음과 같이 dict()라는 생성자를 통해 생성할 수 있다.
그리고 다음과 같이 생성할 수 있다.

purse = dict() # 또는 purse = {}와 같이 생성할 수도 있다.
purse['money'] = 12 # 'money'라는 키에 12라는 값 연결
purse['candy'] = 3 # 'candy'라는 키에 3이라는 값 연결
purse['tissues'] = 75 # 'tissues'라는 키에 75라는 값 연결

이때 purse를 실행해보면 다음과 같은 형태로 출력된다. 여기서는 순서대로 나오지만 항상 입력한 순서대로 출력되는 것은 아니다.

print(purse)
#{'money':12,'candy':3,'tissue':75}

여기에서 candy라는 키에 저장된 값에 접근하려면 다음과 같이 대괄호 안에 키를 넣어서 접근할 수 있으며

print(purse['candy'])
# 3

접근한 내용을 업데이트할 수도 있다.

purse['candy'] = purse['candy']+2
print(purse)
# {'money': 12,'cancy': 5,'tissue': 75}

연관 배열
이렇게 키와 값이 연결되는 개념을 보통 연관 배열이라고 한다. 접근하는 방식이 리스트와 비슷하지만 키를 갖고 접근한다는 차이점이 있다.
여기에서 연관이 의미하는 것은 키와 값 사이의 연결 관계이다.
리스트에는 위치와 값 사이에 연결 관계가 있다. 그러나 위치와의 연결 관계는 비교적 덜 강력하고 덜 유연하다.
그래서 대부분의 현대 프로그래밍 언어에는 연관 배열이라는 개념이 있다.

연관 배열의 다양한 이름

  • property maps : Perl / PHP
  • hash maps : Java
  • property bags : c# / .Net

2) 딕셔너리를 활용한 데이터 빈도 수 측정\

학습목표

딕셔너리를 활용하여 데이터의 빈도 수를 측정할 수 있다.

핵심키워드

  • 빈도 수 계산
  • in 연산자
  • get 메소드

사람의 방식으로 이름 빈도수 세기
사람이 여러 개의 이름을 보고 그 빈도수를 세는 방식은 보통 다음과 같다.
1) 새로운 이름을 보면 목록에 추가한다.
2) 추가된 이름이 1번 나왔다는 표시를 한다.
3) 목록에 있는 이름이면 기존의 숫자에 1을 더해준다.
4) 모든 이름을 살펴본 후 표시의 갯수를 세어 가장 높은 것을 찾는다.

이 과정을 파이썬 코드로 다음과 같이 표현할 수 있다.

ccc = dict()
ccc['csev']=1
ccc['cwen']=1
print(ccc)
# {'csev':1,'cwen':1}
ccc['cwen'] = ccc['cwen']+1
print(ccc)
# {'csev':1,'cwen':2}

딕셔너리를 이용해 이름 빈도수 세기
그런데 만약 수백만 개의 이름이 있다면 이런 방식으로 세는 것은 매우 어려워진다.
이때 딕셔너리를 사용하면 쉽게 해결할 수 있는데, 기본 아이디어는 다음과 같다.
1) 이미 저장되어 있는 이름인지 확인한다
2-1) 만약 이미 저장되어 있는 이름일 경우 : 1을 더한다
2-2) 만약 저장되어 있지 않은 이름일 경우 : 이름을 저장하고 1을 배정한다.
3) 최종 결과 중 가장 빈도가 많은 이름을 찾는다.
여기서 가장 중요한 부분은 이미 저장되어 있는 이름인지 확인하는 부분이다.
만약 다음과 같이 딕셔너리에 없는 키를 찾으려고 하면 오류가 발생하게 된다.

ccc = dict()
print(ccc['csev'])

# Traceback (most recent call last):
# File"<stdin>", line 1, in <module>
# KeyError: 'csev'

in 연산자
이런 문제를 for, list, 문자열에서 사용했던 In 연산자를 사용해 해결할 수 있다.
이와 같이 ccc라는 딕셔너리 안에 'csev'라는 값이 있는지 확인하기 위해 in 연산자를 사용하면 참(True) 또는 거짓(False)이라고 우리에게 알려준다.

'csev' in ccc
# False

이제 이것을 바탕으로 5개의 이름 리스트를 순회하며 빈도를 셀 수 있다.
먼저 빈 딕셔너리를 생성하고, names라는 리스트에 5명의 이름을 입력한다.
그리고 이름을 하나씩 순회하며 위의 과정을 반복한 후 최종 결과를 확인하면 된다.

counts = dict()
names = ['csev','cwen','csev','zqian','cwen']
for name in names:
	if name in counts:
    	counts[name] = counts[name]+1
    else:
    	counts[name] = 1
print(counts)

# {'csev': 2,'zqian': 1,'cwen': 2}

그리고 이렇게 not 연산자를 사용해서 동일하게 해결할 수도 있다.

counts = dict()
names = ['csev','cwen','csev','zqian','cwen']
for name in names:
	if name not in counts:
    	counts[name] = 1
    else:
    	counts[name] = counts[name]+1
print(counts)

# {'csev': 2,'zqian': 1,'cwen': 2}

get 메소드
이와 같이 딕셔너리에 존재하는 키인지 아닌지 여부에 따라 처리하는 패턴은 get이라는 메소드를 사용해서 간결하게 해결할 수 있다.
여기에서 counts,get(name, 0)의 의미는 counts 딕셔너리에 name이라는 키가 존재할 경우 name에 대한 값을 불러오고, 그렇지 않을 경우에는 counts 딕셔너리에 name이라는 키에 0이라는 값을 갖는 데이터를 추가하라는 의미이다.

counts = dict()
names = ['csev','cwen','csev','zqian','cwen']
for name in names:
	counts[name] = counts.get(name,0)+1
print(counts)

# {'csev': 2,'zqian': 1,'cwen': 2}

지금은 이 코드가 조금 복잡해보이지만 앞으로는 이런 패턴을 자주 볼 것이기 때문에 곧 익숙해진다.


3) 딕셔너리 활용하기

학습목표

파일에 저장된 데이터를 읽어와서 딕셔너리를 활용하여 데이터의 빈도 수를 측정할 수 있다.

핵심키워드

  • split 메소드
  • item 메소드

다음 문장에서 가장 많이 나타난 단어는 무엇일까요?

Writing programs (or programming) is a very creative and rewarding activity. You can write programs for many reasons 
ranging from making your living to solving a difficult data analysis problem to having fun to helping someone else solve 
a problem. This book assumes that everyone needs to know how to program and that once you know how to program,
you will figure out what you want to do with your newfound skills.

이런 종류의 문제는 사람이 하기에 매우 어려운 문제이지만, 지난 시간에 배웠던 것과 같은 패턴의 문제이기 때문에 딕셔너리를 사용해 쉽게 해결할 수 있다.
단지, 리스트에 저장된 데이터가 아니라 긴 문장 형태의 데이터라는 점이 다를 뿐이다.
그러면 먼저 문장을 어떻게 단어로 바꿀 수 있는지 알아보자.

split 메소드
문자열에 split 메소드를 실행시키면 다음과 같이 띄어쓰기를 기준으로 문장을 분할해 단어들의 리스트로 만들어준다.
(참고로, 별도의 옵션을 사용하면 공백 문자(스페이스바)가 아닌 다른 문자를 기준으로도 분할할 수 있다.)

line = 'The general pattern to count the words'
print(line.split())

#['The','general','pattern','to','count','the','words']

따라서 문장이 길어지더라도 동일한 결과가 나타난다.

counts = dict()
line = 'The general pattern to count the words in a line of text is to split the line into words, then loop through the words and use a dictionary to track the count of each word independently.'

words = line.split()

print('Words:', words)

# Words: ['The', 'general', 'pattern', 'to', 'count', 'the', 'words', 'in', 'a', 'line', 'of', 'text', 'is', 'to', 'split', 'the', 'line', 'into', 'words,', 'then', 'loop', 'through', 'the', 'words', 'and', 'use', 'a', 'dictionary', 'to', 'track', 'the', 'count', 'of', 'each', 'word', 'independently.']

이렇게 문장을 리스트로 변환시키면 지난 시간에 우리가 사용했던 get 메소드를 활용해서 문장에 있는 단어들의 빈도수를 구할 수 있게 된다.

for word in words:
    counts[word] = counts.get(word,0) + 1
print('Counts', counts)

# Counts {'The': 2, 'general': 2, 'pattern': 2, 'to': 6, 'count': 4, 'the': 8, 'words': 4, 'in': 2, 'a': 4, 'line': 4, 'of': 4, 'text': 2, 'is': 2, 'split': 2, 'into': 2, 'words,': 2, 'then': 2, 'loop': 2, 'through': 2, 'and': 2, 'use': 2, 'dictionary': 2, 'track': 2, 'each': 2, 'word': 2, 'independently.': 2}

그리고 이런 패턴을 활용하면 파일에 저장된 소설과 같은 텍스트에 대한 빈도수도 구할 수 있게 된다. 이것은 잠시 후에 알아보도록 하자.

딕셔너리에 루프를 적용하는 방법
지금부터는 딕셔너리에 루프(반복문)을 적용하는 방법에 대해 알아보자.
지금까지 딕셔너리를 만드는데만 루프를 사용했지만, 딕셔너리에 저장된 데이터를 다룰 때도 루프가 유용하게 사용된다.
먼저 count라는 딕셔너리를 for 반복문에 넣고 다음과 같이 실행하면, 딕셔너리의 키와 값이 각각 출력된다.

counts={'chuck':1,'fred':42,'jan':100}
for key in counts:
	print(key,counts[key])
    
# chuck 1
# fred 42
# jan 100

지금부터는 딕셔너리의 키나 값을 별도로 저장하는 몇 가지 방법을 알아보자
먼저 딕셔너리를 리스트로 변환하면 이와 같이 키로만 구성된 리스트를 얻을 수 있다.

jjj = {'chuck':1,'fred':42,'jan':100}
print(list(jjj))
# {'jan','chuck','fred'}

그리고 딕셔너리의 key라는 메소드를 사용해도 같은 결과를 얻을 수 있다.

jjj = {'chuck':1,'fred':42,'jan':100}
print(jjj.keys())
# {'jan','chuck','fred'}

딕셔너리의 값으로만 구성된 리스트를 얻으려면 value라는 메소드를 사용하면 된다.

jjj = {'chuck':1,'fred':42,'jan':100}
print(jjj.values())
# {100,1,42}

items 메소드
keys, values 메소드로는 딕셔너리의 키와 값의 쌍을 얻을 수 없었다. 키와 값의 쌍을 얻기 위해서는 items 메소드를 사용하면 된다.
다음과 같이 딕셔너리에 items 메소드를 실행하면 '튜플(tuple)'이라는 자료 구조 안에 키와 값이 쌍을 이루어 저장된 리스트가 반환된다.

jjj = {'chuck':1,'fred':42,'jan':100}
print(jjj.items())
#[('jan',100),('chuck',1),('fred',42)]

그리고 이 items 메소드를 사용하면 다음과 같이 간결하게 딕셔너리에 저장된 키와 값을 출력할 수 있게 된다.

jjj = {'chuck':1,'fred':42,'jan':100}
for aaa,bbb in jjj.items():
	print(aaa,bbb)
    
# chuck 1
# fred 42
# jan 100

파일에 저장된 데이터 읽어와서 빈도 분석하기
지난 시간에 배웠던 파일에서 데이터를 읽어오는 방법을 기억해야 한다.
그리고 파일에서 읽어온 데이터를 이와 같이 한 문장씩 읽어온 후 위에서 배운 코드와 연결시키면 텍스트 파일에 있는 모든 단어에 대한 빈도가 계산될 것이다.
(만약 이 코드가 이해되지 않는다면 한줄씩 천천히 곱씹으며 완벽히 이해해야 한다.)

name = input('Enter file:')
handle = open(name)

counts = dict()
for line in handle:
	words = line.split()
    for word in words:
    	counts[word] = counts.get(word,0)+1
        
print(counts)
# Enter file: words.txt

이제 여기에서 가장 많이 나온 단어를 찾아야 한다.
이전에 배웠던 최대값을 찾는 알고리즘을 적용하되 키와 값의 쌍을 출력할 수 있도록 items 메소드를 사용한다.
이 코드는 최대 빈도를 저장할 bigcount라는 변수와 가장 많이 나온 단어를 저장할 bigword라는 변수를 None으로 초기화하면서 시작된다.
그리고 for 반복문을 통해 counts 딕셔너리의 키는 word에, 빈도수는 count에 저장이 된다.
이후 count 값이 가장 큰 단어를 bigword에, 그 단어의 빈도수를 bigcount에 저장을 하고 이것을 모든 단어에 대해 반복한다.
따라서, 반복이 종료된 후 마지막 남아있는 bigword,bigcount를 출력하면 해당 텍스트 파일에서 가장 많이 나온 단어와 그 빈도 수가 출력된다.

bigcount = None
bigword = None
for word.count in counts.items():
	if bigcount is None or count > bigcount:
       bigword = word
       bigcount = count
       
print(bigword,bigcount)
# Enter file: words.txt
# to 16

텍스트 파일에서 데이터를 읽어와 가장 많이 나온 단어를 출력하는 프로그램의 전체 코드는 다음과 같다.

name = input('Enter file:')
handle = open(name)

counts = dict()
for line in handle:
	words = line.split()
    for word in words:
    	count[word] = counts.get(word,0)+1
        
bigcount = None
bigword = None
for word,count in counts.items():
	if bigcount is None or count > bigcount:
       bigword = word
       bigcount = count
       
print(bigword,bigcount)

4) <실습> 딕셔너리를 활용한 데이터 빈도 수 측정

실습
Exercise 9
clown.txt 파일 내용
the clown ran after the car and the car ran into the tent and the tent fell down on the clown and the car

fname = input('Enter File: ')
if len(fname) < 1 : fname = 'clown.txt'
hand = open(fname)

di = dict()
for lin in hand:
	lin = lin.rstrip()
    wds = lin.split()
    for w in wds:
		# idiom: retrieve/create/update counter
        di[w] = di.get(w,0) + 1
       
# print(di)

# now we want to find the most common word
largest = -1
theword = None
for k,v in di.items() :
    if v > largest :
    	largest = v
		theword = k # cature/remember the word that was largest

print(theword,largest)
profile
비전공개발자의 개발일지

0개의 댓글

관련 채용 정보