스터디/파이썬 스터디 강의자료

[3팀/김경은] 7주차 파이썬 스터디 - 자료구조

경은 2023. 5. 10. 01:01

데이터 과학을 위한 파이썬 프로그래밍 교재를 사용하여 작성한 강의자료입니다.

7차시_자료구조_강의안.pdf
1.18MB
7차시_자료구조_과제.pdf
2.89MB

 

자료구조의 개념

다양한 형태 데이터를 저장하여 처리하는 경우

데이터 저장 사례 : 전화번호부

  • 과거 : “Yellow Page”라는 두꺼운 전화번호부에서 전화번호 검색
  • 현재는 전화번호부를 사용하는 일이 없지만 전화번호부에서 전화번호를 효율적으로 찾기 위해서 이름을 기준으로 가나다 순서대로 저장되어 있는 방식이 지금도 사용된다.
  • 데이터 특징을 고려하여 저장하는 방법을 자료구조라고 함

실생활에서 데이터의 특징을 반영하여 저장해야 할 정보

  • 은행의 번호표는 번호표 단말기에서 사용자가 번호표를 하나씩 뽑으면 대기 인원이 1씩 증가하고, 해당 사용자가 은행 서비스 이용을 종료하면 1씩 감소하기 때문에 번호표의 번호 정보와 현재 대기 인원을 모두 관리해야 효율적으로 데이터 관리 가능
  • 택배 수화물을 트럭에 쌓을 때 위치 정보를 어떻게 저장할지에 대한 것
  • 나중에 배달하는 수화물일수록 트럭 안쪽에 쌓고, 먼저 배달하는 수화물일수록 트럭 입구쪽에 쌓는 것이 좋음

💡 자료구조는 특징이 있는 정보를 메모리에 효율적으로 저장 및 반환하는 방법으로 데이터를 관리하는 방식이다. 특히 대용량일수록 메모리에 빨리 저장하고 검색함으로써 메모리를 효율적으로 사용해야 실행 시간을 줄일 수 있다.

 

파이썬에서의 자료구조

자료구조명             특징

스택(stack) 나중에 들어온 값이 먼저 나갈 수 있도록 해주는 자료구조(last in first out)
큐(queue) 먼저 들어온 값이 먼저 나갈 수 있도록 해주는 자료구조(first in first out)
튜플(tuple) 리스트와 같지만 데이터의 변경을 허용하지 않는 자료구조
세트(set) 데이터의 중복을 허용하지 않고, 수학의 집합 연산을 지원하는 자료구조
딕셔너리 (dictionary) 전화번호부와 같이 키(key)와 값(value)의 형태로 데이터를 저장하는 자료구조이며 여기서 키값 은 다른 데이터와 중복을 허용하지 않음
collections 모듈 위에 열거된 여러 자료구조를 효율적으로 사용할 수 있도록 지원하는 파이썬 내장(built-in) 모듈

 

스택

  • 스택 (stack) 은 컴퓨터공학과 학생들이 전공을 시작하면서 처음 배우는 자료구조로, 자료구조의 핵심 개념 중 하나이다.
  • 스택을 간단히 표현하면 'Last In First Out'으로 정의할 수 있다. 즉, 마지막에 들어간 데이터가 가장 먼저 나오는 형태로 데이터의 저장 공간을 구현하는 것이다.
  • 일반적으로 스택이라고 하면 밑의 사진과 같이 사각형의 저장 공간을 뜻한다. 즉 4.10과 같은 데이터를 저장하는 공간으로 리스트와 비슷하지만 저장 순서가 바뀌는 형태를 스택 자료구조 (stack data sructure) 라고 한다.
  • 스택에 데이터를 저장하는 것을 푸시 push, 데이터를 추출하는 것을 팝(pop)이라고 한다.

스택은 어떤 상황에서 사용할 수 있을까?

  • 택배 수화물을 저장하는 방식 : 먼저 배달해야 하는 수화물은 트럭의 입구쪽에, 나중에 배달해야 하는 수화물은 트럭의 안쪽에 넣어야 한다.
  • 수화물을 하나의 데이터로 본다면 먼저 들어간 수화물보다 나중에 들어간 수화물이 먼저 나와야 하는 경우이다. 수화물과 비슷한 개념의 데이터가 있다면 이러한 방식으로 저장했을 때 좀 더 쉽게 데이터를 추출할 수 있다.

파이썬에서의 리스트를 사용한 스택 구현

  • 리스트로 저장 공간을 만든 후 append() 함수로 데이터를 저장 (push) 하고 pop() 함수로 데이터를 추출 (pop) 한다.
>>> a = [1,2,3,4,5]
>>> a.append (10)
>>> a
[1, 2, 3, 4, 5, 10]
>>> a.append(20)
>>> a
[1, 2, 3, 4, 5, 10, 20]
>>> a.рор()
20
>>> a.pop()
10
  1. 변수 a에 [1, 2, 3, 4, 5]가 할당된다.
  2. 변수 a에 10과 20을 추가하면 변수 a에는 [1, 2, 3, 4, 5, 10, 20]이 할당된다.
  3. 다음으로 pop() 함수를 사용하면 가장 마지막에 저장된 값을 추출하고 동시에 리스트에서 삭제시킨다. 즉, pop() 함수를 처음 실행하면 가장 마지막에 저장된 20이 추출되면서 화면에 출력되고, 동시에 변수 a의 값은 [1, 2, 3, 4, 5,10] 으로 바뀐다.
  4. 다시 pop() 함수를 실행하면 마지막에 저장된 10이 추출되면서 화면에 출력되고, 동시에 변수 a의 값은 [1, 2, 3, 4, 5]로 바뀐다.

