Summary: set, dict은 해시함수를 이용한 해시값을 저장하고있는 해시테이블이란 자료구조를 사용하기 때문에, 빠른 탐색이 가능함. 해시는 임의의 변수(문자열 등)를 숫자로 변환하여, 매핑하는 것을 의미함. 따라서, 임의의 변수가 오더라도 숫자로 매핑할 수 있고, 이 숫자로 매핑한 경우 인덱스처럼 사용할 수 있어 빠른 검색이 가능함.

 

 

해시함수를 이용한 해시값으로 매핑 예시. (ref: https://modeling-languages.com/robust-hashing-models/)

 

 

도입: 파이썬으로 자료를 저장하다보면 많은 양의 데이터를 저장해야하는 경우가 있다. 가령, 유전정보와 같이 1번 유전자의 100번째의 위치의 DNA가 바뀌었는지 여부 뿐만아니라... 101번, 10,000,000번째의 위치가 변경되었는지, 변경되었으면 무엇으로 변경되었는지 저장한다. 문제는 이렇게 많은 정보를 저장한 것을 읽어, 특정위치(Input으로 주어지는 위치)에 변이가 있었는지 찾아야한다면? 많은 연산을 해야할 것이다. 

 

 

 


해시란(hash)?

해시는 임의의 길이를 갖는 임이의 데이터를 고정된 길이의 데이터로 매핑하는 함수를 말한다. 쉽게 말해서, 입력값을 아무거나 넣더라도, 각기 다른 출력값을 반환하는 것이다(단, 출력값의 길이는 동일 // 입력값을 동일하게 할경우 출력값은 일관성 있게 동일). 예를 들어, 가장 쉬운 해시는 mod 연산이다. mod k 연산은 어떤 숫자를 k로 나눴을 때 나머지를 구하는 연산인데 이를 통해 해시를 구현할 수 있다. 아래의 예시와 같이 임의의 숫자 13, 3, 14, 23, 22를 7로 나눠 각각 나머지를 구했다. 이런식으로 각각 원소인 13을 6으로 매핑, 3을 3으로 매핑 14을 0으로 매핑하는 것처럼 원래 데이터를 매핑하는 것을 의미한다.

 

 

mod 7 (13) A -> G
mod 7 (3) G -> A
mod 7 (14) 0 T -> C
mod 7 (23) 2 C - > T
mod 7 (22) 1 C -> T

(Table 1. mod 7 연산의 결과)

 

 

 

그런 다음에, 6을 인덱스로 해서 "A->G"라는 데이터를 저장하고, 3에 "G->A"라는 것을 저장하듯 모든 배열을 위와같이 저장한다. 그러면, 14의 내용이 무엇인지 찾고자할때, 13, 3, 14, 23, 22의 중에 14를 찾아서 따라가는 것이 아니라, 1) 14을 mod 7 (14)을 해서, 0을 얻은다음, 2) 0의 인덱스에 무엇이 저장되어있는지 확인하면 된다. 이런 방법으로하면, 상당히 긴 배열에서도 13=14냐? 3=14이냐? 23이 23이냐?? 모두 1:1로 비교하는 방법(선형탐색)을 하지 않고, 바로 검색을 할 수 있다. 이 이유가 hash를 쓰는 이유이다. 

A -> G
G -> A
0 T -> C
2 C - > T
1 C -> T

 

 

그리고, 우리는 위의 6, 3, 0, 2, 1과 같은 값들을 해시값(hash value) 또는 다이제스트(Digest)라고 부르고, 해시값과 저장되어있는 값("A->G", "G-A"...)와 같이 위의 정보를 테이블형태로 저장하고 있는 자료구조를 해시테이블(hash table)이라고 한다.

 

 

 


Hash collision: 혹시나 해시값이 중복이면 어떻게되나?

 

해시충돌의 경우는 아래와 같다. 아래처럼 14의 값과 21을 해시함수 mod 7을 했을 경우 결과값(해시값)이 동일하다. 이 경우는 해시값이 중복인 경우 (예, 0인 경우), 14에서 부터 해시값이 나온건지 21에서부터 해시값이 0이 나온것인지 구분이 필요하다.

 

 

