Program/Python

자료 구조

Hue Kim 2016. 4. 13. 21:12

10장. 자료 구조

자료 구조란 간단하게, 어떤 *자료*를 담는 *구조*를 말합니다. 다른 말로 하면, 서로 연관있는 어 떤 자료들의 집합을 저장하는 데 사용됩니다.

파이썬에는 네 종류의 자료 구조가 있는데, 각각 _리스트, 튜플, 사전, 집합_입니다. 이제 앞으로 각각의 사용법에 대해 알아보고 또 각각이 얼마나 편리한지 확인해보도록 하겠습니다.


10.1. 리스트

리스트란 순서대로 정리된 항목들을 담고 있는 자료 구조입니다. 즉, 리스트에는 항목의 *목록*을 저장할 수 있습니다. 이것은 쉽게 말하자면 장 보러 갈 때 적는 일종의 장바구니 목록 같은 것인데, 아마도 여러분은 각 품목들을 한줄 한줄 적겠지만 파이썬에서는 쉼표로 각 항목을 구분한다는 것만 다릅니다.


리스트를 정의할 때는 대괄호 [] 를 이용해서 파이썬에게 이것이 리스트를 의미한다는 것을 알려 줍니다. 한번 리스트를 만들어 두면 여기에 새로운 항목을 추가하거나 삭제할 수 있으며, 특정항목이 존재하는지 검색할 수도 있습니다. 이 때 항목을 추가 및 삭제가 가능하다는 것을 *비정적(mutable)*이라고 하며, 리스트는 비정적 자료구조로 내부 항목을 변경할 수 있습니다.


10.2. 객체와 클래스에 대한 간단한 소개

객체와 클래스에 대해서는 좀 더 나중에 다룰 예정이지만, 여기서 여러분이 리스트에 대해 좀 더잘 이해하실 수 있도록 이에 대한 간단한 소개를 하도록 하겠습니다. 이들에 대해서는 뒤 챕터에서 좀 더 자세하게 다루겠습니다.


리스트는 객체와 클래스가 사용된 한 예입니다. 변수 i 를 선언하고 예를 들어 5 라는 값을 할당해 주는 것은, int 라는 클래스(또는 타입)의 객체(또는 인스턴스) i 를 만드는 것입니다. 이에

대해 좀 더 자세히 알아보시려면 help(int) 를 읽어보시기 바랍니다.


클래스는 *메소드*를 가질 수 있는데, 여기서 메소드란 그 클래스 내에 정의된 고유의 내장 함수들을 말합니다. 또 이러한 내장 함수들은 클래스로 객체를 생성했을 때에야 비로소 사용할 수 있습니다. 예를 들어, 파이썬은 list 클래스에 append 라는 메소드를 제공하며 이는 리스트의 마지막에 항목을 한 개 추가할 때 사용되는 메소드입니다. 즉 mylist.append('an item') 라 하면 리스트 mylist 의 마지막에 해당 문자열을 추가해 줍니다. 이 때 객체의 메소드에 접근할 때에도 마침표를 이용한다는 점을 기억하세요.


또 클래스는 *필드*를 가질 수 있는데 이것은 단순히 그 클래스 내에 정의된 변수들을 의미합니다. 메소드와 마찬가지로 이러한 변수들은 클래스로 객체를 생성했을 때에야 비로소 사용할 수 있습니다. 필드도 메소드와 마찬가지로 마침표를 이용하여 접근합니다. 예를 들면 mylist.field와 같습니다.

예제 ( ds_using_list.py 로 저장하세요):


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
shoplist = ['apple''mango''carrot''banana']
print('I have'len(shoplist), 'items to purchase.')
print('These items are:',)
 
for item in shoplist:
    print(item,)
print('\nI also have to buy rice.')
shoplist.append('rice')
 
print ('My shopping list is now', shoplist)
print ('I will sort my list now')
 
shoplist.sort()
print ('Sorted shopping list is', shoplist)
print ('The first item I will buy is', shoplist[0])
 
olditem = shoplist[0]
del shoplist[0]
print ('I bought the', olditem)
print ('My shopping list is now', shoplist)
cs




10.3. 튜플

