728x90

파이썬에서 "enumerate" 함수는 반복 가능한 객체를 인자로 받아서 해당 객체의 요소들을 순회하면서, 각 요소의 인덱스와 값을 순서쌍으로 반환한다.

이러한 기능을 통해 코드 작성의 편의성과 가독성을 높일 수 있다.

enumerate(iterable, start=0)
  • iterable: 반복 가능한(iterable) 객체, 예를 들면 리스트(list), 튜플(tuple), 문자열(str), 딕셔너리(dictionary) 등이 있다.
  • start: 인덱스의 시작값을 설정합니다. 기본값은 0이다.

 

예제

다음과 같은 리스트가 있다.

fruits = ['apple', 'banana', 'cherry']

이 리스트를 "enumerate" 함수에 전달하면, 다음과 같은 enumerate 객체가 반환된다.

enumerate_fruits = enumerate(fruits)

이제 이 객체를 for 루프로 반복하면, 인덱스와 해당 요소값을 순서대로 출력할 수 있다.

for index, fruit in enumerate_fruits:
    print(index, fruit)

위의 코드를 실행하면 다음과 같은 결과가 출력다.

0 apple
1 banana
2 cherry

 

enumerate와 for 루프

"enumerate" 함수는 주로 for 루프와 함께 사용된다.

for 루프는 반복 가능한(iterable) 객체를 순회하면서, 각 요소를 처리하는데, 이 때 "enumerate" 함수를 사용하면 인덱스 정보를 함께 처리할 수 있기 때문에 유용하다.

for index, fruit in enumerate(fruits):
    print(f"Index {index}: {fruit}")

위 코드는 fruits 리스트의 요소를 순회하면서, 각 요소의 인덱스와 값을 출력한다. 

 

결론

"enumerate" 함수는 파이썬에서 매우 유용한 내장 함수 중 하나이다.

이 함수는 반복 가능한(iterable) 객체를 인자로 받아서 해당 객체의 요소들을 순회하면서, 각 요소의 인덱스와 값을 순서쌍으로 반환한다. 이러한 기능을 통해 코드 작성의 편의성과 가독성을 높일 수 있다. "enumerate" 함수는 for 루프와 함께 사용되며, for 루프를 사용할 때 인덱스 정보를 함께 처리할 때 유용하다. 이 함수를 사용하면 코드 작성이 더욱 간결해지고 가독성이 좋아진다.

 
728x90

'Python' 카테고리의 다른 글

[Python] collections - Counter  (0) 2024.07.06
[코테 알고리즘] 프로그래머스 고득점 Kit - 완전탐색  (0) 2024.03.18
Python - Set()  (0) 2023.03.04
[Python] DFS & BFS  (0) 2023.02.18
[Python] 자료구조 : 큐(Queue) 개념 및 사용  (0) 2023.02.18
728x90

파이썬 collections 모듈

 

  • collections모듈은 파이썬에서 일반적으로 제공되고 있는 dict, list, set, tuple의 대안을 제공해주는 특별한 데이터 컨테이너들의 집합이다. 
  • 큐를 이용하는 구현을 할 때 사용하는 deque 역시 collections 모듈에서 사용할 수 있다. 
  • collections 모듈에는 활용도 높아 보이는 container들이 많이 보였는데 오늘은 그중 Counter에 대해 공부해 보았다.

 

1. Counter 

  • Counter는 해시 가능한 객체를 세는 데 사용하는 딕셔너리 서브 클래스이다.
  • 여기서 해시란 다양한 길이를 가진 데이터를 고정된 길이를 가진 데이터로 매핑한다는 것이다.
    (이를 이용하여 특정한 배열의 인덱스나 위치, 위치를 입력하여 데이터의 값을 지정하거나 찾을 수 있다.)
  • 즉, 해시 가능한 객체라는 의미는 고정된 길이를 가진 데이터로 매핑이 가능한 데이터라는 뜻이고, 파이썬에서는 보통 Immutable 한 객체를 해시 가능한 객체라고 한다. 정리하자면, Counter는 해시 가능한 객체(Immutable 한 객체)를 세어주는 딕셔너리의 서브 클래스이다.
  • 리스트(list), 딕셔너리(dict), 집합(set) 등은 변경 가능한(mutable) 객체이다.
  • 해시 가능한 객체: 정수, 부동 소수점, 문자열, 튜플 등 변경 불가능한 객체
  • 해시 불가능한 객체: 리스트, 딕셔너리, 집합 등 변경 가능한 객체

 

1.2 Counter 사용법

  • 위에서 설명한 것처럼 Counter는 해시가능한 객체 요소가 딕셔너리 키로 저장되고 개수가 딕셔너리의 값으로 저장된다.
  • 여기서 개수는 0이나 음수를 포함하는 임의의 정숫값이 될 수 있다.
  • 문자열, 리스트, 튜플 또는 다른 반복 가능한(iterable) 객체를 인수로 받아 생성할 수 있다.