[참고]

사실 파이썬에서는 훨씬 더 효율적이고 강력한 스택 라이브러리를 제공한다. collections라는 모듈이 있는데 이 모듈에서 deque를 통해 조금 더 빠르게 스택이라는 자료구조를 구현할 수 있다.

 

스택으로 만들 수 있는 프로그램

  • 입력한 텍스트를 역순으로 추출하는 프로그램
word = input("Input a word: ")
world_list = list(word)
print(world_list)

result = []
for _ in range(len(world_list)):
    result.append(world_list.pop())

print (result)
print(word[::-1])

정답

Input a word: PYTHON           * 사용자 입력(PYTHON)
['P', 'Y', 'T', 'H', 'O', 'N']
['N', 'O', 'H', 'T', 'Y', 'P']
NOHTYP
  1. 입력한 텍스트는 변수 word에 저장되고 그 값을 리스트형으로 변환한다.
  2. 그 후 값을 차례대로 추출하면 입력한 텍스트의 역순값이 출력된다.파이썬에서 굉장히 많이 쓰이는 코드 중 하나이다.
  3. 일반적으로 for문에서 많이 쓰이는데, for문에 _ 기호가 있으면 해당 반복문에서 생성되는 값은 코드에서 사용하지 않는다는 뜻이다. 위의 코드에서는 6행의 range(len (world_List))에서 생성되는 값을 반복문 내에서 사용하지 않으므로 할당받은 것이다.

  • 스택의 반대 개념
  • 큐qucuc는 스택과 다르게 먼저 들어간 데이터가 먼저 나오는 'Fist in First Out'의 메모리 구조를 가지는 자료구조이다.

  • 앞에서 말한것처럼 은행에서 대기 번호표를 뽑을 때 번호를 저장하는 방식이 대표적인 사례이다. 먼저 온 사람이 앞의 번호표를 뽑고, 번호가 빠 른 사람이 먼저 서비스를 받는 구조이다.

메모리 개념

  • 메모리 개념으로 볼 때 큐는 스택보다 구현이 조금 더 복잡하다.
  • 스택은 메모리가 시작하는 지점이 고정되어 있지만. 큐는 처음에 값이 저장되는 메모리 주소가 값이 사용됨에 따라 계속 바뀌게 되어 구현에 좀 더 신경을 써야 한다. 파이썬에서는 이러한 부분이 자동으로 구현되므로 어렵지 않게 사용할 수 있다.
  • 파이썬에서 큐를 구현하는 방법은 기본적으로 스택의 구현과 같은데, pop() 함수를 사용할 때 인덱스가 0번째인 값을 쓴다는 의미로 pop(0)을 사용하면 된다. 즉, pop() 함수가 리스트의 마지막 값을 가져온다고 했을 때 pop(0)은 맨 처음 값을 가져온다는 뜻이다.
>>> a=[1,2,3,4,5]
>>> a.append (10)         # a = [1, 2, 3, 4, 5, 10]
>>> a.append (20)         # a = [1, 2, 3, 4, 5, 10, 20]
›>> a.рор(0)
1
>>> a.pop (0)
2

+디큐

큐와 스택이 혼합된 개념이다.

앞, 뒤 양방향에서 push와 pop연산이 가능하다.

가장 먼저 넣은 자료부터 꺼낼 수도 있고 가장 마지막에 넣은 자료부터 꺼낼 수도 있는 방식이다.

 

튜플

  • 리스트와 같은 개념이지만 값을 변경하는 것이 불가능한 리스트
>>> t= (1,2,3)
>>> print(t + t , t * 2)
(1, 2, 3, 1, 2,3) (1, 2, 3, 1, 2, 3)
›>> len(t)
3
  • 첫 번째 줄이 튜플을 선언하는 명령으로 튜플은 괄호를 이용하여 t= (1, 2, 3)과 같은 형태 로 선언한다.
  • 대괄호 []를 이용하는 리스트와는 차이가 있다. 하지만 선언 외에 여러 가지 연 산은 리스트와 같은 방식으로 리스트에서 사용하는 연산, 인덱싱, 슬라이싱이 모두 동일하게 적용된다.
  • 튜플 간의 덧셈 t + t 나 곱셈 t * 2, 그리고 len() 함수와 같이 리스트형 데이터에 사용하는 모든 함수를 사용할 수 있다.
  • 튜플과 리스트의 차이점이 있다면, 튜플의 값은 마음대로 변경할 수 없다는 것이다. 만약 튜플의 값을 변경하고자 한다면 다음과 같은 오류가 발생한다.
>>> t[1]=5
Traceback (most recent call last):
    File "stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

TypeError: 'tuple' object does not support item assignment