튜플은 여러 개의 객체를 모아 담는 데 사용됩니다. 튜플은 리스트와 비슷하지만, 리스트 클래스에 있는 여러가지 기능이 없습니다. 또 튜플은 수정이 불가능하며, 그래서 주로 문자열과 같이 *비정적*인 객체들을 담을 때 사용됩니다.튜플은 생략할 수 있는 괄호로 묶인 쉼표로 구분된 여러 개의 항목으로 정의됩니다.

튜플에 저장된 값들은 수정이 불가능하기 때문에, 단순 값들의 목록을 다루는 구문이나 사용자 정의 함수에서 주로 사용됩니다.

예제 ( ds_using_tuple.py 로 저장하세요):


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# I would recommend always using parentheses
# to indicate start and end of tuple
 
# even though parentheses are optional.
# Explicit is better than implicit.
zoo = ('python''elephant''penguin')
print ('Number of animals in the zoo is'len(zoo))
 
new_zoo = 'monkey''camel', zoo
 
print ('Number of cages in the new zoo is'len(new_zoo))
print ('All animals in new zoo are', new_zoo)
print ('Animals brought from old zoo are', new_zoo[2])
print ('Last animal brought from old zoo is', new_zoo[2][2])
print ('Number of animals in the new zoo is', \ 
    len(new_zoo)-1+len(new_zoo[2]))
cs



동작 원리변수 zoo 는 여러 항목들을 담고 있는 튜플입니다. 보시는 바와 같이 len 함수를 통해튜플의 길이를 알아올 수 있습니다. 튜플은 열거형 의 한 예 입니다.
이제 동물원(zoo) 안의 동물들을 새로운 동물원으로 옮겨야 한다고 해 봅시다. 이를 위해 새로운 동물원 new_zoo 튜플에 원래 있던 동물들과 함께 기존의 동물원에 있던 동물들을 옮겨 옵니다. 다시 파이썬으로 돌아와서, 이와 같이 튜플 안에 튜플을 담아도 튜플의 성질을 잃지 않습니다. 리스트에서 했던 것과 같이, 튜플 안에 있는 항목의 위치를 대괄호로 묶어 지정해주면 각 항목에 접근할 수 있습니다. 이를 인덱싱 연산자라고 부릅니다. new_zoo 의 세 번째 항목에 접근하려면 new_zoo[2] 와 같이 하며, 이 세 번째 항목은 튜플이므로 이것의 세 번째 항목에 접근하려면 new_zoo[2][2] 와 같이 합니다. 익숙해지면 쉽게 느껴질 것입니다.빈 튜플과 한 개짜리 튜플 빈 튜플은 괄호 안에 아무것도 적지 않고 myempty = () 와 같이 생성할 수 있습니다. 그러나, 항목 한 개만 담고 있는 튜플을 정의할 때는 주의해야 합니다. 이 경우 첫 번째 항목의 뒤에 쉼표를 붙여 주어 파이썬에게 이것이 숫자 연산에 사용되는 괄호가 아니라 객체를 담는 튜플을 의미하는 것이라는 것을 구분할 수 있도록 단서를 주어야 합니다. 예를 들어, 항목 2 를 담고 있는 튜플을 정의하려면 singleton = (2 , ) 와 같이 합니다.


10.4. 사전
사전은 이를테면 전화번호부 같은 것인데, 누군가의 이름을 찾으면 그 사람의 주소와 연락처를 알수 있는 것과 같습니다. 이 때 그 사람의 이름에 해당하는 것을 키 라 부르고, 주소와 연락처 등에 해당하는 것을 값 이라 부릅니다. 전화번호부에 동명이인이 있을 경우 어떤 정보가 맞는 정보인지 제대로 알아낼 수 없듯이, 사전의 키는 사전에서 유일한 값이어야 합니다. 사전의 키는 정적 객체(문자열 등등)이어야 하지만, 값으로는 정적 객체나 비정적 객체 모두 사용 할 수 있습니다. 이것을 간단하게 다시 말하면 사전의 키로는 단순 객체만 사용할 수 있다고 표현합니다.

