2013년 8월 28일 수요일

Python Dictionaries

Python Dictionaries

 여기에서는 hash table 사용하여 구현된 연관 배열(associative array) Python dictionary 다룰 것이다. Dictionary 단순한program들에서도 사용이 가능할 정도로 정말 유용하다.
 대다수의 programmer들에게 dictionary list string 같은 기본 data structure들보다는 친숙하지는 않으므로 dictionary 사용에 대한 몇몇 예제들은 다른 built-in data structure들보다는   복잡할  있다.

Dictionary 무엇인가?
 만약 당신이 다른 language들의 연관 배열(associative array) hash table 사용해본 적이 없다면 list dictionary 비교를 통해dictionary 사용법을 이해하는 것이 좋을 것이다.

  list 값들의 위치를 정수로 나타내는 index 통해서 값들에 접근할  있다.
  dictionary 값들의 위치를 정수나 문자열이나 다른 Python object들로 나타내는 key 통해서 값들에 접근할  있다바꿔 말하면list dictionary 모두 임의의 값에 색인을 통해 접근할  있지만 dictionary index 사용될  있는 항목들이 list index 사용될 있는 항목들보다  많은 것들을 포함한다물론 dictionary index 접근을 제공하기 위해 사용하는 mechanism list에서 사용되는 것과 상당히 다르다.
  list dictionary 모두 어떤 type object라도 저장할  있다.
  list 저장되는 값들은 연속하는 정수로  index 접근하므로 무조건 list 내의 위치로 정렬된다. list에서는  정렬에 대해서 주의를기울일 필요가 있다. dictionary 저장되는 값들은 key들에 다양한 object들을 사용하므로 각각의 값들이 정렬될 필요는 없다. dictionary 사용한다면 list 같은 다른 data structure 사용하여 dictionary 내의 item들의 정렬을 정의할  있다는 것을 주의하기바란다하지만 dictionary 내장(built-in) 정렬을 가지지 않는다는 사실에는 변함이 없다.

 list dictionary 차이들에도 불구하고 이들의 사용법은 종종 비슷하게 보여진다일단  dictionary 생성하는 것으로 부터 시작할 것이다. list 대괄호[] 사용하지만 dictionary 중괄호{} 사용한다.

>>> x = []
>>> y = {}

 위의 예제에서는  line에서  list  생성하여 x 할당하였으며 번째 line에서는  dictionary 생성하여 y 할당하였다.
 dictionary 생성한 후에 list 마찬가지로 값들을 저항할  있다.

>>> y[0] = 'Hello'
>>> y[1] = 'Goodbye'

 위와 같은 할당 방식에 대해서도 dictionary list 사이에서 상당한 차이가 존재한다. list에서 위와 동일한 방식으로 할당을 시도하면error 발생할 것이다이는 Python list 내에서 값이 아직 존재하지 않는 위치에 할당을 하는 것은 허용하지 않기 때문이다예를 들어list x 0 번째 항목에 할당을 하려고 하면 error 발생되는 것을   있다.

>>> x[0] = 'Hello'
Traceback (innermost last):
    File "<stdin>", line 1, in ?
IndexError: list assignment index out of range

 dictionary에서는 새로운 위치를 필요에 따라 생성되므로 위와 같은 문제는 발생하지 않는다.
 위의 dictionary 내에 저장된 값들은 이제 접근할  있으며 활용도 가능하다.

>>> print(y[0])
Hello
>>> y[1] + ", Friend."
'Goodbye, Friend.'

 위에서 dictionary list처럼 사용할  있다는 것을 보았다이제는   차이점을 알아보기 위해서 정수가 아닌 key들에  가지 값들을저장하고 사용해볼 것이다.

>>> y["two"] = 2
>>> y["pi"] = 3.14
>>> y["two"] * y["pi"]
6.2800000000000002

 위의 예제는 list dictionary 확실한 차이점을 보여주고 있다. list index 반드시 정수여야 하지만 dictionary key 숫자문자열이나 다른 Python object들을 사용할  있어서 제한이 거의 없다 특징은 dictionary list에서는   없는 것들을   있게 해준다예를 들면 dictionary 이용하여 전화번호부 application 구현할  있다이는 전화번호가 사람 이름의 성으로 index되어 저장되므로dictionary 적합한 data structure 제공하기 때문이다.