→ 튜플 오브젝트(uple' obiect)에는 새로운 아이템(item)의 할 당을 허용하지 않는다.' 라는 내용

 

만약, 값이 하나일때의 튜플 선언 방법

t= (1)과 같은 코드가 생각날 수 있지만 파이썬은 이렇게 선언할 경우 1 = 1로 이해한다. 인터프린터가 그렇게 설계 되어 있기 때문에 t= (5 + 2) * 2와 같이 연산에서 사용하는 괄호로 이해하는 것이다. 따라서 값이 하나일 때는 t= (1. )와 같이 반드시 콤마(.)를 붙여 t가 튜플임을 선언해야 한다.

이렇게 값을 바꿀 수도 없는 튜플을 사용하는 경우는?

  • 사실 프로그래밍을 하다 보면 여러 사람 과 함께 작업해야 하는 경우가 많다. 자신이 하나의 함수만 만들고, 다른 사람이 그 함수의 결과값을 사용해야 하는 경우도 발생한다. 이때 반환해주는 타입을 튜플로 선언하면 받아서 사용하는 사람이 마음대로 데이터를 바꾸지 못하게 된다.
  • 그렇다면 바뀌면 안되는 데이터에는 어떤 것이 있을까? 학번이나 이름, 주민등록번호와 같이 변경되면 안 되는 정보 등이다. 프로그래머가 이러한 이해 없이 마음대로 값을 변경하려고 할 때 튜플은 이를 방지해주는 역할을 한다.

세트

  • 세트(set) 는 값을 순서 없이 저장하되 중복을 불허 하는 자료형으로 수학의 집합과 개념 적으로 아주 비슷하다.
  • 중복을 불허 하는 특징 때문에 프로그래밍에서 매우 유용하다.
  • 대표적으로 문서 하나에 들어가 있는 단어 종류의 개수를 셀 때 모든 단어를 추출한 후 세트로 변환 하면 단어 종류의 개수를 쉽게 파악할 수 있다. 파이썬에서는 세트 선언을 하는 것으로 사용 할 수 있다.
>>> s=set([1, 2, 3, 1, 2, 3]) 
# set() 함수를 사용하여 1, 2, 3을 세트 객체로 생성
>>> s
{1, 2, 3}
  • 세트를 사용하기 위해 set() 함수를 사용하여 리스트나 튜플의 데이터를 넣으면 해당 값이 세트 형태로 변환된다. 위 코드처럼 [1, 2, 3, 1, 2, 3]이라는 리스트형의 값을 세트로 변환하면, 중복을 제거한 후 {1, 2, 3}으로 변환되어 출력된다.

세트는 튜플과 다르게 삭제나 변경이 가능

>>> s
{1, 2, 3}
>>> s.add(1)  # 1을 추가하는 명령이지만 중복 불허로 추가되지 않음
>>> s
{1, 2, 3}
>>> s.remove(1)                  # 1 삭제
>>> s
(2, 3}
>>> s.update([1, 4, 5, 6, 7])    # [1, 4, 5, 6, 7] 추가
>>> s
{1, 2, 3, 4, 5, 6, 7}
>>> s.discard(3)                 # 3 삭제
>>> s
{1, 2, 4, 5, 6, 7} 
>>> s.clear()                    # 모든 원소 삭제
>>> s
set ()
  • 세트를 지원하는 함수에는 원소 하나를 추가하는 add(), 원소 하나를 제거하는 remove() 또는 discard(), 새로운 리스트를 그대로 추가하는update(), 모든 변수를 지우는 clear() 등이 있다.
  • 값은 모두 순서 없이 저장되는 동시에 중복을 제거하고 저장된다.

  • 파이썬의 세트는 수학의 집합과 마찬가지로 다양한 집한 연산을 제공하여 집합 연산에는 교집합, 합집합, 차집합이 있다.
>>> s1 = set ([1, 2, 3, 4, 5])
>>> s2 = set ([3, 4, 5, 6 ,7])
>>>
>>> s1.union(s2)               # s1과 s2의 합집합
{1, 2, 3, 4, 5, 6, 7}
>>> s1 | s2                    # set([1,2,3,4,5,6,7])
{1, 2, 3, 4,5, 6, 7}
>>> s1.intersection(s2)        # s1과 s2의 교집합
{3, 4, 5}
>>> s1 & s2                    # set([3, 4, 5])
{3, 4, 5}
>>> s1.difference(s2)         # s1과 s2의 차집합
{1, 2}
>>> s1 - s2                    # set([1, 2])
{1, 2}

합집합

  • 두 집합의 중복값을 제거하고 합치는 연산
  • s1.union(s2)를 통해 s1,과 s2의 합집합이 출력된다.
  • 합집합은 union() 과 같은 함수로도 표현할 수 있지만, | 기호로도 추출할 수 있다.
  • s1 | s2 의 결과가 s1.union(s2) 와 동일하다.

교집합

  • 두 집합 양쪽에 모두 포함된 값만 추출하는 연산
  • s1,s2 는 모두 3,4,5 를 원소로 가지고 있어 s1.intersection(s2) 나 s1 & s2 로 교집합을 추출할 수 있다.
  • 교집합의 개념은 if문에서 배웠던 and 조건의 개념과 비슷하다.

차집합

  • 앞에 있는 s1의 원소 중 s2에 포함된 원소를 제거하는 연산
  • s1에서 s1과 s2의 교집합 원소를 삭제하여 s1은 [1, 2, 3, 4, 5]를 가지고 있으므로 [3, 4, 5] 를 제거하면 [1, 2]만 남는다.
  • s1.difference(s2) 또는 s1 - s2 로 차집합을 추출할 수 있다.

연산                         함수                                        기호            예시

합집합 union | s1.union(s2), s1 | s2
교집합 intersection & s1.intersection(s2), s1 & s2
차집합 difference - s1.difference(s2), s1-s2

 

딕셔너리

  • 영어사전에서 검색을 위해 영어 단어들을 저장해 놓은 방식과 비슷
  • 영어사전에서는 각 단어를 검색할 수 있도록 색인(index)을 만들어 놓고 색인을 통해 그 단어를 찾아 의미를 파악한다.
  • 색인은 데이터를 찾기 쉽도록 구분해 놓은 유일한 정보이며 단어를 검색하는 방식

키 (key), 값(value)

  • 딕셔너리 구조에서는 데이터의 유일한 구분자인 키라는 이름으로 검색할 수 있게하고, 실제 데이터를 값이라는 이름과 쌍으로 저장하여 프로그래머가 데이터를 쉽게 찾을 수 있도록 한다.
  •  학번                                     이름                         생년월일                               주소
    20150230 홍길동 1995-04-03 서울시 동대문구
    20150233 김영철 1995-04-20 성남시 분당구
    20150234 오영심 1996-01-03 성남시 중원구
    20150236 최성철 1995-12-27 인천시 계양구
    • 대학생 인적사항에서 학번이 나머지 정보를 구분하는 키로, 학번을 키로 하여 이름, 생년월일, 주소를 리스트 형태로 저장한 다음 한 번에 검색할 수 있는 형태가 되면 학번을 이용해 다른 정보에 쉽게 접근할 수 있다.
  • 키와 값을 쌍으로 저장하는 방식은 실생활에서 개인의 주민등록번호나 학교의 학번, 제품 번호 등은 모두 하나의 데이터를 구분하는 키로 생각할 수 있다.

파이썬에서의 딕셔너리

💡 딕셔너리 변수 = {키1 : 값1, 키2 : 값2, 키3 : 값3, ,,,}

학번 (키)                                                                                      이름 (값)

20140012 Sungchul
20140059 Jiyong
20150234 Jaehong
20140058 Wonchul

학번 → 키, 이름 → 값

  • 다양한 자료형이 들어갈 수 있어 이름은 문자열이므로 일반적인 문자열로 저장한다.
  • 그러나 리스트와 같이 한 번에 여러 데이터를 입력한다거나, 튜플 또는 세트와 같은 데이터도 사용할 수 있다. 딕셔너리도 사용 가능하다.
>>> student_info = {20140012 : 'Sungchul', 20140059 : 'Jiyong', 20150234 : 'Jaehong'}
  • student_info 라는 변수를 먼저 선언한 후, 해당 변수에 {키:값} 형태로 값을 입력한다.

해당 변수에서 특정 값을 호출하는 방법

>>> student_info[20140012]
'Sungchul'
  • 해당 값의 키를 대괄호 [] 안에 넣어 호출하면 된다.
  • 키는 문자열로 선언할 수도 있고, 정수형으로 선언할 수도 있다.
  • 변수의 자료형을 정확히 모르고 호출한다면 리스트로 오해할 수 있으니 반드시 기억해야한다.

재할당과 데이터 추가

>>> student_info[20140012] = 'Sungchul'
>>> student_info[20140012]
'Sungchul'
>>> student_info[20140039] = 'Wonchul'
>>> student_info
{20140012 : 'Sungchul', 20140059 : 'Jiyong', 20140058 : 'Jaehong', 20140039 : 'Wonchul'}
  • 키를 이용하여 해당 변수를 호출한 후 새로운 값을 할당
  • 데이터 추가도 새로운 키를 사용하여 값을 할당하고 할당 시 사용되는 키가 기존에 있던 키라면 해당 값이 변경되고, 기존에 없던 키라면 새로운 값으로 추가된다.

 

딕셔너리 함수

국가명과 국가 전화번호를 묶어 보여주는 코드

>>> country_code = {}     # 딕셔너리 생성
>>> country_code = {"America": 1, "Korea": 82, "China": 86, "Japan": 81}
>>> country_code
{'America': 1, 'Korea': 82, 'China': 86, 'Japan': 81}

딕셔너리 변수 안의 키와 값을 출력하는 함수

  • 키만 출력하는 keys() 함수
    >>> country_code.keys ()                    # 딕셔너리의 키만 출력
    dict_keys(['America', 'Korea', 'China', 'Japan'])
    
  • 키가 리스트 형태로 출력됨
  • 키만 출력하는 keys() 함수
    >>> country_code.keys ()                    # 딕셔너리의 키만 출력
    dict_keys(['America', 'Korea', 'China', 'Japan'])
    
  • 키가 리스트 형태로 출력됨
>>> country_code.keys ()                    # 딕셔너리의 키만 출력
dict_keys(['America', 'Korea', 'China', 'Japan'])

값을 출력하는 values() 함수

>>> country_code ["German"] = 49        # 딕셔너리 추가
>>> country_code
{'America': 1, 'Korea': 82, 'China': 86, 'Japan': 81, 'German': 49}
>>> country_code.values()               # 딕셔너리의 값만 출력
dict_values([1, 82, 86, 81, 49])

키-값 쌍을 모두 보여주는 items() 함수

>>> country_code.items ()               # 딕셔너리 데이터 출력
dict_items([('America', 1), ('Korea', 82), ('China', 86), ('Japan', 81), ('German', 49)])

딕셔너리와 for문

>>> for k, v in country_code.items():
        print("Key:", k)
        print("Value:", v)
Key: America
Value: 1
Key: Korea
Value: 82
Key: China
Value: 86
Key: Japan
Value: 81
Key: German
Vlaue: 49

if문을 사용하여 특정 키나 값이 해당 변수에 포함되어 있는지 확인

>>> "Korea" in country_code.keys ()         # 키에 "Korea"가 있는지 확인
True
>>> 82 in country_code.values ()            # 값에 82가 있는지 확인
True

 

collections 모듈

  • 파이썬의 내장 자료구조 (built-in data structure) 모듈인 collections
  • collections 모듈은 이미 앞에서 배운 다양한 자료구조인 리스트, 튜플, 딕셔너리 등을 확장 하여 제작된 파이썬의 내장 모듈이다. 기존에 배웠던 자료구조보다 효율적이고 편리하다.
  • collections 모듈은 deque, OrderedDict, defaultdict, Counter, namedruple 등을 제 공한다. 각 자료구조를 호출하는 코드는 다음과 같다.
from collections import deque 
from collections import OrderedDict 
from collections import defaultdict 
from collections import Counter 
from collections import namedtuple

 

deque 모듈

  • deque 모듈은 스택과 큐를 모두 지원하는 모듈이다. deque를 사용하기 위해서는 리스트와 비슷한 형식으로 데이터를 저장해야 한다.
  • append() 함수를 사용하면 기존 리스트처럼 데이터가 인덱스(index) 번호를 늘리면서 쌓이기 시작한다.
>>> from collections import deque
>>> 
>>> deque_list = deque ()
>>> for i in range(5):
        deque_list.append(i)

>>> print(deque_list) 
deque ([0, 1, 2, 3, 4])
  • deque_list.pop()을 수행시키면 오른쪽 요소부터 하나씩 추출된다. 즉, 스택처럼 나중에 넣은 값부터 하나씩 추출되는 것이다.
>>> deque_list.pop()
4
>>> deque_list.pop()
3
>>> deque_list.pop()
2
>>> deque_list 
deque([0, 1])

deque에서의 큐 사용 방법

  • pop(0)을 입력하면 실행될 것 같지만, deque에서는 작동하지 않는다.
  • 대신 deque는 appendleft( ) 함수로 새로운 값을 왼쪽부터 입력하도록 하여 먼저 들어간 값부터 출력될 수 있도록 한다.
>>> from collections import deque
>>>
>>> deque_list = deque ()
>>> for i in range (5):
        deque_list.appendleft(i)

>>> print(deque_list) 
deque([4, 3, 2, 1, 0])

deque 모듈의 장점

  • deque 는 연결 리스트의 특성을 지원
  • 연결 리스트는 데이터를 저장할 때 요소의 값을 한 쪽으로 연결한 후, 요소의 다음 값의 주소값을 저장하여 데이터를 연결하는 기법이다.

  • 연결 리스트는 위의 사진처럼 다음 요소의 주소값을 저장하므로 데이터를 원형으로 저장할 수 있다. 또한, 마지막 요소에 첫 번째 값의 주소를 저장 시켜 해당 값을 찾아갈 수 있도록 연결시킨다.
  • 이러한 특징 때문에 가능한 기능 중 하나가 rotate() 함수이다. rotate()는 기존 deque에 저장된 요소들 값의 인덱스를 바꾸는 기법이다.
  • 연결 리스트는 양쪽 끝의 요소들을 연결할 수 있으므로 위의 사진처럼 원형의 데이터 구조를 가진다. 이러한 특징을 이용하여 각 요소의 인덱스 번호를 하나씩 옮긴다면 실제로 요소를 옮기지 않더라도 인덱스 번호를 바꿀 수 있다.
>>> from collections import deque
>>>
>>> deque_list = deque()
>>> for i in range(5):
        deque_list.appendleft(i)
  • 기존 데이터에 rotate(2) 함수를 수행시키면 3과 4의 값이 두 칸씩 이동하여 0번째, 1번째 인덱스로 옮겨진 것을 알 수 있다. 다시 rotated(2)를 수행시키면 1과 2가 0번째, 1번째 인덱스로 이동한다.
>>>
>>> print(deque_list) 
deque([0, 1, 2, 3, 4])
>>> deque_list.rotate(2) 
>>> print(deque_list) 
deque([3, 4, 0, 1, 2])
>>> deque_list.rotate(2) 
>>> print(deque_list) 
deque([1, 2, 3, 4, 0])

reserved () 함수로 반대로 데이터 저장

>>> print(deque(reversed(deque_list))) 
deque([0, 4, 3, 2, 1])

extend() 함수, extendleft() 함수

>>> deque_list.extend([5, 6, 7])
>>> print(deque_list)
deque([1, 2, 3, 4, 0, 5, 6, 7])
>>> deque_list.extendleft([5, 6, 7]) 
>>> print(deque_list)
deque([7, 6, 5, 1, 2, 3, 4, 0, 5, 6, 7])

정리

  • deque 모듈은 메모리의 효율적 사용과 빠른 속도라는 측면에서도 유용하다.
  • 앞에서 제시한 여러 가지 함수는 기존 리스트에서도 모두 사용할 수 있다. 그러나 deque의 메모리 저장 방식을 사용하면 사용자는 조금 더 효율적으로 데이터를 저장하고 삭제할 수 있다. 대용량의 큐나 스택을 처리할 일이 있다면 deque 사용을 권장한다.

 

OrderedDict 모듈

  • OrderedDict 모듈은 이름 그대로 순서를 가진 딕셔너리 객체이다.
  • 딕셔너리는 순서를 보장하지 않는 객체여서 딕셔너리 파일을 저장하면 키는 저장 순서와 상관없이 저장된다.
d = {}
d['x'] = 100
d['T'] = 500
d['y'] = 200
d['z'] = 300

for k, v in d.items():
    print(k, v)
#출력 결

x 100 
l 500 
y 200 
z 300
  • x, l, y, z 의 순서대로 키를 저장했지만, 결과는 저장한 순서와 상관없이 다양한 형태로 출력된다.
  • 하지만 OrderedDict 모듈은 키의 순서를 보장한다. 가장 기본적인 방법은 저장한 순서대로 결과를 화면에 출력 한다.
from collections import OrderedDict      # OrderedDict 모듈 선언

d = OrderedDictO
d['x'] = 100
d['y'] = 200
d['z'] =300
d['l'] =500

for k, v in d.items(): 
    print(k, v)
#출력 결과

x 100 
y 200 
z 300 
l 500
  • 위의 코드를 실행하면 어떤 컴퓨터든 상관없이 x, y, z, 1의 순서대로 키-값쌍이 출력된다.
  • OrderedDict 모듈을 가장 많이 사용하는 경우는 딕셔너리로 데이터 처리 시 키나 값으로 데이터를 정렬할 때이다.

ex) 키를 이용하여 주민등록 번호를 번호 순서대로 정렬한 후 데이터를 출력하는 경우