from collections import Counter

# 문자열을 이용해 Counter 생성
c = Counter("hello world")
print(c)
# 출력: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})

# 리스트를 이용해 Counter 생성
c = Counter([1, 2, 2, 3, 3, 3])
print(c)
# 출력: Counter({3: 3, 2: 2, 1: 1})

# 튜플을 이용해 Counter 객체 생성
data_tuple = (1, 2, 2, 3, 3, 3)
counter = Counter(data_tuple)

print(counter)
# 출력: Counter({3: 3, 2: 2, 1: 1})



1.3 Counter additional methods

  • elements()
    • 요소의 개수만큼 반복되는 순환자료형을 반환한다.
    • 여기서 주의할 점은 Collector 역시 Dict과 마찬가지로 순서가 존재하지 않기 때문에 처음 발견되는 순서대로 반환한다.
  • most_common([n])
    • 가장 갯수가 많은 것부터 적은 것 순으로 n개 나열한 리스트를 반환한다. (정렬의 기능)
c = Counter("hello world")
print(c.most_common(2))
# 출력: [('l', 3), ('o', 2)]
  • subtract([iterable-or-mapping])
    • 순환 자료형이나 다른 매핑 자료형으로부터 현재 카운터의 개수만큼 밴 후 카운터 객체를 반환한다.
    • 여기서 0이나 음수를 반환할 수 있다.

 

728x90

'Python' 카테고리의 다른 글

[Python] enumerate함수  (0) 2024.08.08
[코테 알고리즘] 프로그래머스 고득점 Kit - 완전탐색  (0) 2024.03.18
Python - Set()  (0) 2023.03.04
[Python] DFS & BFS  (0) 2023.02.18
[Python] 자료구조 : 큐(Queue) 개념 및 사용  (0) 2023.02.18
728x90

완전탐색(Brute Force)이란?

- 무식하게 모든 경우를 탐색해보는 알고리즘이다.

- 정확성은100% 보장되나 속도가 최악이라는 단점이 있다.

- 문제에서 주어진 데이터가 맹루 적을때만 사용 가능하다.

- 완전 탐색은 딱히 알고리즘이라고 할 게 없지만, 완전탐색을 하기 위한 기법들은 여러 개가 있다.


1. 단순 브루트포스

- for 문과 if문 등으로 모든 케이스를 조사해보는 방법이다.

- 코테에 단독적으로 나오지 않는다. pass!!


2. 순열

- 완전 탐색의 대표적인 유형이다.

- 서로 다른 n개 중 r개를 중복없이 선택하여 나열하는 경우의 수이다.

- 순서가 상관이 있고 [1,2,3] 과 [3,2,1]은 다른 것이다.


 

순열

from itertools import permutations
list(permutations([1,2,3], 2)) # permutations(값, 몇개로 묶을지)
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]

 

조합 

- 순서 상관 없다. [1,2] 나 [2,1] 은 같다고 봄.

from itertools import combinations
list(combinations([1,2,3], 2))
[(1, 2), (1, 3), (2, 3)]

 

중복순열