dictionary  사전이라고 부르는가?
 dictionary 임의의 object 임의의 object mapping하는 방식이다실제 사전유의어 사전또는 번역서들은 좋은 실세계의 사례들이다 관련성이 얼마나 현실적인지 보기 위해서 English-to-French 색상 번역을 예제로 들어본다.

>>> english_to_french = {}    #  dictionary 생성
>>> english_to_french['red'] = 'rouge
>>> english_to_french['blue'] = 'bleu'
>>> english_to_french['green'] = 'vert'    # 생성된 dictionary  단어를 저장
>>> print("red is", english_to_french['red'])    #'red' 해당되는 값을 출력
red is rouge

다른 dictionary 연산들
 기본 항목 할당과 접근 외에 dictionary 많은 연산에 대해서 지원하고 있다당신은 쉼표들로 구분된 일련의 key 값들로 dictionary 명확하게 정의할  있다.

>>> english_to_french = {'red': 'rouge', 'blue': 'bleu', 'green': 'vert'}

 len dictionary 항목들의 수를 반환한다.

>>> len(english_to_french)
3

 keys method 사용하여 dictionary 모든 key들을 구할  있다이는 dictionary 내용들을 순회(iterate)  있게하여 Python for loop에서 사용될  자주 이용한다.

>>> list(english_to_french.keys())
['green', 'blue', 'red']

 keys 의해서 반환된 list 내의 key들의 순서는 아무런 의미를 가지지 않는다이들은 정렬할 필요가 없으며생성된 순서대로 발생될 필요도 없다당신의 Python에서 실행되면 여기에서 출력된 순서와 다르게 key들이 출력될 것이다만약 key들을 정렬하려고 한다면 이들을 list변수 내에 저장하고 해당 list 정렬하는 방식으로 결과를 얻을  있다.
 values 사용하여 dictionary 내에 저장된 모든 값들을 구할 수도 있다.

>>> list(english_to_french.values())
['vert', 'bleu', 'rouge']

  method keys 만큼은 자주 사용되지는 않고 있다.
 items method 사용하면 모든 key들과 연관된 값들을 tuple들의 순서열(sequence) 반환할  있다.

>>> list(english_to_french.items())
[('green', 'vert'), ('blue', 'bleu'), ('red', 'rouge')]

Dictionary view object
 keys, values, items method들은 list 아니라 순서열(sequence)처럼 동작하고 dictionary 변경될 때마다 update되는 view 반환한다이런 이유로 인해서 여기의 예제들에서 list 출력하기 위해서 list 함수를 사용하는 것이다반면에 순서열(sequence)처럼 동작한다는 것은 for loop 내에서 순회(iterate)하도록 in 다음에 사용하여 code 작성할  있게 해준다.
 keys items 의해서 반환되는 view 집합(set)처럼 동작하여 합집합(union), 차집합(difference), 교집합(intersection) 연산들을 사용할 수도 있다.

 keys 마찬가지로 items method dictionary 내용들을 순회(iterate)  있게 하여 for loop 함께 자주 사용된다.
 del 구문은 dictionary에서 항목(key/value ) 제거하는데 사용될  있다.

>>> list(english_to_french.items())
[('green', 'vert'), ('blue', 'bleu'), ('red', 'rouge')]
>>> del english_to_french['green']
>>> list(english_to_french.items())
[('blue', 'bleu'), ('red', 'rouge')]

 dictionary 내에 있지 않은 key 접근을 시도하면 Python에서 error 발생 시킨다이를 처리하는데 in 예약어(keyword) 사용하여 해당key 존재하는지 dictionary 시험할  있다해당 key 저장된 value dictionary 존재한다면 True 반환하고 존재하지 않는다면False 반환한다.

