연관배열 활용

연관배열(associative array)은 하나의 (key)와 하나의 (value)을 연관짓는 값들로 구성된 자료형을 의미한다. 즉, 연관 배열의 항목은 키와 값 사이의 관계를 나타낸다.

연관배열은 해시(hash), 사전(dictionary) 등으로 불리며, 파이썬은 사전 자료형(dict)을 연관배열 용도로 제공한다.

사전 자료형이 지원하는 기능은 다음과 같다.

  • 키와 값으로 이루어진 쌍을 사전에 추가하기
  • 특정 키와 연관된 값 확인하기
  • 특정 키와 연관된 값 업데이트 하기
  • 특정 키와 관련된 값 제거하기

여기서는 사전 자료형을 이용하여 데이터셋 다루기에서 다룬 데이터셋을 보다 효율적으로 분석하는 방법을 소개한다.

목표

results5m.txt 파일에 저장된 정보에서 1, 2, 3등을 차지한 선수 이름과 점수를 확인하고자 한다. 다이빙 선수 열 명의 5m 기록을 담고 있는 results5m.txt 파일의 내용은 다음과 같다.

이름  점수
권준기  7.11
김세윤  8.55
나진서  9.02
마동탁  8.35
서길석  7.80
이준용  8.17
이차승  9.33
차승연  7.11
표방호  8.57
한석준  8.93

다이빙 선수들의 등수 확인하기

앞서 리스트 자료형을 활용하여 1, 2, 3등의 점수를 확인할 수 있었다. 하지만 어떤 선수의 점수인지를 확인할 수는 없었다.

앞서 살펴 본 코드는 아래와 같다.

In [1]:
def ranking(n, fileName):                         # fileName 기록파일의 n등 점수 요구
    try:
        resultsData = open(fileName, 'r') 
    except FileNotFoundError:
        print("열고자 하는 파일이 존재하지 않습니다.")

    score_list = [] 

    for line in resultsData: 
        name, score = line.split() 
        try:
            score_list.append(float(score)) 
        except:
            continue
    resultsData.close() 

    score_list.sort(reverse=True) 
    
    return score_list[n-1]                        # fileName 기록파일의 n등 점수 내주기

1, 2, 3등 점수는 다음과 같다.

In [2]:
fName = "./data/results5m.txt"

print(ranking(1, fName))
print(ranking(2, fName))
print(ranking(3, fName))
9.33
9.02
8.93

ranking 함수의 핵심은 각각의 줄을 공백을 기준으로 두 개의 문자열로 쪼갠 후 점수 부분만을 따로 리스트로 저장하는 것이다.

for line in resultsData: 
    name, score = line.split() 
    try:
        score_list.append(float(score)) 
    except:
        continue

그런데 그와같이 점수만 따로 다루면 선수이름과 점수 사이의 연관성을 잃어버리게 된다. 따라서 선수이름과 점수 사이의 연관 관계를 유지하는 방법을 고안해야 한다.

사전 자료형

점수와 선수이름과의 관련성을 정보로 저장하기 위해 점수를 키(key)로, 해당 점수를 얻은 선수이름을 값(value)으로 사용하는 연관배열을 사용한다.

파이썬에서는 연관배열을 사전(dicionary) 자료형으로 제공한다. 예를 들어, 다이빙 50m 경기에서 1, 2, 3등의 실제 점수와 이름은 다음과 같다.

9.33 이차승
9.02 나진서
8.93 한석준

따라서 1, 2, 3등의 점수와 선수이름을 사전으로 표현하면 다음과 같다.

{9.33:이차승, 9.02:나진서, 8.93:한석준}

위 사전에서 볼 수 있듯이 사전 자료형의 항목은 키:값 형식을 가지며, 각 항목들은 쉼표(,)로 구분된다.

빈 사전 생성

빈 사전을 먼저 선언한 후에 항목을 추가할 수 있다. 빈 사전을 선언하는 방식은 다음과 같다.

In [3]:
results50mDict = dict()

results50mDict가 가리키는 값 dict()는 빈 사전이다. 즉, 아무 원소도 포함하지 않음을 아래와 같이 확인할 수 있다.

주의: len 함수는 모음 자료형의 값에 포함된 항목의 개수를 내준다.

In [4]:
len(results50mDict)
Out[4]:
0