from itertools import combinations_with_replacement
list(combinations_with_replacement([1,2,3], 2))
[(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]

3. 재귀

- 자기 자신을 호출한다는 의미이다.

- 자신이 수행할 작업을 유사한 형태의 여러 조각으로 쪼갠 뒤 그 중 한 조각을 수행하고, 나머지는 자기 자신을 호출해 실행한다. 

- 반복문을 줄일 수 있지만 스택 오버플로우가 잘 발생한다는 점에서 주의해야 한다.

- 한번 함수의 상태를 저장하는 파라미터와 재귀 탈출 조건이 반드시 필요하고, return 문을 명확하게정의해야 한다.

 

ex. 간단한 재귀함수 예시(팩토리얼)

def recursive_factorial(n):
    if n <= 1:
        return 1
    return n * recursive_factorial(n-1)
    
print(recursive_factorial(5))
120

** 재귀함수는 주로 BFS/DFS에서 사용된다.


4. 비트마스크

- 2진수를 사용하여 연산하는 방식이다.

- 각 원소가 포함 or 불포함으로 구성되는 경우에 사용된다.

 

ex. [1,2,3,4] 의 부분집합을 만들때 비트마스크를 사용하면

[1, 2, 3, 4]
 1  1  1  1
 1  1  1  0
 1  1  0  1
 1  1  0  0
 ...

- 리스트의 길이만큼 비트 리스트를 만든 후에 원소가 포함되면 1, 아니면 0으로 구분해서 간단하게 표기할 수 있다.

- 비트 연산은 (&,|,^,~,<<,>>)이 가능하다.


5. BFS/DFS

- 이건 나중에 따로 정리


6. 백그래킹

- 해를 찾는 도중에 막히면 더 이상 깊이 들어가지 않고 이전 단계로 되돌아가서 해를 찾아나가는 기법이다.

- 불필요한 부분을 쳐내고(가지치기) 최대한 올바른 방향으로 나아가려는 방식이다.

- 백트래킹을 사용하는 대표적인 알고리즘이 DFS인데, DFS는 재귀를 통해 가능한 경로 중 유망한 경로만 찾아서 탐색을 수행한 뒤 다시 돌아와 그 중 가장 최적의 경로를 반환하는 것이다. 


* 완전탐색 시간복잡도

비트마스크 > DFS/BFS > Brute-Force > 재귀함수 > 순열 > 백트래킹

728x90

'Python' 카테고리의 다른 글

[Python] enumerate함수  (0) 2024.08.08
[Python] collections - Counter  (0) 2024.07.06
Python - Set()  (0) 2023.03.04
[Python] DFS & BFS  (0) 2023.02.18
[Python] 자료구조 : 큐(Queue) 개념 및 사용  (0) 2023.02.18
728x90

집합 자료형, Set()이란?

집합(set)은 집합에 관련된 것을 쉽게 처리하기 위해 만든 자료형이다.

집합 자료형은 다음과 같이 set 키워드를 사용해 만들 수 있다.

>>> s1 = set([1,2,3])
>>> s1
{1, 2, 3}

위와 같이 set()의 괄호 안에 리스트를 입력하여 만들거나 다음과 같이 문자열을 입력하여 만들 수도 있다.

>>> s2 = set("Hello")
>>> s2
{'e', 'H', 'l', 'o'}

비어 있는 집합 자료형은 s = set()로 만들수 있다.
>> print(s)
## 출력 ##
>> {}


집합 자료형의 특징

위에서 출력한 set("Hello")의 결과가 좀 이상하다. 분명 "Hello" 문자열로 set 자료형을 만들었는데 생성된 자료형에는 l 문자가 하나 빠져 있고 순서도 뒤죽박죽이다. 그 이유는 set에 다음과 같은 2가지 큰 특징이 있기 때문이다.

  • 중복을 허용하지 않는다.
  • 순서가 없다(Unordered).

중복을 허용하지 특징 때문에 set은 자료형의 중복을 제거하기 위한 필터로 종종 사용된다.

리스트나 튜플은 순서가 있기(ordered) 때문에 인덱싱을 통해 자료형의 값을 얻을 수 있지만 set 자료형은 순서가 없기(unordered) 때문에 인덱싱으로 값을 얻을 수 없다.

 

만약 set 자료형에 저장된 값을 인덱싱으로 접근하려면 다음과 같이 리스트나 튜플로 변환한후 해야 한다.

>>> s1 = set([1,2,3])
>>> l1 = list(s1)
>>> l1
[1, 2, 3]
>>> l1[0]
1
>>> t1 = tuple(s1)
>>> t1
(1, 2, 3)
>>> t1[0]
1

교집합, 합집합, 차집합 구하기

set 자료형을 정말 유용하게 사용하는 경우는 교집합, 합집합, 차집합을 구할 때이다.

우선 다음과 같이 2개의 set 자료형을 만든 후 따라 해 보자. s1은 1부터 6까지의 값을 가지게 되었고, s2는 4부터 9까지의 값을 가지게 되었다.

>>> s1 = set([1, 2, 3, 4, 5, 6])
>>> s2 = set([4, 5, 6, 7, 8, 9])

 

1. 교집합

s1과 s2의 교집합을 구해 보자.

>>> s1 & s2
{4, 5, 6}

"&" 기호를 이용하면 교집합을 간단히 구할 수 있다.

또는 다음과 같이 intersection 함수를 사용해도 동일한 결과를 리턴한다.

>>> s1.intersection(s2)
{4, 5, 6}

s2.intersection(s1)을 사용해도 결과는 같다.

 

2. 합집합

합집합은 다음과 같이 구할 수 있다. 이때 4, 5, 6처럼 중복해서 포함된 값은 한 개씩만 표현된다.

>>> s1 | s2
{1, 2, 3, 4, 5, 6, 7, 8, 9}

"|" 기호를 사용한 방법이다.

>>> s1.union(s2)
{1, 2, 3, 4, 5, 6, 7, 8, 9}

또는 union 함수를 사용하면 된다. 교집합에서 사용한 intersection 함수와 마찬가지로 s2.union(s1)을 사용해도 동일한 결과를 리턴한다.

 

3. 차집합

차집합은 다음과 같이 구할 수 있다.

>>> s1 - s2
{1, 2, 3}
>>> s2 - s1
{8, 9, 7}

빼기(-) 기호를 사용한 방법이다.

>>> s1.difference(s2)
{1, 2, 3}
>>> s2.difference(s1)
{8, 9, 7}

difference 함수를 사용해도 차집합을 구할 수 있다.


집합 자료형 관련 함수들

값 1개 추가하기(add)

이미 만들어진 set 자료형에 값을 추가할 수 있다. 1개의 값만 추가(add)할 경우에는 다음과 같이 한다.

>>> s1 = set([1, 2, 3])
>>> s1.add(4)
>>> s1
{1, 2, 3, 4}

값 여러 개 추가하기(update)

여러 개의 값을 한꺼번에 추가(update)할 때는 다음과 같이 하면 된다.

>>> s1 = set([1, 2, 3])
>>> s1.update([4, 5, 6])
>>> s1
{1, 2, 3, 4, 5, 6}

특정 값 제거하기(remove)

특정 값을 제거하고 싶을 때는 다음과 같이 하면 된다.

>>> s1 = set([1, 2, 3])
>>> s1.remove(2)
>>> s1
{1, 3}
728x90
728x90

※ DFS/BFS를 알기 전에 밑에 글부터 읽고오자!

 

https://veggie-garden.tistory.com/28


** DFS & BFS


* DFS(Depth First Search)란? == 깊이 우선 탐색

자료의 검색, 트리나 그래프를 탐색하는 방법. 한 노드를 시작으로 인접한 다른 노드를 재귀적으로 탐색해가고 끝까지 탐색하면 다시 위로 와서 다음을 탐색하여 검색한다. 

  • dfs는 그래프의 노드를 갈 수 있는 데 까지 끝까지 방문하는 것이다. ==> 그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘
  • 노드를 순서대로 목적지에 도달할 때 까지 방문하면서, 각 노드에서 또 방문할 수 있는 위치를 저장해 둔다.
  • 노드가 끝까지 갔다가 다시 한칸 뒤로 돌아와, 갈 수 있는 방향을 탐색한다.
  • 정리하면 특정한 경로로 탐색하다가 특정한 상황에서 최대한 깊숙이 들어가서 노드를 방문한 후, 다시 돌아가 다른 경로로 탐색하는 알고리즘
  • 따라서 재귀적이고 / 스택을 이용할 수 있다. ==> 스택 자료구조 이용

 

* BFS(Breadth First Search)란? == 너비우선탐색

자료의 검색, 트리나 그래프를 탐색하는 방법. 한 노드를 시작으로 인접한 다른 노드를 재귀적으로 탐색해가고 끝까지 탐색하면 다시 위로 와서 다음을 탐색하여 검색한다. 

  • bfs는  노드에서 인접한 노드를 먼저 방문한다. ==> 가까운 노드부터 탐색하는 알고리즘 
  • 한 노드에서 갈수있는 모든 경우의 수를 저장한 후, 순서대로 방문한다.
  • 이를 반복하기 위해서 큐를 사용한다. ==> 선입선출 방식인 큐 자료구조를 이용
  • 큐에 방문할 위치를 저장함으로써 순서대로 방문이 가능하다.

* DFS vs BFS

출처: https://iq.opengenus.org/dfs-vs-bfs/

DFS와 BFS의 차이점은 바로 노드 방문 순서이다. 

 

- BFS : 너비우선탐색

: 동작 방식 

1. 탐색 시작 노드를 큐에 삽입하고 방문 처리를 한다.

2. 큐에서 노드를 꺼내 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리를 한다.

3. 2번의 과정을 더 이상 수행할 수 없을 때까지 반복한다.

: 탐색 소요 시간 : O(N) 

 

 

- DFS : 깊이 우선 탐색

: 동작 방식

1. 탐색 시작 노드를 스택에 삽입하고 방문처리한다.

2. 스택의 최상단 노드에 방문하지 않은 인접 노드가 있으면, 그 인접 노드를 스택에 넣고 방문처리를 한다. 방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드를 꺼낸다.

3. 2번의 과정을 더 이상 수행할 수 없을 때까지 반복한다.

 

>> 방문 처리는 스택에 한번 삽입되어 처리된 노드가 다시 삽입되지 않게 체크하는 것. 방문처리를 함으로써 각 노드를 한번씩만 처리할 수 있다.


- 그래프를 표현하는 2가지 방법

 

1. 인접행렬 ( Adjacency Matrix )

: 2차원 배열로 그래프의 연결 관계를 표현하는 방식

: 메모리가 불필요하게 낭비됨

: 정보를 얻는 속도가 빠르다

 

2. 인접리스트 ( Adjacency List )

: 리스트로 그래프의 연결 관계를 표현하는 방식

: 메모리를 효율적으로 사용 ( 연결된 정보만을 저장 )

: 정보를 얻는 속도 느림 ( 연결된 데이터를 하나씩 확인해야하기 때문 )

 

 

==> 파이썬에서는 단순히 2차원 리스트를 이용하면 된다!

파이썬은 기본 자료형인 리스트 자료형이 append()와 메소드를 제공하기 때문!


** 실습


DFS와 BFS에 대해 더 이해하기 위해서는 직접 관련 코드를 보면서 연습해봐야한다.

나는 밑에 글을 참고하면서 코드 연습을 했기에 따로 작성은 하지 않을거고 밑에 글을 참고하면서 연습하면 될 듯하다.

https://gorokke.tistory.com/131

728x90
728x90

1. Queue란?

 

  •  가장 먼저 입력 된 데이터가 가장 먼저 출력되는 구조이다.
  • FIFO(First In, First Out)라고 부르기도 하며 반대로 LILO(Last In, Last Out)이라고 설명하기도 한다.
  • 큐에서 알아두어야 할 용어가 있는데 Enqueue(인큐), Dequeue(디큐)이다.

-. Enqueue : 큐에서 데이터를 입력하는 기능

-. Dequeue : 큐에서 데이터를 꺼내는 기능

  • 추가로 파이썬에서는 queue, deque라는 두가지 내장 모듈을 통해 큐를 구현할 수 있다. 

 

2. queue 내장 모듈을 통한 큐 구현 - 1 

 

  •  파이썬에서는 queue라는 내장 모듈을 제공한다. 아래 예시로 든 그림을 통해 코드를 작성해보았다.
  • put( )은 큐에 데이터를 넣을 때 사용하는 메서드, get( )은 큐에서 데이터를 꺼내는 메서드이다

 

 

<코드>

import queue

#큐 모듈의 큐 클래스 객체 선언
data = queue.Queue()
print(type(data))

#선언 된 큐 객체에 3개 데이터 입력하기 : 2,5,8
data.put(2)
data.put(5)
data.put(8)

#큐 객체에서 입력된 객체 하나씩 꺼내기 :FIFO
print(data.get())
print(data.get())
print(data.get())

 

<결과>

<class 'queue.Queue'>
2
5
8

 

숫자 2,5,8을 순서대로 넣고 get( )을 3번 하면 먼저 입력 된 순서대로 출력된다.

 

 queue 내장 모듈 내에는 기본적인 Queue( ) 클래스 외에도 LifoQueue( ), PriorityQueue( ) 클래스가 존재한다. 여기서 자세히 다루진 않겠지만 LifoQueue( )는 스택과 같은 LIFO 구조, PriorityQueue( ) 는 사용자가 우선순위를 지정한대로 데이터를 꺼낼 수 있는 구조라고 보면 될 것 같다.

 

 

2. deque 내장 모듈을 통한 큐 구현 - 2 

  • 큐를 구현할려면 위의 예시처럼 queue 를 호출해도 되지만,
    일반적으로 deque를 사용하여 큐를 구현하는 것을 추천한다.
  • append(): 큐의 끝에 요소를 추가
  • popleft(): 큐의 맨 앞에서 요소를 제거하고 반환
from collections import deque

queue = deque()

# 큐에 요소 추가 (enqueue)
queue.append(1)
queue.append(2)
queue.append(3)

print(queue)  # deque([1, 2, 3])

# 큐에서 요소 제거 (dequeue)
front_element = queue.popleft()
print(front_element)  # 1
print(queue)  # deque([2, 3])

 

deque의 장점

  • 양방향 작업의 효율성:
    • deque는 리스트와 달리 양쪽 끝에서 요소를 추가하거나 제거할 때 O(1)의 시간 복잡도를 가진다.
    • 리스트는 한쪽 끝에서만 O(1)의 시간 복잡도를 가지며, 다른 쪽 끝에서는 O(n)의 시간 복잡도를 가진다.
  • 유연성:
    • deque는 스택과 큐의 모든 연산을 효율적으로 지원하므로, 상황에 따라 스택과 큐의 역할을 모두 수행할 수 있다.

 

deque의 주요 메서드

  • 스택 연산
    • append(x): 스택의 맨 위에 요소 x를 추가
    • pop(): 스택의 맨 위에서 요소를 제거하고 반환
  • 큐 연산
    • append(x): 큐의 끝에 요소 x를 추가
    • popleft(): 큐의 맨 앞에서 요소를 제거하고 반환
  • 기타 메서드
    • appendleft(x): 큐의 맨 앞에 요소 x를 추가
    • popright(): 큐의 맨 끝에서 요소를 제거하고 반환

이와 같이 deque는 스택과 큐의 역할을 모두 효율적으로 수행할 수 있는 강력한 자료 구조이다.

 

deque와 queue 모듈의 비교

  • 용도:
    • deque: 양방향에서 빠른 삽입 및 삭제가 필요한 경우에 사용된다. 큐와 스택의 연산을 모두 효율적으로 수행할 수 있다.
    • queue 모듈: 멀티스레딩 환경에서 안전하게 큐를 사용해야 하는 경우에 사용된다. 특히, 스레드 간의 작업을 조정할 때 유용하다.
  • 성능:
    • deque: 대부분의 큐 및 스택 연산이 O(1)로 매우 효율적이다.
    • queue 모듈: 멀티스레딩을 지원하기 위해 추가적인 동기화가 필요하므로 약간의 성능 오버헤드가 있을 수 있다.
  • 사용 편의성:
    • deque: 단순한 큐 및 스택 연산을 수행할 때 간단하고 직관적인 인터페이스를 제공한다.
    • queue 모듈: 멀티스레딩 환경에서 안전하게 사용할 수 있지만, 단일 스레드 환경에서는 다소 복잡할 수 있다.

결론

  • 일반적인 큐 또는 스택 기능이 필요하고 단일 스레드 환경에서 사용한다면 collections.deque를 사용하는 것이 좋다.
  • 멀티스레딩 환경에서 안전하게 큐를 사용해야 한다면 queue 모듈을 사용하는 것이 적합하다.
728x90
728x90

해시란?

해시/해쉬(Hash)는 해시 테이블로 Key, Value를 매핑해서 데이터를 저장하는 자료구조이다.

파이썬에서는 기본적으로 제공되는 딕셔너리 자료형이 해시 테이블과 같은 구조이다.!

 

용어

키(Key) - 고유의 값으로 해시 함수의 Input, 다양한 길이의 값이 될 수 있다.

해시테이블(Hash Table) 또는 해시 맵(Hash Map) - Key와 Value를 매핑해서 데이터를 저장하는 자료구조

해시 함수(Hash Function) - 임의의 값을 고정된 길이의 데이터로 변환하는 함수. 다양한 길이의 키를 고정된 길이의 해시로 변환시키므로 저장소를 효율적으로 운영

해시(Hash) - 해시함수의 Output으로 해시값과 매칭되어 버킷에 저장

해시 값(Hash Value) 또는 해시 주소(Hash Address) - Key에 해시함수를 적용하여 얻는 해시 값

버킷(Bucket) - 한 개의 데이터를 저장할 수 있는 공간

 

 

다양한 길이를 갖고있는 키 값에 해시 함수를 적용시키면 00, 01, 02 등과 같이 고정된 길이의 데이터로 변환한다. 이렇게 변환된 데이터가 해시 값이고 버킷에는 키와 매핑된 원래 데이터를 저장한다. 결과적으로 변환된 키값과 버킷에 매핑되어 있는 데이터를 해시라 하고이러한 자료구조를 해시 테이블 이라 한다.

해시 언제 사용하면 좋을까?!

해시를 사용하면 좋을 때를 소개해드리고자 합니다 : )

 