mod 7 (13) A -> G
mod 7 (3) G -> A
mod 7 (14) 0 T -> C
mod 7 (23) 2 C - > T
mod 7 (22) 1 C -> T
mod 7 (21) 0 T -> A

 

 

위의 예시만 봐도, 위의 mod 함수를 해시함수로 쓰는 경우 해시값(output)의 충돌은 발생한다. 가장 좋은 건 해시함수를 좋은 해시함수(결과물의 충돌이 거의 없는 함수)를 쓰는 것이 좋다. 그러나, 좋은 해시함수를 쓰더라도, 해시충돌이 발생할 확률은 언제나 존재한다. 이 원인에 대한 직관적인 이해는 비둘기집의 원리와 같다. 

 

비둘기집의 원리: "차량등록번호", "휴대전화번호"처럼 차량수/휴대전화가가 등록할수 있는 번호의 개수를 초과하는 경우와 같은 원리. 비둘기집이 9개가 있고, 각 비둘기집에는 한 마리씩 비둘기가 있을 수 있다고 가정해보자. 만일 비둘기가 10마리가 있다면 어떻게될까? 9개의 집 중, 하나는 두마리가 들어가야한다. 마찬가지로 "고정길이 문자열을 반환하는 해시값은 유한하다. 따라서, 임의의 가변길이 문자열의 개수 > 반환할 수 있는 고정길이 문자열이기에, 겹칠 수 있다"는 말이다. 예를 들어, 영어 알파뱃 26개인데 정보를 10개의 고정길이 문자열로 반환하는 해시함수를 만들었다고 하자. 해시함수가 반환할 수 있는 값은 26의 10제곱이다. 그러나 입력값을 0부터 ZZZZZZZZZZZZZZZZZZZZZ까지 엄청나게 많은 은 알파벳과 숫자의 조합이라면? 매핑할 수 있는 값의 종류가 모자르다.

 

Figure. Pigeons in holes.