사전 항목 추가

이제 1, 2, 3등의 정보를 추가해야 한다. 점수를 키로, 선수이름의 해당 키와 연관된 값으로 선언하는 방식은 다음과 같다.

In [5]:
results50mDict[9.33] = '이차승'
In [6]:
results50mDict[9.02] = '나진서'
In [7]:
results50mDict[8.93] = '한석진'

세 사람의 정보가 추가되었음을 확인할 수 있다.

In [8]:
print(results50mDict)
{9.33: '이차승', 9.02: '나진서', 8.93: '한석진'}

사전에 포함된 항목들을 보여주는 순서는 아무 의미가 없다. 실제로 사전에 포함된 항목들은 집합의 원소처럼 다뤄진다. 즉, 순서와 중복을 무시한다. 실제로 아래와 같이 1등 정보를 추가해도 아무런 변화가 발생하지 않는다.

In [9]:
results50mDict[9.33] = '이차승'
In [10]:
print(results50mDict)
{9.33: '이차승', 9.02: '나진서', 8.93: '한석진'}

사전 생성

순서쌍들의 리스트나 튜플을 이용하여 사전을 생성할 수 있다.

In [11]:
results50mDict = dict([(9.33, '이차승'), (9.02, '나진서'), (8.93, '한석진')])

print(results50mDict)
{9.33: '이차승', 9.02: '나진서', 8.93: '한석진'}
In [12]:
results50mDict = dict(((9.33, '이차승'), (9.02, '나진서'), (8.93, '한석진')))

print(results50mDict)
{9.33: '이차승', 9.02: '나진서', 8.93: '한석진'}

물론 사전 항목을 직접 작성할 수도 있다.

In [13]:
results50mDict = {9.33:'이차승', 9.02:'나진서', 8.93:'한석진'}

print(results50mDict)
{9.33: '이차승', 9.02: '나진서', 8.93: '한석진'}

사전 항목 수정

그런데 3등 선수의 이름이 한석진이 아니라 한석준이다. 따라서 3등 선수의 이름을 수정해야 하는데 다음과 같이 3등 점수에 해당하는 선수이름을 수정하면 된다.

In [14]:
results50mDict[8.93] = '한석준'

3등 선수의 이름이 수정되었음을 확인할 수 있다.

In [15]:
print(results50mDict)
{9.33: '이차승', 9.02: '나진서', 8.93: '한석준'}

사전 항목 확인

그리고 2등 선수의 이름을 확인하려면 2등 선수의 점수를 이용한다.

In [16]:
results50mDict[9.02]
Out[16]:
'나진서'

즉, 대괄호 연산자에 키를 인자로 사용하면 키와 연관된 값을 확인할 수 있다. 그런데 존재하지 않는 키를 사용하면 오류가 발생한다.

In [17]:
results50mDict[9.01]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-17-5ded3a461dd0> in <module>
----> 1 results50mDict[9.01]

KeyError: 9.01

get 메서드

오류 대신에 특정 값이나 정보를 전달하려면 get 메서드를 활용할 수 있다.

In [18]:
results50mDict.get(9.01)

키가 존재하지 않을 경우 다른 값을 보여주게 할 수 있다.

In [19]:
results50mDict.get(9.01, '존재하지 않는 키 입니다. ')
Out[19]:
'존재하지 않는 키 입니다. '

사전 항목 삭제

이제 4등 정보를 추가해 보자. 4등은 8.57을 획득한 표방호이다.

In [20]:
results50mDict[8.57] = '표방호'
In [21]:
print(results50mDict)
{9.33: '이차승', 9.02: '나진서', 8.93: '한석준', 8.57: '표방호'}

그런데 기록에 3등까지의 기록만 남기고 싶다면, 4등 정보는 삭제해야 한다. 두 가지 방식이 있다.

  • pop 사전 자료형 메서드 적용
In [22]:
results50mDict.pop(8.57)
Out[22]:
'표방호'

pop 메서드는 키와 연관된 값을 내주면서 동시에 기존 사전에서 해당 항목을 삭제한다.

In [23]:
print(results50mDict)
{9.33: '이차승', 9.02: '나진서', 8.93: '한석준'}

없는 키를 삭제하려 하면 오류가 발생한다.

In [24]:
results50mDict.pop(8.57)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-24-9d22968886eb> in <module>
----> 1 results50mDict.pop(8.57)

