본문 바로가기
프로그래밍 언어/Python

[Python] 유용한 데이터 구조

by 토마토베이컨수프 2021. 11. 7.

데이터 구조

파이썬에서는 하나의 데이터를 저장하는데에도 다양한 종류의 데이터 구조를 사용할 수 있습니다. 수많은 데이터 구조들 중 상황에 따라 어떤 방법을 쓰는 것이 메모리, 코드 성능, 시간 절약면에서 효율적인지에 대해 알아봅시다.

 


집합, 딕셔너리

집합set()을 이용해 구현할 수 있고, 다음과 같은 특성을 지닙니다.

  • 집합은 중복을 허용하지 않습니다.
  • O(1) 시간 복잡도로 요소에 접근할 수 있습니다.
  • 리스트의 분할, 조회와 같은 작업을 허용하지 않습니다.

따라서 집합은 항목에 자주 엑세스하고 O(1) 시간 복잡도로 항목에 엑세스를 설정해야 하는 경우에 사용하는 것이 좋습니다.

 

딕셔너리는 다양한 종류의 매핑이 필요한 데이터를 저장하고 빠르게 엑세스해야 하는 경우 사용하는 것이 좋습니다.

딕셔너리의 요소에 접근할 때 키가 존재하지 않으면 KeyError가 발생하는데요, defaultdict 사용 시 존재하지 않는 키에 접근하면 새로운 키를 생성하고 그 키에 기본값을 할당합니다.

from collections import defaultdict

languages = defaultdict(int)
languages["Go"]		# 0
languages = defaultdict(str)
languages["Go"]		# ""

languages.keys()	# dict_keys(['Go'])

 

딕셔너리를 이용해 파이썬에서는 존재하지 않는 스위치 구문을 구현할 수도 있습니다. 이런 형식의 코딩은 else if 조건을 여러개 추가하지 않아 좀 더 깔끔한 코드를 작성할 수 있게 도와줍니다.

def a_course(data):
	...
    return refined_data

def b_course(data):
	...
    return refined_data

def c_course(data):
	...
    return refined_data
    
data_refinery = {
    "a": a_course,
    "b": b_course,
    "c": c_course,
}

def refine_data(course, data):
    data_refinery[course](data)
    
refine_data("b", data)

 

딕셔너리끼리의 병합은 다음과 같이 **같은 언패킹 연산자를 이용해 쉽게 할 수 있습니다. 키 값은 중복되지 않으므로 중복키가 있을 시 그 값을 덮어쓰게 됩니다.

dict_1 = {"a": 1, "b": 2}
dict_2 = {"a": 2, "c": 3}
dict_3 = {**dict_1, **dict_2}
dict_3		# {'a': 2, 'b': 2, 'c': 3}

 

딕셔너리의 출력은 json모듈을 사용하면 깔끔하게 결과물을 받아볼 수 있습니다.

import json

data = {"a": 12, "b": {"x": 87, "y": {"t1": 21, "t2": 34}}}
print(json.dumps(data, sort_keys=True, indent=4))

 


네임드 튜플

네임드 튜플은 데이터의 이름을 가진 튜플로, 클래스를 생성할 필요 없이 가벼운 객체 타입을 생성할 수 있습니다. 인덱스 대신 이름을 사용해 속성값에 접근할 수 있으니 딕셔너리보다 좀 더 편리합니다.

from collections import namedtuple

# 마치 User라는 클래스를 생성한 것과 같은 효과를 줍니다
User = namedtuple("User", ["name","age","birthday"])
user1 = User(name="KM", age=26, birthday="1996-08-27")

user1.name	# KM
user1.age	# 26
user1.birthday	# 1996-08-27

테임드 튜플의 몇 가지 특징 및 제한 사항은 다음과 같습니다.

  • 튜플과 동일한 크기의 메모리를 사용하므로 튜플과 성능이 비슷합니다.
  • 값이 불변입니다. 따라서 내부 속성값을 바꿀 수 없습니다.
  • 필드 이름이 문자열이어야 합니다.

 


zip을 이용한 리스트 처리

zip()을 통해 두 개 이상의 리스트를 병렬적으로 처리할 수 있습니다. 다수의 리스트들을 한번에 이터레이팅할 수 있다는 장점이 있습니다.

list1 = ["a","b","c","d","e"]
list2 = [10, 20, 30, 40, 50]
list3 = ["A", "B", "C", "D", "E"]

data = []
for i, j, k in zip(list1, list2, list3):
    data.append([i, j, k])
    
print(data)  
"""
[["a", 10, "A"],
["b", 20, "B"],
["c", 30, "C"],
["d", 40, "D"],
["e", 50, "E"]]
"""

 


카운터

namedtuple과 마찬가지로 collections 내장라이브러리에서 제공하는 모듈로, 리스트 요소들의 빈도를 딕셔너리 형태로 반환해줍니다.

from collections import Counter

users = ["KM", "JH", "KM", "JS", "KC", "JH", "KM", "JS"]
counts = Counter(users)
counts		# Counter({'KM': 3, 'JH': 2, 'JS': 2, 'KC': 1})

 

Counter의 메소들 중 most_common()은 빈도수가 가장 많은 요소와 해당 개수를 반환하고,  elements()는 요소들의 개수만큼 반복되는 이터레이터를 반환합니다.

counts.most_common(1)	# [('KM', 3)]
for i in counts.elements():
    print(i)	# KM KM KM JH JH JS JS KC

 


큐와 스택

collections 내장라이브러리에서 제공하는 deque모듈을 사용하면 O(1)의 효율적인 성능으로 메모리를 추가 및 제거하는 큐와 스택을 한번에 구현할 수 있습니다.

from collections import deque

# deque 생성
deq = deque("abcdefg")	# deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])

# 오른쪽에 새 항목 추가
deq.append("h")		# deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
# 왼쪽에 새 항목 추가
deq.appendleft("i")	# deque(['i', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])

# 맨 오른쪽 요소 제거
deq.pop()	# "h"
# 맨 왼쪽 요소 제거
deq.popleft()	# "i"

# 모든 데이터 제거
deq.clear()	# deque([])

 


참고 자료

  • <클린 파이썬 : 효과적인 파이썬 코딩 기법, 수닐 카필 지음>

'프로그래밍 언어 > Python' 카테고리의 다른 글

[Python] 클래스 구조  (0) 2021.11.16
[Python] 제너레이터  (0) 2021.11.11
[Python] 파이썬 다운 코딩  (0) 2021.11.05
[Python] SOLID 원칙  (0) 2021.10.03
[Python] 파이썬 프로그래밍의 개발 지침  (0) 2021.10.01