>>> 'red' in english_to_french
True
>>> 'orange' in english_to_french
False

 이를 대신하여 get 함수를 사용할 수도 있다 함수는 dictionary 해당 key 포함하고 있다면 key 연결된 value 반환하지만, dictionary 해당 key 포함하고 있지 않다면  번째 argument 반환한다.

>>> print(english_to_french.get('blue', 'No translation'))
bleu
>>> print(english_to_french.get('chartreuse', 'No translation'))
No translation

  번째 argument option이며 없을 경우에는 dictionary에서 해당 key 포함하지 않는다면 get None 반환한다.
 setdefault method get 함수와 유사하게 동작하지만 dictionary 내에 해당 key 없다면 기본값으로 key value 지정한다.

>>> print(english_to_french.setdefault('chartreuse', 'No translation'))
No translation

 위의 예제에서 get setdefault 차이점은 setdefault 호출한 후에 dictionary 내에서 chartreuse key No translation value 쌍이 남는다는 것이다.
 dictionary 복사본은 copy method 사용하여 획득할  있다.

>>> x = {0: 'zero', 1: 'one'}
>>> y = x.copy()
>>> y
{0: 'zero', 1: 'one'}

 copy method 얕은 복사로 복사본을 생성한다대부분의 상황에서  방식을 사용하게  것이다수정될  있는 object들을 value들로포함하는 dictionary에서 copy.deepcopy 함수를 사용하여 깊은 복사를 만들 수도 있다.
 update method  번째 dictionary  번째 dictionary 모든 key/value 쌍으로 갱신한다 dictionary에서 공통으로 가지고 있는key들의 value  번째 dictionary value들이  번째 dictionary 값들에 덮어 써진다.

>>> z = {1: 'One', 2: 'Two'}
>>> x = {0: 'zero', 1: 'one'}
>>> x.update(z)
>>> x
{0: 'zero', 1: 'One', 2: 'Two'}

 dictionary method dictionary들을 조작하고 사용할  있는 완전한 tool들을 제공한다다음 표는 주요 dictionary 함수들을 정리한 것이다.

 dictionary 연산자들
Dictionary 연산자
설명
예제
{}
 dictionary 생성한다.
x = {}
len
dictionary  항목들의 숫자를 반환한다.
len(x)
keys
dictionary  모든 key들의 view 반환한다.
x.keys()
values
dictionary  모든 value들의 view 반환한다.
x.values()
items
dictionary  모든 항목들의 view 반환한다.
x.items()
del
dictionary에서 하나의 항목을 제거한다.
del x[key]
in
해당 key dictionary 내에 존재하는지 test한다.
'y' in x
get
해당 key value 설정할  있는 기본값을 반환한다.
x.get('y',None)
setdefault
dictionary 내에 key 존재한다면 value 반환하고, key 존재하지 않는다면 해당 key value 기본값으로지정하고 value 반환한다.
x.setdefault('y', None)
copy
dictionary 복사본을 생성한다.
y = x.copy()
update
 dictionary들의 항목들을 합친다.
x.update(z)

 위의 표는 전체 dictionary 연산을 포함하고 있지는 않다완전한 list 공식 Python documentation 참조하라.

Word counting
  line  하나의 단어로  list 포함하는 file 있다고 가정해보자 file에서  단어가 얼마만큼 반복되는지 알아내려고  dictionary 사용하면 쉽게 알아낼  있다.

>>> sample_string = "To be or not to be"
>>> occurrences = {}
>>>for word in sample_string.split():
...    occurrences[word] = occurrences.get(word, 0) + 1
...
>>>for word in occurrences:
...    print("The word", word, "occurs", occurrences[word], \
...           "times in the string")
...

 위의 예제에서는  단어에 대해서 occurrences count 증가시켰다 예제는 dictionary 강력함을 보여주는 좋은 예제이다code 단순할 뿐만 아니라 꽤나 빠르기도 하다이는 dictionary 연산들이 Python에서 꽤나 최적화 되었기 때문이다.