KeyError: 8.57

삭제할 키가 존재하지 않는 경우 아래와 같이 내주는 값을 None 으로 지정하면 오류가 발생하지 않는다.

In [ ]:
results50mDict.pop(8.57, None)
  • del 함수 활용

del 함수는 사전 자료형의 메서드는 아니지만 키의 정보를 이용하여 해당 항목을 사전에서 삭제할 수 있다.

예를 위해, 다시 4등 정보를 추가하자.

In [ ]:
results50mDict[8.57] = '표방호'
In [ ]:
print(results50mDict)

이제 del 함수를 아래와 같이 적용한다.

In [ ]:
del results50mDict[8.57]
In [ ]:
print(results50mDict)

이미 삭제된 정보를 다시 삭제하려 시도하면 오류가 발생한다.

In [ ]:
del results50mDict[8.57]

pop 메서드 경우와는 달리 del 함수를 오류가 발생하지 않도록 인자를 추가하는 방식은 존재하지 않는다.

다이빙 기록과 사전 자료형

이제 사전을 이용하여 선수들의 점수와 이름에 대한 정보를 저장할 수 있다. 앞서 잘못 구현한 ranking 함수를 조금만 수정하면 선수들의 등수 기록을 사전으로 저장하는 함수를 아래와 같이 선언할 수 있다.

In [ ]:
def scoreDict(fileName):                          # fileName 기록파일의 n등 점수 요구
    try:
        resultsData = open(fileName, 'r') 
    except FileNotFoundError:
        print("열고자 하는 파일이 존재하지 않습니다.")

    score_dict = dict()                           # 기록 정보 저장용 사전 생성

    for line in resultsData: 
        name, score = line.split() 
        try:
            score_dict[float(score)] = name       # 점수와 선수이름 연관성 정보 추가
        except:
            continue
    resultsData.close() 

    return score_dict                             # 선수 기록 정보 사전 내주기

이제 선수들의 기록 정보를 사전 자료형으로 확인해보자.

In [ ]:
scores = scoreDict('./data/results5m.txt')
print(scores)

사전 자료형과 for 반복문

사전 자료형의 항목들을 for 반복문을 이용하여 모두 확인할 수 있다. 그런데 아래와 같이 하면 사용된 키들만 보여준다. 이유는 반복문이 키를 중심으로 진행되기 때문이다.

In [ ]:
for key in scores:
    print(key)

따라서 선수들의 이름과 점수를 모두 출력하고 싶으면 다음과 같이 대괄호 연산자를 이용하여 키와 연관된 값을 확인해야 한다.

In [ ]:
for key in scores:
    print(f'{scores[key]} 선수의 점수는 {key}입니다.')

사전과 정렬

하지만 위와 같이 하면 등수를 확인할 수 없다. 등수대로 선수들의 정보를 나열하려면 먼저 사전을 키(점수)를 기준으로 사전에 포함된 항목들을 정렬해야 한다.

그런데 사전 자료형은 순서를 무신한다고 앞서 언급하였다. 따라서 순서 또는 정렬과 관련된 메서드를 제공하지 않는다. 대신에, 사용된 키들로만 구성된 리스트를 정렬한 후 정렬된 순서대로 키와 값을 확인하는 방법을 사용할 수 있다.

구현 방식은 다음과 같다.

  • 먼저, 사전 자료형 메서드인 keys를 이용하여 사용된 키들만을 따로 뽑아낸다.
In [ ]:
scoreKeys = scores.keys()
print(scoreKeys)
  • 뽑아낸 키들을 이용하여 내림차순으로 정렬된 리스트를 생성한다.
In [ ]:
sortedScoreKeys = sorted(scores.keys(), reverse=True)
print(sortedScoreKeys)
  • 정렬된 순서대로 키를 이용하여 키와 값을 확인한다.
In [ ]:
for key in sortedScoreKeys:
    print(f'{scores[key]} 선수의 점수는 {key}입니다.')

enumerate 함수 활용

앞서 등수대로 보여지는 하였다. 하지만 정작 등수는 직접 언급되지 않았다.

아래와 같이 반복문 실행횟수를 셈하여 등수를 보여줄 수는 있다.

In [ ]:
count = 1