def sort_by_key(t): 
    return t[0]

from collections import OrderedDict       # OrderedDict 모듈 선언

d = dict() 
d['x'] = 100 
d['y'] = 200 
d['z'] = 300 
d['l'] = 500

for k, v in OrderedDict(sorted(d.items(), key=sort_by_key)).items(): 
    print(k, v)
#출력 결과

l 500 
x 100 
y 200 
z 300
  • 코드를 보면 딕셔너리의 값인 변수 d를 리스트 형태로 만든 다음, sorted() 함수를 사용하여 정렬한다.
  • sorted(d.items(), key=sort_by_key)의 코드만 따로 실행하면 다음처럼 정렬되어 이차원 형태로 값이 출력된다.

💡 [(’l’, 500), ('x', 100), (’y’, 200),(’z’, 300)]

  • 즉, 기존의 딕셔너리 변수를 리스트로 추출하고, sorted() 함수로 키를 기준으로 정렬한 후. 다시 OrderedDict 모듈로 감싸주는(wrapping) 방식이다. 이렇게 하면 기존의 딕셔너리나 리스 트의 순서를 지키면서 딕셔너리 형태로 관리할수 있다.
  • 만약, 값을 기준으로 정렬한다면 위의 코드의 1행과 2행을 다음처럼 바꾸면 된다. 참고로 t[0]과 t[1]은 위 리스트 안의 튜플 값 중 0번째 인덱스(l, x, y, z)와 1번째 인덱스(500, 100, 200, 300)를 뜻한다.