1. 리스트를 쓸 수 없을 때 

리스트는 숫자 인덱스를 이용하여 원소에 접근하는데 즉 list[1]은 가능하지만 list['a']는 불가능합니다.

인덱스 값을 숫자가 아닌 다른 값 '문자열, 튜플'을 사용하려고 할 때 딕셔너리를 사용하면 좋습니다.

 

2. 빠른 접근  / 탐색이 필요할 때 

아래에서 표로 정리해 보여드릴 예정이지만, 딕셔너리 함수의 시간복잡도는 대부분 O(1)이므로 아주 빠른 자료구조 입니다!

 

3. 집계가 필요할 때

원소의 개수를 세는 문제는 코딩 테스트에서 많이 출제되는 문제입니다. 이때 해시와, collections 모듈의 Counter 클래스를 사용하면 아주 빠르게 문제를 푸실 수 있을 것입니다.

 

딕셔너리와 리스트의 시간 복잡도 차이

아까 위에서 딕셔너리의 시간 복잡도는 대부분 O(1)을 갖는다고 말씀드렸는데 리스트와 시간복잡도를 비교해보도록 하겠습니다.

Operation Dictionary List
Get Item O(1) O(1)
Insert Item O(1) O(1) ~ O(N)
Update Item O(1) O(1)
Delete Item O(1) O(1) ~ O(N)
Search Item O(1) O(N)