key 무엇이 사용될  있는가?
 위의 예제들은 key 문자열(string) 사용하고 있지만 Python  방식으로 문자열(string) 사용하는  외에도  많은 방식을 허용하고 있다수정을   없거나 (immutable) hashable Python object 모두 dictionary key 사용이 가능하다.
 Python에서 수정할  있는 object mutable이라고 부른다항목들을 추가변경제거가 가능한 list mutable하다. dictionary 동일하게 동작하므로 mutable하다숫자는 수정을   없다.(immutable) 만약 숫자값 3 가진 변수 x 4 할당하여 x 값을 변경하였다고하더라도 숫자 3 변경한 것은 아니다. 3 여전히 3이다문자열(string) 수정을   없다.(immutable) list[n] list n 번째 항목을 반환하고, string[n] 문자열에서 n 번째 문자를 반환한다그리고 list[n] = value list n 번째 항목을 변경하지만 string[n] = characterPython에서는 허용되지 않으므로 error 발생시킨다.
 불행하게도 key 수정을   없거나 (immutable) hashable해야한다는 요구사항은 list dictionary key 사용할  없다는 것을 의미한다하지만 key list 유사한 key 가지는 것이 편의상 좋은 상황이라면 여기에 사용될  있는 많은 instance들이 존재한다예를 들면성과 이름으로 구성된 key 사람의 정보를 저장하는 것에 대해서는  항목으로  list key 사용할  있다면 편의상 좋을 것이다.
 Python tuple 제공하는 것으로 이에 대한 해법을 제시하고 있다. tuple list 유사하게 생성하거나 사용되지만 수정할  없으므로제약사항에 부합하면서 list처럼 사용이 가능하다하지만 여기에는 key들은 hashable하기도 해야한다는 제약사항이  가지  존재한다. hashable하기 위해서는 value 해당 value 유효한 동안에는 절대 변하지 않는 (__hash__ method 의해서 제공되는) hash value이어야 한다이는 수정될  있는(mutable) 값들을 포함하는 tuple 자체로는 수정을   없지만 (immutable) hashable  것은 아니다수정가능한 (mutable) object들을 포함하지 않는 tuple들만 hashable하며 dictionary key로도 사용될  있다다음 표는 어떤 Python 내장type들이 수정 불가능한지 (immutable), hashable한지, dictionary key   있는 지를 정리하여 설명하고 있다.

 dictionary key 사용될  있는 Python value
Python type
수정 불가능(Immutable)?
Hashable?
Dictionary key?
int
Yes
Yes
Yes
float
Yes
Yes
Yes
boolean
Yes
Yes
Yes
complex
Yes
Yes
Yes
str
Yes
Yes
Yes
bytes
Yes
Yes
Yes
bytearray
No
No
No
list
No
No
No
tuple
Yes
Sometimes
Sometimes
set
No
No
No
frozenset
Yes
Yes
Yes
dictionary
No
No
No

 다음으로는 tuple dictionary 같이 사용하는 방법에 대해서 설명을  것이다.

Sparse matrix
 수학 용어로 행렬(matrix) 숫자들로 이루어진 이차원 grid 이다보통 다음과 같이 양쪽에 꺾쇠괄호([]) grid 표시한 형태의textbook으로 작성된다.

 행렬(matrix) 표현하는 표준 방식은 list들의 list 사용하는 것이다. Python에서는 다음과 같이 표현할  있다.

matrix = [[3, 0, -2, 11], [0, 9, 0, 0], [0, 7, 0, 0], [0, 0, 0, -5]]

 행렬(matrix) 내의 항목들은 (column) (row) 숫자로 접근할  있다.

element = matrix[rownum][colnum]

 하지만 일기 예보와 같은 application에서는  면에  천개의 항목이 존재하며 전체적으로는 100 단위의 항목으로 구성되는 매우  행렬이 사용되는 것이 일반적이다또한 zero 값을 많이 포함하는 행렬도 일반적인 형태이다적은 percentage 행렬의 항목들을 zero 지정되어야 하는 application 있다. memory 절약을 위해 이와 같은 행렬(matrix)들은 zero 값이 아닌 항목만 저장되도록 하는 것이 일반적이다이와 같은 표현법을 sparse matrix라고 부른다.
 tuple index들을 가진 dictionary들을 사용하여 sparse matrix들을 구현하는 것은 단순하다예를 들면 이전의 sparse matrix 다음과 같이표현될  있다.