def sort_by_value(t): 
    return t[1]

 

defaultdict 모듈

  • defaultdict 모듈은 딕셔너리의 변수를 생성할 때 키에 기본값을 지정하는 방법이다.
  • 새로운 키를 생성할 때 별다른 조치 없이 새로운 값을 생성할 수 있다.
d = dict()
print(d["first"])
Traceback (most recent call last):
    File "defauUdictl.py", line 2, in <module>
        print(d["first"]) 
KeyError: 'first'
  • 실제 딕셔너리에서는 위의 사진처럼 키를 생성하지 않고 해당 키의 값을 호출하려고 할 때 오류가 발생한다. 즉, 코드에서 first의 키 값을 별도로 생성하지 않은 채 바로 호출하여 오류가 발생한 것이다.

defaultdict 모듈 사용

from collections import defaultdict

d = defaultdict(lambda: 0)          # Default 값을 0 으로 설정
print(d["first"])
#출력 결과

0
  • 위의 코드에서의 핵심은 3행의 d=defaultdict(lambda: 0)이다.
  • defaultdict 모듈을 선언하면서 동시에 초깃값을 0으로 설정한 것이다.lambda() 함수를 ‘return 0’이라고 이해하면 된다. 어떤 키가 들어오더라도 처음 값은 전부 0으로 설정한다는 뜻이다.