List에 비해 Dictionary가 매우 빠른 시간복잡도를 갖는 것을 보실 수 있습니다.

즉 , 원소를 넣거나 삭제, 찾는 일이 많을 때에는 딕셔너리를 사용하는 것이 좋습니다 

※ 파이썬 3.7 이상부터 딕셔너리는 원소가 들어온 순서를 보장합니다. 반면, 3.7 미만은 순서를 보장하지 않습니다. 딕셔너리를 이용해 문제를 풀 때에는 버전에 유의하세요.

 

Dictionary 사용법

📌 Init

{}를 사용하거나 dict함수 호출 시 빈 딕셔너리를 선언할 수 있습니다. 

key - value 쌍을 갖는 dictionary 선언도 바로 가능합니다.

# 빈딕셔너리 생성
dict1 = {} # {}
dict2 = dict() # {}
# 특정 key-value쌍을 가진 dictionary 선언

Dog = {
    'name': '동동이',
    'weight': 4,
    'height': 100,
}


'''
{'height': 100, 'name': '동동이', 'weight': 4}
'''
# dictionary를 value로 가지는 dictionary 선언

Animals = {
    'Dog': {
        'name': '동동이',
        'age': '5'
    },
    'Cat': {
        'name': '야옹이',
        'weight': 3
    }
}