for key in sortedScoreKeys:
    print(f'{count}등: {scores[key]} 선수의 점수는 {key}입니다.')
    count += 1

하지만 이보다 좀 더 세련된 방법이 있다. 바로 enumerate 함수를 활용하는 것이다. 언급된 함수가 일은 아래 예제를 통해 바로 확인할 수 있다.

In [ ]:
sortedScoreKeys_index = enumerate(sortedScoreKeys)

for item in sortedScoreKeys_index:
    print(item)

즉, enumerate 함수는 리스트의 항목들이 자신들의 인덱스와 함께 하나의 쌍을 이루도록 하여, 그런 순서쌍들의 리스트와 유사한 자료형을 생성하여 내준다.

In [ ]:
type(sortedScoreKeys_index)

하지만 구성 항목들을 바로 보여주지는 않는다. 구성항목들을 확인하려면 위와 같이 for 반복문을 돌려야 한다.

In [ ]:
print(sortedScoreKeys_index)

등수와 선수 정보를 출력하는 코드를 정리하면 다음과 같다.

주의: enumerate 함수가 내주는 값의 항목은 길이가 2인 튜플이라는 정보를 이용하여 인덱스와 키를 각각 idxkey에 할당하였다.

In [ ]:
sortedScoreKeys_index = enumerate(sortedScoreKeys)

for idx, key in sortedScoreKeys_index:
    print(f'{idx+1}등: {scores[key]} 선수의 점수는 {key}입니다.')

10등의 경우 두 자릿수이기 때문에 다른 등수보다 선수의 이름이 한 칸 밀렸다. 이런 것을 서식 지원 기능을 이용하여 정렬할 수 있다.

또한 idx 값이 0부터 출발하지만 등수는 1부터 출발하기에 idx+1을 사용하였다. 애초부터 idx 값을 1부터 시작하게 하려면 enumerate 함수를 다음과 같이 추가 인자를 사용하여 호출해야 한다.

In [ ]:
sortedScoreKeys_index = enumerate(sortedScoreKeys, 1)

for idx, key in sortedScoreKeys_index:
    print(f'{idx:2}등: {scores[key]} 선수의 점수는 {key}입니다.')

예제: 선수정보 추출

좀 더 복잡한 데이터를 쪼개어 사전으로 저장하는 방식을 살펴보고자 한다. 주어진 데이터와 목표는 다음과 같다.

  • 주어진 데이터: 아래 내용의 문자열
    '101;대파도;8.32;Fish'
    
  • 목표: 아래 내용으로 출력하고자 함
      번호:    101
      이름:    대파도
      기록:    8.32
      보드유형: Fish

위 목표를 달성하기 위해 여기서는 아래 모양의 사전을 생성하고자 한다.

{'번호':'101', '이름':"대파도", '기록':'8.32', '보드유형':'Fish'}

수동 생성

수동 방식 1

주어진 데이터를 세미콜론(;)을 기준으로 쪼개어 얻어진 리스트의 각각의 항목을 값으로 사용하여 수동으로 사전을 생성할 수 있다.

In [25]:
surferInfo = '101;대파도;8.32;Fish'
In [26]:
surferInfoList = surferInfo.split(';')
print(surferInfoList)
['101', '대파도', '8.32', 'Fish']
In [27]:
surferInfoDict = dict()
surferInfoDict['번호'] = surferInfoList[0]
surferInfoDict['이름'] = surferInfoList[1]
surferInfoDict['기록'] = surferInfoList[2]
surferInfoDict['보드유형'] = surferInfoList[3]
In [28]:
print(surferInfoDict)
{'번호': '101', '이름': '대파도', '기록': '8.32', '보드유형': 'Fish'}

수동 방식 2

아래와 같이 쪼개진 각각의 항목을 동시에 이용할 수도 있다.

In [29]:
surferInfoDict = dict()
surferInfoDict['번호'], surferInfoDict['이름'], surferInfoDict['기록'], surferInfoDict['보드유형'] \
= surferInfo.split(';')

print(surferInfoDict)
{'번호': '101', '이름': '대파도', '기록': '8.32', '보드유형': 'Fish'}

주의: 역 슬래시(\)는 파이썬 명령문이 다음 줄에 이어서 계속된다는 것을 의미함.

자동 생성

먼저 사용할 키들을 지정한다.

In [30]:
infoItems = ['번호', '이름', '기록', '보드유형']