from collections import defaultdict

s = [('yellow', 1), ('blue’, 2), ('yellow', 3), ('blue', 4), ('red', 1)] 
d = defaultdict(list)
for k, v in s:
    d[k].append(v)

print(d.itemsO)
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
#출력 결과

dict_items([('yellow', [1, 3]), ('blue', [2, 4]), ('red', [1])])
  • 이외에도 defaultdict의 초깃값은 처럼 리스트 형태로도 설정할 수 있다.
  • s 변수에 튜플 데이터들을 이차원 리스트 형태로 저장하였다.
  • 또한 4행의 d = defaultdict(list) 코드는 변수 d를 defaultdict 타입으로 선언하면서 초깃값을 리스트로 선언하였다. 5행부터는 for문이 작동하면서 변수 s의 각 요소에서 키 값과 실제 값을 추출하여 변수 시에 추가하였다.
  • 중요한 것은 변수 d는 deafultdict 타입이면서 list가 초깃값으로 선언되었기 때문에 새로운 키 값이 없더라도 별도로 오류가 발생하지 않는다. 이는 일반적인 ‘diet’에 비해 코드 수를 줄일 수 있어 defaultdict 타입의 가장 큰 장점이라고 할 수 있다.

 

Counter 모듈

  • Counter 모듈은 시퀀스 자료형의 데이터 값의 개수를 딕셔너리 형태로 반환하는 방법이다. 즉, 리스트나 문자열과 같은 시퀀스 자료형에 저장된 요소 중에서 같은 값이 몇 개 있는지 그 개수를 반환한다.
>>> from collections import Counter
>>>
>>> text = list("gallahad")
>>> text
['g' 'a', 'l', 'l', 'a', 'h', 'a', 'd']
>>> c = Counter(text)
>>> c
Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1}) 
>>> c["a"]
3
  • 위 코드에서는 기존 문자열값인 ‘gallahad’를 리스트형으로 변환한 후, text 변수에 저장하였다. 사실 ‘gallahad’ 자체도 시퀀스 자료형인 문자열이므로 굳이 리스트로 변환할 필요는 없지만 이해를 돕기 위해 리스트 형태로 저장하였다.
  • c 라는 Counter 객체를 생성하면서 text 변수를 초깃값으로 설정하였다. 이를 출력하면 위 결과처럼 각 알파벳이 몇 개씩 있는지 쉽게 확인할 수 있다.
  • c["a"]처럼 딕셔너리 형태의 문법을 그대로 이용해 특정 텍스트의 개수도 바로 출력 할 수 있다.
  • Counter를 이용하면 각 문자의 개수 세는 작업을 매우 쉽게 할 수 있다. 다음과 같이 코드를 작성하면 정렬까지 끝낸 결과물을 확인할 수 있는데, 결과적으로 이전 Lab에서 수행한 작업 을 단 한 줄의 코드로 작성하였다.