사전을 정의할 때 키와 값의 쌍은 d = {key1 : value1, key2 : value2 } 와 같이 지정해 줍니다. 이 때 키와 값은 콜론으로 구분하며 각 키-값 쌍은 쉼표로 구분하고 이 모든 것을 중괄호`{}`로 묶어 준다는 것을 기억하시기 바랍니다.여기서 사전의 키-값 쌍은 자동으로 정렬되지 않습니다. 이를 위해서는 사용하기 전에 먼저 직접 정렬을 해 주어야 합니다. 앞으로 여러분이 사용하게 될 사전은 dict 클래스의 인스턴스/객체입니다.
예제 ( ds_using_dict.py 로 저장하세요):


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 'ab' is short for 'a'ddress'b'ook
ab = { 'Swaroop' : 'swaroop@swaroopch.com',
'Larry' : 'larry@wall.org',
'Matsumoto' : 'matz@ruby-lang.org',
'Spammer' : 'spammer@hotmail.com'
}
print ("Swaroop's address is", ab['Swaroop'])
 
# Deleting a key-value pair
del ab['Spammer']
print ('\nThere are {} contacts in the address-book\n'.format(len(ab)))
 
for name, address in ab.items():
    print ('Contact {} at {}'.format(name, address))
    
# Adding a key-value pair
ab['Guido'= 'guido@python.org'
if 'Guido' in ab:
    print ("\nGuido's address is", ab['Guido'])
cs



동작 원리먼저 설명한 표기법을 이용하여 사전 ab 를 생성합니다. 그러면 앞서 리스트와 튜플을 설명할 때 언급했던 인덱싱 연산자에 키를 지정해 주어 사전의 키-값 쌍에 접근할 수 있습니다. 간단하지요? 키-값 쌍 또한 del 문으로 삭제할 수 있습니다. del 문에 인덱싱 연산자에 해당 키를 지정해 준 사전을 넘겨 주기만 하면 됩니다. 이 때 그 키에 해당하는 값은 알 필요가 없습니다. 다음으로, 사전의 items 메소드를 사용하여 각 키-값 쌍에 접근합니다. 이 메소드는 키와 값 순으로 구성된 튜플들을 묶은 튜플 하나를 반환해 줍니다. 


그 후 for..in 문을 사용하여 각각을 변수 name 과 address 에 반복하여 지정해 주게 하고 그 값을 출력합니다. 위 예제의 'Guido' 와 같이 인덱싱 연산자를 사용하여 새 키-값 쌍을 추가할 수도 있습니다. 또, 사전 안에 키-값 쌍이 존재하는지 in 연산자를 통해 확인할 수 있습니다. dict 클래스의 모든 메소드 목록을 확인하시려면 help(dict) 를 입력하시기 바랍니다.


10.5. 열거형

열거형들은 리스트, 튜플, 문자열 같은 것입니다. 그러면 열거형이란 무엇이고 열거형에서는 무엇이 중요할까요?

열거형의 주요한 두 가지 기능은 멤버십 테스트 ( in 과 not in 연산)와 열거형의 특정 항목을얻어올 수 있는 *인덱싱 연산*입니다. 또한 리스트, 튜플, 문자열의 세 가지 열거형은 슬라이스 연산 기능을 가지고 있는데, 이것은 열거형의 일부분을 잘라낸(slice) 것을 반환하는 연산, 즉 부분 집합을 반환해 주는 연산입니다.

예제 ( ds_seq.py 로 저장하세요):


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
shoplist = ['apple''mango''carrot''banana']
name = 'swaroop'
 
# Indexing or 'Subscription' operation #
print ('Item 0 is', shoplist[0])
print ('Item 1 is', shoplist[1])
print ('Item 2 is', shoplist[2])
print ('Item 3 is', shoplist[3])
print ('Item -1 is', shoplist[-1])
print ('Item -2 is', shoplist[-2])
print ('Character 0 is', name[0])
 
# Slicing on a list #
print ('Item 1 to 3 is', shoplist[1:3])
print ('Item 2 to end is', shoplist[2:])
print ('Item 1 to -1 is', shoplist[1:-1])
print ('Item start to end is', shoplist[:])
 
# Slicing on a string #
print ('characters 1 to 3 is', name[1:3])
print ('characters 2 to end is', name[2:])
print ('characters 1 to -1 is', name[1:-1])
print ('characters start to end is', name[:])
cs



동작 원리먼저, 열거형의 각 항목을 얻어오기 위해 어떻게 인덱스를 사용하는지 보겠습니다. 이를 다른 말로 서브스크립션 연산 이라고도 합니다. 위 예제에서 보인 것과 같이 대괄호 내에 특정 숫자를 지정해 주면, 파이썬은 열거형에서 해당 숫자의 위치에 있는 항목을 얻어옵니다. 이 때 파이썬은 숫자를 0부터 센다는 점을 기억하시기 바랍니다. 따라서 shoplist[0] 과 shoplist[3] 은 각각 열거형 shoplist 의 첫 번째와 네 번째 항목을 읽어오는 연산을 의미합니다. 인덱스에는 음수가 지정될 수도 있습니다. 이 경우, 열거형의 마지막부터 위치가 계산됩니다. 따라서, shoplist[-1] 은 열거형의 마지막 항목을 의미하며 shoplist[-2] 는 열거형의 마지막 항목 바로 뒤의 항목을 의미합니다.


슬라이스 연산은 대괄호 안에 콜론으로 구분한 숫자들을 입력해 주는 것입니다. 슬라이스 연산은앞서 설명한 인덱싱 연산과 굉장히 비슷합니다. 이 경우 숫자는 반드시 지정해 줄 필요는 없지만 콜론은 반드시 들어가야 합니다.


슬라이스 연산에서 콜론 앞의 첫 번째 숫자는 슬라이스를 시작할 위치를 의미하며 콜론 뒤의 두 번째 숫자는 슬라이스를 멈출 위치를 지정합니다. 만약 첫 번째 숫자가 지정되지 않았을 경우, 파이썬은 열거형의 맨 처음부터 슬라이스를 시작합니다. 두 번째 숫자가 지정되지 않았을 경우, 파이썬은 열거형의 맨 끝에서 슬라이스를 멈춥니다. 이 때 슬라이스는 시작 위치부터 슬라이스를 시작_하며 _끝 위치의 직전까지 수행됩니다. 즉, 시작 위치에 해당하는 항목은 슬라이스에 포함되나 마지막 위치에 해당하는 항목은 포함되지 않습니다.


따라서, shoplist[1:3] 은 위치 1 에 해당하는 항목부터 시작하여 위치 2 에 해당하는 항목을 포함하지만, 위치 3 에 해당하는 항목은 포함하지 않습니다. 따라서 두 개의 항목의 슬라이스 가

반환됩니다. 이와 비슷하게, shoplist[:] 는 전체 열거형의 복사본이 반환됩니다.


슬라이스 숫자로도 음의 위치를 지정해 줄 수 있습니다. 음수는 열거형의 마지막부터 위치를 계산하는 것을 의미합니다. 예를 들어, shoplist[:-1] 은 마지막 항목을 제외한 모든 항목을 포함하고 있는 슬라이스를 반환해 줍니다.


슬라이스 숫자에 세 번째 인수를 지정해 줄 수 있는데, 이것은 슬라이스 _스텝_에 해당합니다 (기본값은 1 입니다):



1
2
3
4
5
6
7
8
9
shoplist = ['apple''mango''carrot''banana']
shoplist[::1]
['apple''mango''carrot''banana']
shoplist[::2]
['apple''carrot']
shoplist[::3]
['apple''banana']
shoplist[::-1]
['banana''carrot''mango''apple']
cs

보시는 바와 같이 스텝이 2일 경우 위치 0, 2, … 에 해당되는 항목들이 반환되며 스텝이 3일 경우 0, 3, … 에 해당되는 항목들이 반환됩니다.
파이썬 인터프리터에서 여러 가능한 슬라이스 숫자의 조합들을 시험해 보시면 그 결과를 곧바로확인해 보실 수 있습니다. 이 모든 사항은 모든 열겨형에 적용되므로, 튜플, 리스트, 문자열의 경우 모두 동일한 방법을 사용할 수 있습니다!



10.6. 집합

집합은 정렬되지 않은 단순 객체의 묶음입니다. 집합은 포함된 객체들의 순서나 중복에 상관없이 객체를 묶음 자체를 필요로 할 때 주로 사용합니다.

집합끼리는 멤버십 테스트를 통해 한 집합이 다른 집합의 부분집합인지 확인할 수 있으며, 두 집합의 교집합 등을 알아낼 수도 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bri = set(['brazil''russia''india'])
print('india' in bri)
 
 
'usa' in bri
print(False)
 
bric = bri.copy()
bric.add('china')
bric.issuperset(bri)
 
 
bri.remove('russia')
print(bri & bric) # OR bri.intersection(bric)
{'brazil''india'}
cs


동작 원리이 책을 읽는 여러분들은 아마도 학교에서 기초 집합론에 대해 이미 배우셨을 것이므로
위 예제에 대해서는 딱히 설명할 것이 없습니다.




10.7. 참조

객체를 생성하고 변수에 할당해 줄 때, 사실 실제 객체가 변수에 할당되는 것은 아닙니다! 변수에는 객체의 참조 가 할당됩니다. 참조란, 그 변수의 이름이 여러분의 컴퓨터 메모리 어딘가에 저장되어 있는 실제 객체의 위치를 가리키는 것을 말합니다. 이를 객체에 이름을 바인딩 한다고 말합니다.

일반적으로는 이에 대해 크게 신경 쓸 필요가 없습니다만, 참조로 인해 발생하는 몇 가지 현상에 대해 알고 계실 필요가 있습니다.

예제 ( ds_reference.py 로 저장하세요):



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
print ('Simple Assignment')
shoplist = ['apple''mango''carrot''banana']
# mylist is just another name pointing to the same object!
mylist = shoplist
 
# I purchased the first item, so I remove it from the list
del shoplist[0]
print ('shoplist is', shoplist)
print ('mylist is', mylist)
 
# Notice that both shoplist and mylist both print
# the same list without the 'apple' confirming that
# they point to the same object
 
print ('Copy by making a full slice')
 
# Make a copy by doing a full slice
mylist = shoplist[:]
 
# Remove first item
del mylist[0]
 
print ('shoplist is', shoplist)
print ('mylist is', mylist)
# Notice that now the two lists are different
 
cs


동작 원리주석에 거의 모든 설명을 달아 두었습니다.

리스트와 같은 어떤 열거형이나 복잡한 객체 (정수형과 같이 단순 객체 를 제외하고)의 복사본을 생성하고 싶을 때에는, 슬라이스 연산자를 이용하여 복사본을 생성해야 합니다. 단순히 한 변수를 다른 변수에 할당하게 되면 두 변수는 같은 객체를 ''참조'' 하게 되며 실제 복사본이 생성되지 않습니다. 따라서 이것을 조심하지 않으면 문제가 발생할 수 있습니다.


10.8. 문자열에 대한 좀 더 자세한 설명

앞서 문자열에 대해 이미 상세히 다루었지만, 몇 가지 더 알아두면 좋을 것들이 있습니다. 문자열도 객체이므로 여러 메소드를 가지고 있는데 이를 통해 문자열의 앞 뒤 공백을 제거한다거나 하는 일들을 할 수 있습니다!


파이썬에서 사용되는 모든 문자열은 str 클래스의 객체입니다. 다음 예제에 이 객체가 제공하는 몇가지 유용한 메소드들의 용례가 나타나 있습니다. str 클래스의 모든 메소드의 목록을 확인하시려면 help(str) 을 실행해 보시기 바랍니다.

예제 ( ds_str_methods.py 로 저장하세요):


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
# This is a string object
name = 'Swaroop'
 
if name.startswith('Swa'):
    print ('Yes, the string starts with "Swa"')
 
if 'a' in name:
    print ('Yes, it contains the string "a"')
 
if name.find('war'!= -1:
    print ('Yes, it contains the string "war"')
 
delimiter = '_*_'
mylist = ['Brazil''Russia''India''China']
 
print (delimiter.join(mylist))
cs



동작 원리여기서는 문자열이 제공하는 여러 메소드에 대해 알아보았습니다. startswith 메소드는 문자열이 주어진 문자열로 시작하는지 여부를 반환합니다. in 연산자는 문자열에 주어진 문자열이 포함되어 있는지 확인하는데 사용합니다. 


find 메소드는 문자열 내에 포함된 특정 문자열의 위치를 반환합니다. 이 때 주어진 문자열을 찾지 못한 경우 find 는 -1 을 반환합니다. 또 str 클래스는 join 이라는 좋은 메소드를 가지

고 있는데, 이것은 주어진 문자열들을 해당 문자열을 구분자로 하여 결합한 하나의 큰 문자열을 만들어 반환해 주는 메소드입니다.



10.9. 요약

이 챕터에서는 파이썬의 여러 내장 자료구조들에 대해 상세히 알아보았습니다. 이 자료 구조들은 프로그램을 짧고 보기 쉽게 작성하는데 꼭 필요한 구성 요소들입니다.

이제 여러분은 파이썬의 여러 기본적인 문법에 익숙해졋을 것입니다. 이 다음부터는 실제 파이썬프로그램을 설계하고 작성해 보도록 하겠습니다.