기초 방식 1 (비추)

세미콜론으로 쪼갠 리스트 각각의 항목의 인덱스를 이용하여 사전을 생성한다.

In [31]:
surferInfoList = surferInfo.split(';')
surferInfoDict = dict()

index = 0
while index < len(infoItems):
    surferInfoDict[infoItems[index]] = surferInfoList[index]
    index += 1

for info in surferInfoDict:    
    print(f"{info}:\t{surferInfoDict[info]}")     # \t는 탭을 의미함
번호:	101
이름:	대파도
기록:	8.32
보드유형:	Fish

기초방식 2

index와 같은 임시변수를 사용하는 것은 별로 추천되지 않는다. 아래 코드는 for 반복문을 사용하는 코드이다. 그래도 어쩔 수 없이 인덱스는 확인해야 한다.

In [32]:
surferInfoList = surferInfo.split(';')
surferInfoDict = dict()

for item in infoItems:
    item_index = infoItems.index(item)                   # 리스트의 index 메소드 활용
    surferInfoDict[item] = surferInfoList[item_index]

for info in surferInfoDict:    
    print(f"{info}:\t{surferInfoDict[info]}")
번호:	101
이름:	대파도
기록:	8.32
보드유형:	Fish

고급방식 1 (추천)

인덱스를 함께 사용하려면 enumerate 함수가 유용하다.

In [33]:
surferInfoList = surferInfo.split(';')
surferInfoDict = dict()

for index, item in enumerate(infoItems):
    surferInfoDict[item] = surferInfoList[index]

for info in surferInfoDict:    
    print(f"{info}:\t{surferInfoDict[info]}")
번호:	101
이름:	대파도
기록:	8.32
보드유형:	Fish

고급방식 2 (추천)

짝짓기 함수 zip을 활용한다.

zip 함수는 두 개 이상의 리스트 또는 튜플을 인자로 사용하여 동일한 인덱스의 항목들끼리 튜플로 묶어놓은 리스트를 생성한다.

  • 두 개의 리스트 짝짓기
In [34]:
list_abc = ['a', 'b', 'c']
list_abc_indices = [0, 1, 2]

list_pairs = zip(list_abc, list_abc_indices)
In [35]:
for item in list_pairs:
    print(item)
('a', 0)
('b', 1)
('c', 2)
  • 세 개의 튜플 짝짓기
In [36]:
tuple_a = ('a', 0, '파이썬이', 2.5)
tuple_b = ('b', 1, '가장', 3.3)
tuple_c = ('c', 2, '좋아요.', 5.6)

tuple_pairs = zip(tuple_a, tuple_b, tuple_c)
In [37]:
for item in tuple_pairs:
    print(item)
('a', 'b', 'c')
(0, 1, 2)
('파이썬이', '가장', '좋아요.')
(2.5, 3.3, 5.6)

이제 zip 함수를 이용하여 두 개의 리스트를 짝지은 다음에 dict 함수를 이용하여 사전자료형을 생성할 수 있다. 결과적으로 zip 함수의 첫째 인자 항목들을 키로, 둘째 인자 항목들을 값으로 사용된다.

In [38]:
surferInfoDict = dict(zip(infoItems, surferInfoList))

for info in surferInfoDict:    
    print(f"{info}:\t{surferInfoDict[info]}")
번호:	101
이름:	대파도
기록:	8.32
보드유형:	Fish

예제: CSV 파일 다루기

csv는 쉼표로 구분된 값(comma-separated values)들을 가리키며, csv 파일은 csv 데이터셋을 저장한 파일이다. 쉼표 대신에 세미콜론(;)이 사용되기도 한다.

예를 들어, surfing_data.csv 파일에 서핑 대회에 참가한 다섯 명 선수의 기록이 다음과 같이 포함되어 있다.

101;대파도;8.32;Fish
102;파도왕;9.01;Gun
103;웨이브킹;8.85;Cruizer
104;킹크랩;8.91;Malibu
105;대마왕;8.65;Fish

이제, 다음 기능을 수행하는 함수 findInfo를 구현해보자.

  • 선수의 번호를 인자로 받아서, 해당 선수의 정보를 위에서처럼 번호, 이름, 기록, 보드유형으로 구분된 항목으로 구성된 사전 자료형을 리턴값으로 내준다.
  • 선수의 번호가 포함되어 있지 않으면 빈 사전을 리턴값으로 내준다.