>>> text = 'A press release is the quickest and easiest way to get free 
publicity. If well written, a press release can result in multiple published 
articles about your firm and its products. And that can mean new prospects 
contacting you asking you to sell to them. •••.'.lower().split()
>>> Counter(text)
Counter({'and': 3, 'to': 3, 'can': 2, 'press': 2, 'release': 2, 'you': 2, 'a': 2, 'sell': 1,
'about': 1, 'free': 1, 'firm': 1, 'quickest': 1, 'products.': 1, 'written,': 1, 'them.': 1,
'•••.': 1, 'articles': 1, 'published': 1, 'mean': 1, 'that': 1, 'prospects': 1, 'its': 1, 
'multiple': 1, 'if': 1, 'easiest': 1, 'publicity.': 1, 'way': 1, 'new': 1, 'result': 1,
'the': 1, 'your': 1, 'well': 1, 'is': 1, 'asking': 1, 'in': 1, 'contacting': 1, 'get': 1})
  • Counter는 단순히 시퀀스 자료형의 데이터를 세는 역할도 하지만. 딕셔너리 형태나 키워드 형태의 매개변수를 사용하여 Counter를 생성할 수 있다.
  • 먼저 딕셔너리 형태로 Counter 객체를 생성하는 방법이다. 다음 코드를 보면, {'red': 4, 'blue': 2}라는 초깃값을 사용하여 Counter를 생성하였다. 또한 elements() 함수를 사용 하여 각 요소의 개수만큼 리스트형의 결과를 출력하였다.
>>> from collections import Counter 
>>>
>>> c = Counter({'red': 4, 'blue': 2})
>>> print(c)
Counter({'red': 4, 'blue': 2})
>>> print(list(c.elements()))
['red', 'red', 'red', 'red', 'blue', ’blue’]

키워드 형태의 매개변수를 사용하여 Counter를 생성하는 방법

>>> from collections import Counter 
>>>
>>> c = Counter(cats = 4, dogs = 8)
>>> print(c)
Counter({'dogs': 8, 'cats': 4})
>>> print(list(c.elements()))
['cats', 'cats', 'cats', 'cats', 'dogs', 'dogs', 'dogs', 'dogs', 'dogs', 'dogs', 'dogs', 'dogs']
  • 매개 변수의 이름을 키 (key) 로 , 실제 값을 값 (vlaue)으로 하여 Counter 를 생성할 수 있다.

기본 사칙연산 지원

>>> from collections import Counter 
>>>
>>> c = Counter(a = 4, b = 2, c = 0, d =-2)
>>> d = Counter(a = 1, b = 2, c = 3, d = 4)
>>> c.subtract(d)
>>> c
Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})
  • 파이썬에서 지원하는 기본 연산인 덧셈, 뺄셈, 논리 연산 등이 가능하다.
  • 위 코드는 2개의 Counter c와 d를 생성한 후 c.subtract(d)를 수행하여 仁에 대한 d의 차를 구한 결과이다. c에 있는 각 요소의 개수가 d에 있는 요소의 개수만큼 감소하였다.

덧셈, AND, OR 연산 지원

>>> from collections import Counter 
>>>
>>> c = Counter(a = 4, b = 2, c = 0, d = -2) 
>>> d =Counter(a =1, b =2, c =3, d =4) 
>>> print(c + d)
Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2})
>>> print(c & d)
Counter({'b': 2, 'a': 1})
>>> print(c | d)
Counter({'a': 4, 'd': 4, 'c': 3, 'b': 2})
  • 十 기호는 두 Counter 객체에 있는 각 요소를 더한 것이고, & 기호는 두 객체에 같은 값이 있을때, 즉 교집합의 경우에만 출력하였다.
  • 반대로 | 기호는두 Counter 객체에서 하나가 포함되어 있다면, 그리고 좀 더 큰 값이 있다면 그 값으로 합집합을 적용하였다.

 

namedtuple 모듈

  • namedtuple 모듈은 튜플의 형태로 데이터 구조체를 저장하는 방법이다.
  • 어떤 특정 데이터는 저마다 규정된 정보가 있다. 예를 들어, 학생이라는 정보를 컴퓨터에 저장하기 위해 몇 가지 변수(학번, 이름, 학년, 학과 등)가 있다. 이러한 정보를 하나의 리스트로 만들어 이차원 리스트 형태로 구성해도 된다. 그러나 이때, 나중에라도 이 리스트를 다른 사람이 사용한다면 자세한 정보를 모르기 때문에 사용이 어려울 수 있다. 그래서 이러한 정보를 하나의 튜플 형태로 구성해 손쉽게 사용할 수 있는 자료구조가 namedtuple이다.
  • C 언어에서는 struct라는 이름으로 사용되고 있다.