matrix = {(0, 0): 3, (0, 2): -2, (0, 3): 11, (1, 1): 9, (2, 1): 7, (3, 3): -5}

 이제 당신은 다음 code 같이 (column) (row) 숫자를 사용하여 개별적인 행렬(matrix) 항목에 접근할  있을 것이다.

if (rownum, colnum) in matrix:
    element = matrix[(rownum, colnum)]
else:
    element = 0

 이를 수행하기 위해서  명확하지만 ( 효율적인방식은 dictionary get method 사용하는 것이다이를 사용하면 dictionary 내에서key 찾지 못했다면 0 반환하고 key 찾으면 key 대한 값을 반환하도록   있다이는 dictionary 검색들 중에 하나를 방지한다.

element = matrix.get((rownum, colnum), 0)

 만약 당신이 광범위한 행렬(matrix) 연산을 해야한다면 숫자 연산 package NumPy 사용을 고려해보는 것을 권장한다.

cache로써 dictionary
 다음은 dictionary 결과를 저장하여 결과를 계속 재연산하는 것을 방지하는 data structure 이용하여 cache 사용될  있게 해주는예제이다여기 sole라는 함수는  개의 정수들을 argument들로 받아서 결과로 반환한다.

def sole(m, n, t):
    # . . . do some time-consuming calculations . . .
    return(result)

 만약  함수는 시간을 소비하므로 일만번 가량 호출한다면 program 매우 느리게 동작되는 문제점을 가지고 있다.
 하지만 어떤 program 동작하는 동안에 200 가지의 조합들로만 sole 호출된다고 가정해보자어떤 program 실행되는 동안 sole(12, 20, 6) 형식으로 50 이상 호출을 한다동일한 argument들의 sole 재연산을 제거하기 위해서 다음과 같이 tuple들을 key들로 사용한dictionary 사용하였다.

sole_cache = {}
def sole(m, n, t):
    if (m, n, t) in sole_cache:
        return sole_cache[(m, n, t)]
    else:
        # . . . do some time-consuming calculations . . .
        sole_cache[(m, n, t)] = result
        return result

 재작성된 sole 함수는 기존의 결과들을 저장하기 위해서 전역 변수를 사용한다전역 변수는 dictionary이며 key들은 기존의 sole에서 사용된 argument 조합들로  tuple들을 사용한다이후에는 한번 연산된 결과의 조합으로 sole argument들을 전달하여 호출하면재연산하지 않고 저장된 결과를 반환한다.

dictionary 효율성
 만약 당신이 전통적인 compiled-language 사용한 경험이 있다면 dictionary list(array)보다 효율적이지 않다는 추측으로 사용을 꺼려할 수도 있다하지만 Python dictionary implementation 상당히 빠른 것이 진실이다많은 내부 language 기능들이 dictionary 의존하고있으며이들을 요휼적으로 동작하게 하기위한 많은 최적화 작업들이 수행되어져 오고 있다모든 Python data structure들이 상당히 최적화되어 있으므로 어느 것이  빠르고 효율적인지 고민을  필요는 없다만약 list 사용할 때보다 dictionary 사용할 때에 문제가 명확하고 쉽게 해결된다면 해당 방식을 사용하라. dictionary 수용할  없는 지연을 초래하는 것이 명확할 때만 다른 방식을 고려하는 것이 좋다.

요약
 dictionary 많은 목적으로 사용되며 Python 자체 내에서도 사용되는 기초적이고 강력한 Python data structure이다수정 불가능한(immutable) object key 사용하여 해당값을 검색하는 능력은 dictionary data collection  적은 code 처리하고 다른 solution들보다  직접적으로 접근할  있게 만들어 준다.