여기서는 surfing_data.csv 파일이 data 하위폴더에 저장되어 있다고 가정한다. 위 파일을 다운로드하여 저장하기 위해 데이터셋 다루기에서 사용한 myWget 함수를 이용할 수 있다.

파일 내용 확인

파일을 불러와서 각각의 줄을 출력하여 내용을 확인해보자.

In [39]:
try:
    resultsData = open('./data/surfing_data.csv', 'r') 
except FileNotFoundError:
    print("열고자 하는 파일이 존재하지 않습니다.")

for line in resultsData: 
    print(line.strip())

resultsData.close() 
101;대파도;8.32;Fish
102;파도왕;9.01;Gun
103;웨이브킹;8.85;Cruizer
104;킹크랩;8.91;Malibu
105;대마왕;8.65;Fish

csv를 리스트로 전환

각각의 줄이 세미콜론으로 번호, 이름, 기록, 보드유형 관련 값들이 구분되어 있다. 따라서 세미콜론 기준으로 값들을 분리한 후에 앞서 사용한 방식대로 각 줄을 하나의 사전 자료형으로 변환시킨다.

먼저 세미콜론 기준으로 쪼개보자. strip 메서드를 이용하여 줄바꿈 기호를 없앤 후에 쪼개야 한다.

In [40]:
try:
    resultsData = open('./data/surfing_data.csv', 'r') 
except FileNotFoundError:
    print("열고자 하는 파일이 존재하지 않습니다.")

for line in resultsData: 
    print(line.strip('').split(';'))

resultsData.close() 
['101', '대파도', '8.32', 'Fish\n']
['102', '파도왕', '9.01', 'Gun\n']
['103', '웨이브킹', '8.85', 'Cruizer\n']
['104', '킹크랩', '8.91', 'Malibu\n']
['105', '대마왕', '8.65', 'Fish\n']

csv를 사전으로 전환

이제 앞서 사용한 방식으로 각 선수의 정보를 사전 형식으로 저장한다. 이후, 해당 선수의 번호를 키로 하고, 생성된 사전을 값으로 하여 모든 선수의 정보를 저장하는 사전 surferInfos을 생성한다.

In [41]:
try:
    resultsData = open('./data/surfing_data.csv', 'r') 
except FileNotFoundError:
    print("열고자 하는 파일이 존재하지 않습니다.")

surferInfos = dict()
    
infoItems = ['번호', '이름', '기록', '보드유형']

for line in resultsData: 
    surferInfoList = line.strip().split(';')
    surferInfoDict = dict(zip(infoItems, surferInfoList))
    surferNumber = surferInfoList[0]
    surferInfos[surferNumber] = surferInfoDict

resultsData.close() 

이제 surferInfos의 내용을 확인해보자.

In [42]:
for key in surferInfos:
    print(f"{key}번 선수 정보: {surferInfos[key]}")
101번 선수 정보: {'번호': '101', '이름': '대파도', '기록': '8.32', '보드유형': 'Fish'}
102번 선수 정보: {'번호': '102', '이름': '파도왕', '기록': '9.01', '보드유형': 'Gun'}
103번 선수 정보: {'번호': '103', '이름': '웨이브킹', '기록': '8.85', '보드유형': 'Cruizer'}
104번 선수 정보: {'번호': '104', '이름': '킹크랩', '기록': '8.91', '보드유형': 'Malibu'}
105번 선수 정보: {'번호': '105', '이름': '대마왕', '기록': '8.65', '보드유형': 'Fish'}

findInfo 함수 구현

지금까지 확인한 것들을 정리하여 findInfo 함수를 다음처럼 구현할 수 있다. 선수 번호를 이용하여 surferInfos가 가리키는 사전에서 해당 번호의 키값을 확인하면 된다. 단, 키가 문자열임에 주의해야 한다.