'''
 { 'Dog': { 'name': '동동이', 'age': '5'},
   'Cat': {'name': '야옹이','weight': 3 }}
'''

 

📌 Get

Dictionary에서 원소를 가져오는 방법은 두가지 입니다.

1. [] 사용하기

2. get 메소드 이용하기

get 메소드는 get(key,x) 로 사용하실 수 있습니다. 이는 '딕셔너리에 key가 없는 경우, x를 리턴해줘라' 라는 용도입니다.

딕셔너리를 카운터하는 (집계) 경우에 get함수가 유용하게 사용됩니다.

# [] 기호 사용해 원소 가져오기

dict = {'하이': 300, '헬로': 180, 3: 5}
dict['헬로'] # 180
# get 메소드를 아용해 원소 가져오기 1
# 딕셔너리에 해당 key가 없을때 Key Error 를 내는 대신, 특정한 값을 가져온다.

dict = {'하이': 300, '헬로': 180}
dict.get('동동', 0) # 0
# get 메소드를 아용해 원소 가져오기 2
# 물론, 딕셔너리에 해당 key가 있는 경우 대응하는 value를 가져온다.

dict = {'하이': 300, '헬로': 180}
dict.get('헬로', 0) # 180

 

📌 Set

딕셔너리에 값을 집어넣거나, 값을 업데이트 할 때 [ ] 를 사용합니다.