자세한 내용은 아래를 참고하자 (https://en.wikipedia.org/wiki/Pigeonhole_principle)

따라서, 해시값이 충돌할 경우, 여러 방안이 존재한다. 체이닝, Open addressing (선형탐사, 제곱탐사, 이중해시).. 등 여러 방법이 존재한다. 가장 쉬운 예시는, 겹치는 해시값안에는 여러 변수를 일단 배열형태로 저장한 후에, 선형탐사하는 것이다. 일단 겹치는 경우, 저장하고 훨씬 적은 수를 선형탐사해서 찾겠다는 것과 같다.

 

 


Set, Dict 에서의 hash테이블: 파이썬의 set, dict자료구조는 해시테이블을 이용한다. 즉, dict의 key와 set의 원소를 x라한다면, hash(x)을 실행시켜 같으면 x원소가 반드시 존재한다는 것으로 활용할 수 있다. 아래는 set의 해시테이블을 직접 접근한 예시이다. 아래와 같이 파이썬의 내장함수는 해시함수를 제공하고 있다.

 

>>> hash
<function hash(obj, /)>

 

그리고, 파이썬의 객체는 hash함수값을 매직매서드로 가지고있다. 아래의 "a"라는 문자의 해시값은 "8870042888964940309"이다.

 

>>> "a".__hash__() 
8870042888964940309

 

그리고 set을 하나 만들어, 해시값을 소환해보자. set 내부에 해시테이블을 직접 접근하는 방법은 찾지 못했으나, 이와 유사하게 아래와같이 각 원소의 hash값을 알고있으면 이를 통해서 검색하면 매우 빠르다.

 

>>> myset = {"a", "b"}
>>> {element.__hash__() for element in myset}

{8703158610484096297, 8870042888964940309}

 

반응형

문자나 숫자를 컴퓨터끼리 송수신하기위해서는 문자나 숫자를 그대로 전송하는 것이 아니라, 이러한 숫자나 문자를 비트단위로 끊어서 전송하게된다. 예를들어 숫자 3을 보내기 위해서는 0011으로 전송하게된다. 우리가 알고 있는 디지털신호(0아니면 1)로 변환해서 보내기 용히하기 때문이다. 이때, 숫자를 16진수로 보내면 최대 한 전송단위 16까지를 보낼수있다. 이렇게 한 전송단위(16비트)을 다시 2진수로 변환하여 송수신할 수 있다. 즉, 파이썬 내부에서 다뤄지는 문자열, 숫자는 파이썬에서 생성된 객체이고, 이를 바로 트랜스포트에 그대로 싣는 것은 안된다는 것이다.

따라서, 우리는 객체를 byte으로 적절히 인코딩을 해줘서 보내고, 컴퓨터는 이를 디지털신호로 바꿔 전송한후, 컴퓨터가 16진수는 숫자를 4bit단위로 끊어서, 인식하게 된다. 아래와 같이, 16진법 앞에는 "0x"을 붙여주어, 사용한다. 앞에 접두사로 "0x"을 붙이는 이유는 컴퓨터 입장에서는 0001을 이라는 디지털 신호가 왔을때, 이게 2진법인지 8진법으로 해석해야하는지 16진법으로 해석해야하는지 모르기 때문이다. "0x"은 16진수로 신호를 해석하라는 의미가 된다.

 

그 다음에 아래의 16진법 표기법과 같이, 해석을한다. 16진법에서는 컴퓨터가 받아들이는 입장에서는 "10"에서의 "1"과 "01"에서의 "1" 동일하기 때문에, "10"을 A로 표기한다. 그 이후에 11은 B, 12은 C... 15은 F까지 표기를한다. 

10진법 Hex(16진법) Binary
0 0x00 0000
1 0x01 0001
2 0x02 0010
3 0x03 0011
4 0x04 0100
5 0x05 0101
6 0x06 0110
7 0x07 0111
8 0x08 1000
9 0x09 1001
10 0x0A 1010
11 0x0B 1011
12 0x0C 1100
13 0x0D 1101
14 0x0E 1110
15 0x0F 1111

 

Python 내부에서 이러한 구조체를 사용할 수 있게 내장함수가 구현되어있는데, stuct (structure)을 사용하면 구현할 수 있다. 예를 들어, 아래와 같이 5를 한 byte 사이즈로 표현하면 0x05가 될 것이고, 4개의 byte로 표현하면 0x가 4개로 이루어진 hexadecimal이 표현된다.

# hexadecimal.py
import struct

a = struct.pack('b', 5)
print(a)

b = struct.pack('i', 5)
print(b)

b'\x05'
b'\x05\x00\x00\x00'

 

아래와 같이 2개 이상의 문자또는 숫자를 보내야하는 경우에는 포맷에 그 크기에 맞춰 포맷팅을 보내면 된다. 그렇지 않으면 아래의 예시의 a와 같이 패킹을 할 수 없다. 5,5을 16진법으로 표현할 방법이 없기 때문이다. 

import struct

b = struct.pack('ii', 5, 5)
print(b)
# b'\x05\x00\x00\x00\x05\x00\x00\x00


a = struct.pack('b', 5, 5)
print(a)
# Traceback (most recent call last):
#   File "hexadecimal.py", line 3, in <module>
#     a = struct.pack('b', 5, 5)
# struct.error: pack expected 1 items for packing (got 2)
반응형

 

 

fixture 란?


단위 테스트를 위해서, fixture을 이용하면 사전에 미리 정의된 신뢰할만하고 일관성있는 테스트를 이용할 수  있다. fixture은 환경이나 데이터셋에 대해서도 테스트해볼 수 있다. 이 fixture은 "step"과 "data"을 정의하고 이 테스트를 

 

fixture을 이용한 서비스, 상태, 조작환경등이 인자(argument)을 통해 모두 접근가능하고, 각 fixture는 보통 파라미터를 지정해준다. 그리고 pytest을 이용할 때, 어떤 함수를 테스트할것인지 @pytest.fixture 와 같이 "@"데코레이터를 이용해서 설정할 수 있다.

 

@pytest.mark.parametrize(ids=?) 내 ids란?? 

parameterize로 전달해주는 파라미터에 대해서 string으로 각 아이디를 줄 수 있고, 이는 "-k"을 옵션을 이용해서 특별한 파라미터(케이스)에 대해서만 실행해볼 수도 있다.

 

@pytest.fixture(scope=??):


scope범위에 따라 데코레이터를 달아준 함수의 실행범위가 달라진다. 예를 들어, scope="function"인 경우는 데코레이터를 달아준 함수를 매번 call해서 사용하고, 소멸된다. 한편, scope="module"로 지정하면 ".py"파일 내에서는 무조건 1번만 생성된다. 한편 scope="session"인 경우는 최초 테스트 실행시 단 한번만 객체가 실행되며, 각 테스트에서 이 하나의 객체가 호출된다. 

Reference: https://velog.io/@gyuseok-dev/pytest-pytest.fixture

https://velog.io/@dahunyoo/Test-fixture-on-Pytest

 

yeild 사용


yield은 보통 제너레이터에서 lazy하게 데이터를 불러올떄 사용한다. pytest에서는 테스트 실해 후에 환경정리, 테스트 데이터들을 정리할 때 yield 을 이용할 수 있다. yield는 호출한 테스트케이스가 실행된 후 종료되어도 마지막까지 실행되게끔 해주는 키워드이다.

반응형

연속형변수의 구간화(Bucketized)

균등하게 자르는 방법이 있음. 아래와 같이 pandas.cut 메서드를 사용하면 8개의 구간으로 잘려 할당됨

import pandas as pd
from sklearn.datasets import load_iris

data = pd.DataFrame(load_iris()['data'], columns=load_iris()['feature_names'])
pd.cut(data['sepal width (cm)'], 8)

 

0      (3.2, 3.5]
1      (2.9, 3.2]
2      (2.9, 3.2]
3      (2.9, 3.2]
4      (3.5, 3.8]
          ...    
145    (2.9, 3.2]
146    (2.3, 2.6]
147    (2.9, 3.2]
148    (3.2, 3.5]
149    (2.9, 3.2]
Name: sepal width (cm), Length: 150, dtype: category
Categories (8, interval[float64]): [(1.998, 2.3] < (2.3, 2.6] < (2.6, 2.9] < (2.9, 3.2] < (3.2, 3.5] < (3.5, 3.8] < (3.8, 4.1] < (4.1, 4.4]]
반응형

a @ b: PEP465에서 권장(matrix multiplication). 

np.dot(a, b): numpy에서 사용을 권장하지 않음.

np.matmul: Matrix multiplication 

다 내적

e.g) shape (3, 2) x shape (2, 4) => (3, 4)

 

A * B: 

element wise multiplication 와 matrix multiplication 둘다 지원함

multiplication

matrix인 경우와 arrayt인 경우 다름

 

blog.finxter.com/numpy-matmul-operator/

 

반응형

CentOS

$sudo yum update
$sudo yum install python3-dev python3-pip  # Python 3
$sudo pip install --user --upgrade virtualenv

 

가상환경 생성하고 활성화 하기

$  python3 -m virtualenv venv   # 가상환경 폴더가 생성된것을 확인할 수 있다.

$ source "venv/bin/activate"  # 가상환경 활성화

(venv) $ deactivate  # 가상환경 비활성화

 

주피터 노트북 커널에 가상환경 추가하기

(venv) $ pip install ipykernel

$ python -m ipykernel install --user --name "만든폴더명" --display-name "디스플레이커널명"

패키지 설치는 주피터노트북에서 설치하기보다, 가상환경 돌리는 터미널에서 미리 설치하고 돌아와야 PATH문제가 없다

(venv) $ pip install [package_name]

반응형

+ Recent posts