In [43]:
def findInfo(num):
    try:
        resultsData = open('./data/surfing_data.csv', 'r') 
    except FileNotFoundError:
        print("열고자 하는 파일이 존재하지 않습니다.")

    surferInfos = dict()                                       # 모든 선수들의 정보 저장

    infoItems = ['번호', '이름', '기록', '보드유형']

    for line in resultsData: 
        surferInfoList = line.strip().split(';')
        surferInfoDict = dict(zip(infoItems, surferInfoList))  # 각 선수의 정보 사전
        surferNumber = surferInfoList[0]                       # 선수 번호를 키로 사용
        surferInfos[surferNumber] = surferInfoDict             # 선수 정보를 키값으로 사용

    resultsData.close()
    
    return surferInfos[str(num)]                               # 키가 문자열이다.
In [44]:
findInfo(101)
Out[44]:
{'번호': '101', '이름': '대파도', '기록': '8.32', '보드유형': 'Fish'}
In [45]:
findInfo(105)
Out[45]:
{'번호': '105', '이름': '대마왕', '기록': '8.65', '보드유형': 'Fish'}

그런데 해당 선수의 번호가 존재하지 않을 경우 오류가 발생한다.

In [46]:
findInfo(107)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-46-8d3cd4b54b8d> in <module>
----> 1 findInfo(107)

<ipython-input-43-25f238ce35d3> in findInfo(num)
     17     resultsData.close()
     18 
---> 19     return surferInfos[str(num)]                               # 키가 문자열이다.

KeyError: '107'

이럴 때는 대괄호 연산자 보다 get이라는 사전 자료형의 메서드를 활용하면 된다.

In [47]:
def findInfo(num):
    try:
        resultsData = open('./data/surfing_data.csv', 'r') 
    except FileNotFoundError:
        print("열고자 하는 파일이 존재하지 않습니다.")

    surferInfos = dict()

    infoItems = ['번호', '이름', '기록', '보드유형']

    for line in resultsData: 
        surferInfoList = line.strip().split(';')
        surferInfoDict = dict(zip(infoItems, surferInfoList))
        surferNumber = surferInfoList[0]                     
        surferInfos[surferNumber] = surferInfoDict           

    resultsData.close()
    
    return surferInfos.get(str(num), dict())                   # 정보 없으면 빈 사전이 리턴값

선수 번호가 존재하면 정보를 내준다.

In [48]:
findInfo(101)
Out[48]:
{'번호': '101', '이름': '대파도', '기록': '8.32', '보드유형': 'Fish'}
In [49]:
findInfo(105)
Out[49]:
{'번호': '105', '이름': '대마왕', '기록': '8.65', '보드유형': 'Fish'}

해당 선수의 번호가 존재하지 않을 경우 빈 사전을 내준다.

In [50]:
findInfo(107)
Out[50]:
{}

연습문제

  1. enumerate 함수 대신에 리스트 자료형의 index 메서드를 활용하여 5m 다이빙 선수들의 기록을 등수와 함께 순서대로 출력하는 프로그램을 구현하라.

  2. 데이터셋 다루기에서 정의한 ranking 함수를 수정하여 다음 조건을 만족하는 함수 rankingDict를 구현하라. 즉, rankingDict(n, fileName)를 실행하면 지정된 경기기록 파일에서 n등 점수와 해당 선수의 이름을 아래 형식으로 확인해 주어야 한다.

    n등: OOO 선수의 점수는 x.xx입니다.

  3. 아래 내용을 run1000.txt 파일에 저장하라.

     달려라 하니,2-54,3:21,2.34,2.45,3.01,2:01
     날쌘 돌이,2.59,2.11,2:39,2:23,3-10,3-23
     날아라 번개,3-43,3.99,4:11,2-72,3:91,3.31

    파일에 저장된 내용은 각각 하니,돌이, 번개의 1,000m 달리기 기록이다.

    1. 달리기 기록 표기가 일정하지 않다. 3:21처럼 콜론을 사용하는 방식으로 코드를 정리하는 함수 sanitize를 구현하라.
       sanitize('3-21') = '3:21'
       sanitize('3.21') = '3:21'
       sanitize('3:21') = '3:21'
      힌트: split 메서드 활용

    2. 선수 이름을 인자로 입력하면 해당 선수의 1,000m 기록중에서 가장 빠른 기록 세 개를 리턴값으로 내주는 함수 bestRecords3를 사전 자료형을 이용하여 구현하라.
       bestRecords3('하니') = ['2:01', '2:34', '2:45']
       bestRecords3('돌이') = ['2:11', '2:39', '2:59']
       bestRecords3('번개') = ['2:72', '3:31', '3:43']