# 값 집어넣기

dict = {'김철수': 300, 'Anna': 180}
dict['홍길동'] = 100
dict #{'Anna': 180, '김철수': 300, '홍길동': 100}
# 값 수정하기1

dict = {'김철수': 300, 'Anna': 180}
dict['김철수'] = 500
dict # {'Anna': 180, '김철수': 500}
# 값 수정하기2

dict = {'김철수': 300, 'Anna': 180}
dict['김철수'] += 500
dict # {'Anna': 180, '김철수': 800}

 

📌 Delete

딕셔너리에서 특정 key값을 지울 때에 다음과 같은 방법을 사용할 수 있습니다.

1. del dict_obj[key]

del은 키워드로써, 만약 딕셔너리에 key가 없다면 keyError가 발생합니다.

2. pop(key[,default])

pop은 메소드로써, pop메소드는 key 값에 해당하는 value를 리턴합니다. key가 없다면 두번째 파라미터인 default를 리턴합니다.

만약 default 설정하지 않았을 시엔 keyError가 발생합니다.

# del 이용하기 - 키가 있는 경우
dict = {'김철수': 300, 'Anna': 180}
del dict['김철수']

dict #{'Anna': 180}
# del 이용하기 - 키가 없는 경우 raise KeyError
my_dict = {'김철수': 300, 'Anna': 180}
del my_dict['홍길동'] 
'''
keyError 발생!
'''
# pop 이용하기 - 키가 있는 경우 대응하는 value 리턴
my_dict = {'김철수': 300, 'Anna': 180}
my_dict.pop('김철수', 180) # 300
# pop 이용하기 - 키가 없는 경우 대응하는 default 리턴
my_dict = {'김철수': 300, 'Anna': 180}
my_dict.pop('홍길동', 180) # 180

 

📌 Iterate

딕셔너리를 for문을 이용하여 조회할 때 두가지 방법이 존재합니다.

1. key로만 순회하기

2. key, value 동시 순회하기 (items() 사용)

# key로만 순회
dict = {'김철수': 300, 'Anna': 180}
for key in dict:
    print(key)
    # 이 경우 value를 찾고 싶으면 dict[key] 와 같이 접근을 따로 해주어야.

'''
김철수
Anna
'''
# key-value 동시 순회

dict = {'김철수': 300, 'Anna': 180}
for key, value in dict.items():
    print(key, value)

'''
김철수 300
Anna 180
'''

 

그 밖에 딕셔너리 유용한 팁

1. 특정 key가 딕셔너리에 있는지 없는지 조회할 때 - in 사용하기

dict = {'김철수': 300, 'Anna': 180}
print("김철수" in dict) #True
print("김철수" not in dict) # False

 