>>> from collections import namedtuple 
>>>
>>> Point = namedtuple('Point1', ['x', 'y']) 
>>> p = Point(ll, y=22)
>>> p
Point(x=ll, y=22)
>>> p.x, p.y
(11, 22)
>>> print(p[0] + p[l]) 
33
  • 위 코드는 좌표 평면에서의 점의 위치를 표현하기 위해 Point라는 객체를 생성하여 값을 저장한 namedtuple 이다. Point = namedtuple(’Point', ['x', 'y']) 코드에서 Point 객체의 이 름은 Point로 지정하고, 저장되는 요소의 이름을 x와 y로 지정하였다.
  • 다음으로 Point 객체를 생성한다. 생성은 함수와 비슷하다. Point 객체에서 x와 y를 변수로 사용하고 있고 각각 p = Pointd(11, y=22)에서 차례로 사용되어 값을 저장할 수 있다. 입력된 값은 p 변수에 저장한다.
  • 여기서 주목할 것은 p 변수에 저장된 값을 호출하는 방법이다. 첫째, 요소의 이름을 그대로 사용하는 방법이 있다. p.x, p.y와 같이 변수명과 요소의 이름을 점(.) 으로 연결하면 해당 값을 불러낼 수 있다.
  • 또 다른 방법으로 인덱스를 사용하는 것이다. 인덱스는 이름이 들어간 순서대로 값이 저장되어 있다. 즉, p[0]은 Point 객체에서 먼저 저장되어야 하는 x값에 대응된다. p[1]은 Point 객체에서 두 번째로 저장되어야 하는 y값에 대응된다. 이로 인해 덧셈 연산이나 언패킹 (unpacking) 연산 등이 모두 가능해진다.

 

실습

텍스트 마이닝 프로그램

  • 앞에서 배운 딕셔너리와 collections 모듈을 이용하여 텍스트 마이닝 프로그램을 만들어보 자. 텍스트를 분석하여 의미 있는 결과를 도출하는 과정을 텍스트 마이닝 (text mining)이라고 한다. 일반적으로 텍스트 마이닝을 할 때 각 문장에서 단어가 얼마나 많이 출현하는지 분석한다. 이때, defaultdict 모듈을 사용하면 문장에 있는 단어의 개수를 쉽게 파악할 수 있다.
  • 다음 문장에 있는 단어의 개수를 파악해본다.

💡 A press release is the quickest and easiest way to get free publicity. If well written, a press release can result in multiple published articles about your firm and its products. And that can mean new prospects contacting you asking you to sell to them. •••

 

  • 프로그램을 작성하는 규칙

💡 1. 문장의 단어 개수를 파악하는 코드를 작성한다. 2. defaultdict 모듈을 사용한다. 3. 단어의 출현 횟수를 기준으로 정렬된 결과를 보여주기 위해 OrderedDict 모듈을 사용한다.

  • 실행 결과
and 3
to 3
a 2
press 2 
release 2
...
(생략)
...
contacting 1 
asking 1 
sell 1
them. 1
•••. 1

정답

text = """A press release is the quickest and easiest way to get free 
publicity. If well written, a press release can result in multiple 
published articles about your firm and its products. And that can mean new 
prospects contacting you asking you to sell to them. •••.’"",.lower().split()

from collections import defaultdict

word_count = defaultdict(lambda: 0)    # Default 값을 0으로 설정
for word in text:
    word_count[word] += 1

from collections import OrderedDict
for i, v in OrderedDict(sorted(word_count.items(), key=lambda t: t[1], 
reverse=True)).items():
    print(i, v)
  • 1행은 text 변수에 원하는 문장을 넣고, 이를 소문자로 바꾼 후 단어 단위로 자르는 코드이다. 이를 위해 lower ()와 split() 함수를 연속으로 사용하였다. 이 코드의 결과를 확인하기 위해 파이썬 셸에 다음과 같이 입력하면 리스트의 결과를 볼 수 있다.
>>> text = 'A press release is the quickest and easiest way to get free 
publicity. If weU written, a press release can result in multiple published 
articles about your firm and its products. And that can mean new prospects 
contacting you asking you to sell to them. lower().split()
>>> print(text)
['a', 'press'j 'release', 'is', 'the', 'quickest', 'and', 'easiest', 'way', 'to',
'get', 'free', 'publicity', 'if', 'well', 'written,', 'a', 'press', 'release', 
'can', 'result', 'in', 'multiple', 'published', 'articles', 'about', 'your', 
'firm', 'and', 'its', 'products.', 'and', 'that', 'can', 'mean', 'new', 'prospects',
'contacting', 'you', 'asking', 'you', 'to', 'sell', 'to', 'them.', '•••.']
  • 다음으로 이 리스트에서 각각의 단어가 몇 개 있는지 카운트하는 코드가 필요하다. 3〜7행 을 보면 defaultdict 모듈을 사용하여 딕셔너리의 키값을 단어가 출현할 때마다 word.count [word] += 1을 통해 그 수를 증가시키고 있다.
  • 다음으로 단어의 출현 횟수를 기준으로 정렬된 결과를 보여주고 싶다면. 9〜11행과 같이 OrderedDict 모듈을 사용하여 코드를 구성한다.
  • 이 코드는 이후에 배울 텍스트 분석에서 벡터 스페이스 모델(vector space model)을 직접 구현하는 데 매우 중요한 코드이다. 파이썬의 다양한 내장 모듈을 사용하여 효율적으로 텍스트를 분석 하는 방법은 이후에 다시 배우도록 하겠다.

[참고]

문장을 모두 소문자로 바꾸는 이유는 보통 영어 문장의 첫 글자가 대문자일 가능성이 있기 때문이다. 이를 대비하기 위해 일반적으로 모든 텍스트를 소문자로 변환한다. 만약 텍스트의 대, 소문자 구분이 중요하다면 이를 그대로 놔두는 것이 좋다.