자료 구조
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 |
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 |
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. 요약
이 챕터에서는 파이썬의 여러 내장 자료구조들에 대해 상세히 알아보았습니다. 이 자료 구조들은 프로그램을 짧고 보기 쉽게 작성하는데 꼭 필요한 구성 요소들입니다.
이제 여러분은 파이썬의 여러 기본적인 문법에 익숙해졋을 것입니다. 이 다음부터는 실제 파이썬프로그램을 설계하고 작성해 보도록 하겠습니다.