2. key 또는 value만 뽑아내는 방법

1. key 만 : keys()

# key를 extract - keys 사용


my_dict = {'김철수': 300, 'Anna': 180}
my_dict.keys() # dict_keys(['김철수', 'Anna'])

2. value만 : values()

# value를 extract - values 사용


my_dict = {'김철수': 300, 'Anna': 180}
my_dict.values() # dict_values([300, 180])

3. key - value 모두 : items()

# key, value 쌍을 extract - items 사용


my_dict = {'김철수': 300, 'Anna': 180}
my_dict.items() # dict_items([('김철수', 300), ('Anna', 180)])
728x90
728x90

* Stack(스택)

  • 스택은 LIFO(Last - In - First - Out)로써 가장 나중에 들어온 자료가 가장 먼저 꺼내어진다라고 이해하면 쉽다.

 

1. 정의

  • 스택은 데이터를 한 쪽 끝에서만 넣고 뺄 수 있는 자료 구조이다.
  • 큐는 대표적으로 FIFO 정책을 사용하지만 스택은 LIFO(Last In First Out) 후입선출 정책을 사용한다.
  • 즉 가장 나중에 쌓은 데이터를 가장 먼저 제거할 수 있다. 또한 단어 그대로 쌓아 올린다는 것을 뜻한다.
  • 프링글스 과자를 생각하면 쉽다.

 

2. 스택의 장단점

  • -장점 
    • 구조가 단순해서 구현이 쉽다.
    • 데이터 저장/읽기 속도가 빠르다.
  • -단점
    • 스택에 데이터를 쌓을 수 있는 최대 범위를 미리 정해놓아야한다.
    • 이로인해 저장 공간의 낭비가 발생할 수 있다.

 

3. 스택의 메서드

파이썬 리스트 기능에서 스택은 두 가지 메서드를 제공한다.

- append(push) : 데이터를 집어넣기

- pop : 데이터를 빼기

 

4. 스택의 쓰임

- 웹브라우저의 방문기록(뒤로가기) : 가장 나중에 열린 페이지부터 다시 보여준다.
- 역순 문자열 만들기
- 실행 취소(undo)

 

* Python에서 Stack 구현하기

Python에서는 기본 클래스인 list를 통해 스택 자료구조를 쉽게 구현할수있다.

 

list pop / push 시간복잡도

pop Item list.pop( ) O(1)
push Item list.append(item) O(1)

 

** list를 이용하여 Stack 구현하기 - 1

stack - Init

list는 파이썬에서 자주 쓰이기 때문에 리스트 자료형 생성은 매우 익숙할거같다.

stack = [] # []
stack = [1,2,3] # [1,2,3]

 

stack - push

스택에 원소를 넣을 때에는 append 메소드를 이용해 리스트의 가장 마지막에 원소를 넣으실 수 있다.

stack = []
stack.append(4) # [4]
stack.append(2) # [4,2]

 

stack - pop

스택에 원소를 제거할 때는 pop 메소드를 이용하여 리스트의 가장 마지막 원소를 제거한다.

이때 pop메소드에 의해 제거된 원소를 리턴 받는다.

리스트의 pop(0) 연산은 O(n) 시간 복잡도를 가진다. 즉, 리스트의 첫 번째 요소를 제거하면, 나머지 모든 요소를 왼쪽으로 이동해야 하기 때문에 큐의 크기가 커질수록 성능이 저하된다.

stack = [1,2,3,4]
top = stack.pop() # stack [1,2,3]

print(top) # 4

 

stack - top

스택에서 원소를 제거하지 않고 가져오기만 할 때에는 [-1]을 이용하여 찾을 수 있다.

stack = [1,2,3,4]
top = stack[-1]# 4 , stack [1,2,3,4]

print(top) # 4

 

 

** deque를 사용하여 스택 구현하기 - 2

 

from collections import deque

stack = deque()

# 스택에 요소 추가 (push)
stack.append(1)
stack.append(2)
stack.append(3)

print(stack)  # deque([1, 2, 3])

# 스택에서 맨 뒤의 요소 제거 (pop)
top_element = stack.pop()
print(top_element)  # 3
print(stack)  # deque([1, 2])

# 스택에서 맨 앞의 요소 제거 (popleft)
print(queue.popleft())  # 1
print(queue.popleft())  # 2
print(queue.popleft())  # 3

 

  • append(): 스택의 맨 위에 요소를 추가한다.
  • pop(): 스택의 맨 위에서 요소를 제거하고 반환한다.
  • popleft() : 스택의 맨 앞의 요소를 제거하고 반환한다. O(1) 시간 복잡도를 가지며, 이는 리스트의 pop(0)보다 훨씬 효율적이다. 따라서 큐의 크기가 커져도 성능이 일정하게 유지된다.

 

728x90

+ Recent posts