💡해시 테이블

  • 해시 테이블의 연산은 대부분 분할 상환 분석에 따라 시간 복잡도가 O(1)에 달하는 빠른 연산을 보장한다.
  • 데이터 양에 큰 영향을 받지 않고 연산의 시간 복잡도가 동일하게 작다.

 

💡해시

  • 해시 함수란 어떤 데이터든지 특정 고정 크기로 데이터를 변환할 수 있는 함수를 의미한다.
    • 해시 함수는 주로 무작위화 함수, 손실 압축, 암호, 체크섬 같은 개념과 혼용되서 사용된다.

  • 해싱이란 해시 함수를 활용한 데이터를 저장한 해시 테이블에서 빠르게 인덱싱 하는 작업을 의미한다.
    • 해싱은 데이터를 가능한 빠르게 찾고 저장할 수 있는 기법으로 사용된다.
    • 최적의 검색을 요구하는 기능에 해싱 기술이 사용된다.

  • 좋은 해시 함수의 특징
    • 해시 함수 값 충돌의 최소화
    • 쉽고 빠른 연산
    • 해시 테이블에 해시 값 균일하게 분포
    • 사용할 키의 모든 정보를 활용하여 해싱 작업

  • 함수 값 충돌 최소화에 대한 고찰
    • 생일이 같은 사람이 2명 이상일 확률은 23명만 모여도 50%가 넘는다. 가능성 있는 생일이 365일이기 때문에 366명이 함께 있어서 같은 생일이 2명 이상일 확률이 50%가 넘을 것으로 생각하지만 생각 이상으로 중복이 많다.
    • 증명 (10만번 테스트 가정)
import random

TRIALS - 100000
same_birthday = 0

for _ in range(TRIALS):
	birthdays=[]
    for i in range(23):
    	birthday=random.randint(1,365)	# ==> 365일 중 random 값 추출
        # 현재 birthdays 중에서 같은 숫자가 있으면 same_birthday 업데이트
        if birthday in birthdays:
        	same_birthday += 1
            break
        # 기존 생일 중에 없다면 birthdays 업데이트
        birthdays.append(birthday)
        
# 같은 생일이 나올 확률 출력
print("확률 : {}%".format(same_birthday / TRIALS * 100))
  • 비둘기 집 원리
    • 9개 있는 비둘기 집에 10마리 비둘기들이 온다면 충돌은 1번 일어날 것으로 기대할 수 있다.
    • 하지만 처음 1마리가 들어간 곳으로 9마리 모두 들어가려다가 충돌이 일어날 수 있다. (중복 의미)
  • 로드 팩터
    • 해시 테이블에 저장된 데이터 개수(m)를 버킷의 개수만큼 나눈 것
    • 로드 팩터 값을 통하여 해시 함수가 제 역할을 다하는지 확인할 수 있음
    • 하지만 로드 팩터 값이 증가할 수록 해시 테이블의 성능은 더 감소한다.

 

  •  충돌 에러
    • 해시 함수가 아무리 정교해도 해싱 작업에서 동일한 결과로 충돌한다면, 개별 체이닝 기술을 활용하여 값을 연결리스로 연결 수 있다.
    • 개별 체이닝 기술
      - 키의 해시 값을 계산한다.
      - 해시 값을 활용하여 배열의 인덱스를 구한다.
      - 같은 인덱스가 있을 경우 연결 리스트로 연결한다.
    • 잘 구현할 경우 작업 시간 복잡도가 O(1)로 간단히 끝날 수 있지만 경우에 따라서는 모든 해시들이 충돌을 일으켜 O(n)이 될 수도 있다.

 

💡 로드 팩터

  • 로드 팩터란 해시 테이블의 데이터 개수를 해시 테이블의 전체 버킷의 양으로 나눈 값을 의미
  • 로드 팩터라는 개념을 기준으로 해시 테이블의 공간 재할당 및 크기 조정, 해시 함수 재작성 여부를 판단
  • 보통 로드 팩터의 기본값은 0.75 정도로 잡고 있으며(Java 기준) 로드 팩터의 수치가 올라갈수록 해시 테이블의 성능은 감소

 

💡 해시 함수

  • 해시 함수란 해시 테이블에 데이터를 인덱싱하기 위해 변환하는 함수를 의미
  • 해당 해시 함수로 인덱싱하는 과정을 해싱이라고 부른다.
  • 다양한 알고리즘이 있지만 기본적으로 나눗셈 방식을 의미하는 모듈로 연산으로 정수형 해싱 기법을 적용.
  • 파이썬에서 모듈로 연산은 k % n 로 표기함

 

💡 해시 값 충돌 해결 방법

  1. 개별 체이닝
    • 입력 값의 해시 값이 동일하여 충돌할 경우 기존 해시 값에 연결리스트로 연결하는 방식
    • 해시 테이블 구조의 원형이기도 하며, 가장 전통적인 방식으로 인식
    • 해시 테이블의 크기에 영향 받지 않으며, 무한적으로 저장할 수 있다.
    • 작동 원리
      • 해시 함수를 통해 해시값을 변환한 후 해당 값의 인덱스를 활용하여 값을 확인했을 때, 이미 값이 있다면, 연결 리스트로 연결
  2. 오픈 어드레싱
    • 해시 값 충돌 발생 시 탐사를 통해 빈 공간을 찾아 배정하는 방식
    • 해시 테이블 전체 공간 이상을 저장할 수 없다.
    • 모든 데이터가 중복 없이 해시값과 일치하는 주소에 저장된다.
    • 다양한 알고리즘 방식이 있지만 기본적으로 선형 탐사 방식이 있다
    • 선형 탐사 방식은 충돌이 발생한 해당 위치부터 순차적으로 탐사하며 빈 공간을 찾는다.
    • 전체적으로 간단하고 성능이 좋지만 예상할 수 있는 문제점으로는 충돌 발생한 공간부터 순차적으로 탐사하다 보니 해시값의 인덱스가 특정 공간에 몰릴 수 있는 우려가 있다. 이로 인해 클러스터링 현상이 발생하면서 해싱의 효율을 떨어뜨릴 수 있다.

'Develop > 자료구조' 카테고리의 다른 글

[자료구조] 우선순위 큐 구현  (0) 2023.01.17

💡우선순위 큐

  • 기존 큐, 스택과 같은 자료구조와 비슷한 추상 자료형이지만 특별히, 각 요소의 우선순위와 연관돼 있다.
  • 우선순위 큐는 특정 조건인 우선순위에 따라 값을 추출하는 자료구조다.
  • 대표 예시로 최대값 추출을 들 수 있다
    • [1,4,2,7,3] 이라는 값에서 최대값을 추출하는 우선순위 큐가 있다고 가정하면, 남아 있는 값들의 최대값을 우선순위로 추출하여 7, 4, 3, 2, 1 순으로 추출 될 것이다.
  • 여기서 시간복잡도의 개념을 고려하면 기본 정렬 기능을 통해 O(n) 시간복잡도를 기대할 수 있다.
  • 하지만 더 효율적인 방법으로 힙 정렬 등 힙 자료구조와 연동으로 새로운 접근도 가능할 수 있다는 사실을 인지하자.

 

💡우선순위 큐 활용 문제

  • k개의 정렬된 리스트를 1개의 리스트(정렬된 상태)로 병합하기
    (Merge all the linked-lists into one sorted linked-list and return it.)
  • 예시
Input: lists = [[1,4,5],[1,3,4],[2,6]]
Output: [1,1,2,3,4,4,5,6]
Explanation: The linked-lists are:
[
  1->4->5,
  1->3->4,
  2->6
]
merging them into one sorted list:
1->1->2->3->4->4->5->6

 

💡우선순위 큐 아닌 단순 풀이

  • 나만의 풀이
  • 리트코드에서는 통과할 수 없다.
  • 왜냐면 input 값으로 들어오는 lists값이 ListNode라는 연결리스트 자료구조이기 때문에 단순 List로 해결 X
def simple_solution(lists):
	"""
    :type lists: List[ListNode]
    :rtype: ListNode
    """
    total_values=[]
    for each_list in lists:
        total_values.extend(each_list)
    total_values.sort()
    return total_values

 

💡우선순위 큐 구현

  • 우선순위 큐는 heapq 자료구조와 관련이 깊다
  • python은 PriorityQueue 클래스를 지원하고 있지만 이 클래스 내부에서도 heapq 모듈을 활용하여 우선순위 큐를 구현하고 있다.
  • PriorityQueue 클래스는 멀티 스레드 활용 시 스레드 세이프 기능을 제공하고 있어 스레드 활용 프로그래밍 시 안전한 작동을 보장한다.
  • 하지만 실제 파이썬 특성상 멀티 스레드 활용 시 세이프 기능이 큰 의미가 없으며, 활용도가 낮다.
  • 그래서 대부분 heapq를 활용하여 구현한다.
  • heap 자료구조는 최소값을 가지고 올 수 있고, heappop 실행할 때마다 내부 값을 재정렬한다.
from heapq import heappush, heappop

# Definition for singly-linked list.

class ListNode(object):
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution(object):
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        # 초기 값이 [None, None] 이 상태로 들어간다
        root = result = ListNode(None)
        """
        print(result.val)   # ==> None
        print(result.next)  # ==> None
        """
        heap = []

        # 각 연결 리스트의 루트를 힙에 저장한다
        for i, lst in enumerate(lists):
            # 이 말은 lists 노드의 i번째 값이 있으면(None이 아니면)
            if lists[i]:
                # heappush의 인자값이 중복이 있다면, 에러를 발생하기 때문에
                # 중복된 값을 구분할 수 있는 추가 인자값이 필요하다.
                # heappush 활용해서 값을 추가할 때 heap 정렬의 구조에 따라 들어가는걸 확인할 수 있음
                heappush(heap, (lists[i].val, i, lists[i]))
                """
                Input : [ListNode([1,4,5]),ListNode([1,3,4]),ListNode([2,6])]

                Output :
                [([1, 3, 4], 1, <__main__.ListNode object at 0x0371A590>), 
                 ([1, 4, 5], 0, <__main__.ListNode object at 0x0371A570>), 
                 ([2, 6], 2, <__main__.ListNode object at 0x0371A1F0>)]
                """
        
        # heap 자료구조 안에 데이터가 다 없어질때까지 로직 구현
        while heap:

            # heappop으로 값을 가져오면 가장 작은 노드의 연결 리스트부터 차례대로 나온다.
            # 여기서 node는 tuple type  ex) ([2, 6], 2, <__main__.ListNode object at 0x0371A1F0>)
            node = heappop(heap)
            # 위에서 설정한 연결 리스트의 index값
            idx = node[1]
            # 연결 리스트로 정의된 객체 자체를 정답 값으로 업데이트
            result.next = node[2]   # => ex) <__main__.ListNode object at 0x0371A1F0>
            
            # result 값 갱신
            result = result.next
            
            if result.next:
                # heap 자료구조에 다시 추가한다.
                heappush(heap, (result.next.val, idx, result.next))

'Develop > 자료구조' 카테고리의 다른 글

[자료구조] 해시 테이블 기본 개념  (0) 2023.01.18

Flask Error Handler 활용 배경

  • Python 마이크로서비스 배포로 Flask 웹프레임워크 주로 사용
  • Flask에서 제공하는 Error Handler 활용
  • 클라이언트에 보내는 에러 메세지를 커스터마이징하여 출력 세팅
  • 에러 발생 가능한 모든 4xx, 50x 에러 처리 필요
  • flask에서 제공하는 _aborter 라이브러리 활용

 

코드 구현

  • error_handler.py
from flask import Flask, jsonify
from werkzeug.exceptions import HTTPException, _aborter, default_exceptions

def error_app(app):
    """
    Error Handler 정의
    """
 
    def _error_handling(error):
        
        # HTTP Exception인 경우
        if isinstance(error, HTTPException):
            result = {
                "error_code" : error.code,
                "description" : error.description,
                "message" : str(error)
            }
        
        # 그 외 나머지 Exception인 경우
        else:
            # 500 에러로 mapping 돼 있는 error message를 추출
            description_500 = _aborter.mapping[500].description
            result = {
                "error_code" : 500,
                "description" : description_500,
                "message" : str(error)
            }
        
        # result dict 데이터를 json으로 변경
        res = jsonify(result)
        
        # response의 status_code를 result의 error_code 값으로 업데이트
        res.status_code = result['error_code']
        
        return res
    #####################################################################
    
    # error handler 등록
    for code in default_exceptions.keys():
        app.register_error_handler(code, _error_handling)
        
    return app

 

Flask app 소스코드

  • app.py
# 위에서 작성한 코드 패키지 형식으로 활용
from error_handler import _error_handling

# Flask app을 _error_handling으로 감싸주기
app = _error_handling(Flask(__name__))

 

FastAPI

  • python의 웹프레임워크의 한 종류로서 최근 급부상하고 있다.
  • 기존에는 flask를 통해 가벼운 앱을 빠르게 구축했었다.
  • FastAPI의 신박함을 보고, 새로운 기술을 배우듯 도전했다.
  • 매우 쉬운 구조도 공식 문서도 나름 잘 설명하고 있어서 적용하는 데 어렵지 않았다
  • uvicorn 과 gunicorn의 적절한 조화로 비동기 방식의 웹 서버 구축을 할 수 있었다.

 

Nginx

  • 서버사이드 스크립트인 python 소스코드와 통신을 하기 위해서는 proxy를 받아주고 뿌려주는 역할이 필요하다.
  • 정확한 개념은 더 공부가 필요하지만, 우선 python으로 개발한 기능을 Rest API로 통신하기 위해서는 html 통신 규약을 전달해주는 nginx가 필요하다.

 

Docker

  • 컨테이너 기반의 독립적인 서버라고 인식하고 있다.
  • 물론 정확한 개념을 따지자면 서버라고 정의할 수는 없지만, 어떤 OS 환경에서든 이미지로 생성한 개발환경을 바로 사용할 수 있다.
  • docker를 활용하지 않는다면, 클라우드 서버를 새로 생성할 때 해당 서버 안에 nginx부터 시작해서 필요한 모든 개발환경들을 설치 및 세팅해줘야 한다.
  • 처음은 재밌게 하는데 이 작업이 반복되면 매우 귀찮다.
  • 약 3번정도 똑같은 개발환경 세팅을 반복해보니 docker쪽에 눈이 갔다.
  • 아래 나올 내용은 새로 인스턴스를 새로 생성하든, 새로운 로컬 환경에서 FastAPI + Nginx 로 배포할 일이 생길 때 docker로 편하게 하기 위한 간단한 소스코드를 정리했다.

 

디렉토리 구조

  • 배포할 프로젝트 코드는 각자 구성해보고 docker image build와 컨테이너 활용하는 개념 위주로 참고하길 바란다.
  • app 폴더 내 Dockerfile / nginx 폴더 내 Dockerfile 활용하여 docker compose로 한번에 build할 예정이다.
  • 결국 docker-compose.yml 소스 코드로 전체를 관리할 예정이다.
📦docker-fastapi-build
 ┣ 📂app
 ┃ ┣ 📂src
 ┃ ┃ ┣ 📂features
 ┃ ┃ ┃ ┣ 📜__init__.py
 ┃ ┃ ┃ ┣ 📜extract_guide.py
 ┃ ┃ ┃ ┣ 📜extract_keyword.py
 ┃ ┃ ┃ ┗ 📜extract_law.py
 ┃ ┃ ┗ 📂utils
 ┃ ┃ ┃ ┣ 📜__init__.py
 ┃ ┃ ┃ ┗ 📜data_loader.py
 ┃ ┣ 📜Dockerfile
 ┃ ┣ 📜main.py
 ┃ ┗ 📜requirements.txt
 ┣ 📂nginx
 ┃ ┣ 📜Dockerfile
 ┃ ┣ 📜nginx.conf
 ┃ ┗ 📜project.conf
 ┗ 📜docker-compose.yml

 

📜 docker-compose.yml

version: '3.0'  # ==> 도커 버전을 의미
services:       # ==> 배포 할 서비스들

  # nginx 서비스
  nginx:                          # ==> 이름
    container_name: nginx-build   # ==> 컨테이너 이름
    build: ./nginx                # ==> 빌드 할 디렉토리
    ports:                        # ==> 배포할 때 사용할 inbound:outbount 포트번호
      - "80:80"
    restart: always
    volumes:
      - ./app:/app                # ==> 데이터 마운트
    depends_on:                   # nginx가 배포하면서 web 서비스가 바로 배포되도록 연결
      - fastapi

  # FastAPI 서비스
  fastapi:                            # ==> 이름
    build:                        # ==> 빌드 할 디렉토리를 위 처럼 해당 디렉토리를 바로 지정해도 되고, 여기처럼 나눠서 지정 가능
      context: ./app
      dockerfile: Dockerfile
    container_name: fastapi-build # ==> 컨테이너 이름

    # 배포하면서 실행 할 명령어
    command: gunicorn -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:8000

    restart: always
    volumes:
      - ./app:/app
    expose:                       # ==> gunicorn을 8000포트로 열어놨기 때문에 배포할 포트 번호도 동일하게 8000포트로
      - "8000"

 

  • docker compose up --build : docker 이미지 파일들을 빌드한 후 up 하는 명령어
  • 위 명령어를 실행하면 우선 ./nginx 디렉토리 안에 있는 docker 파일을 빌드 한다

./nginx/Dockerfile

FROM nginx:1.15.8                     # ==> nginx 이미지 버전 선택

RUN rm /etc/nginx/nginx.conf          # ==> nginx 이미지 내부의 기존 nginx.conf 데이터를 지운다
COPY nginx.conf /etc/nginx/           # ==> 현재 디렉토리 안에 있는 nginx.conf 파일을 복사해서 대체한다
RUN rm /etc/nginx/conf.d/default.conf # ==> nginx 이미지 내부의 기존 default.conf 데이터를 지운다
COPY project.conf /etc/nginx/conf.d/  # ==> 현재 디렉토리 안에 있는 project.conf 파일을 복사해서 대체한다

 

./nginx/nginx.conf

#ngnix.conf

#Define the user that will own and run the Nginx server
user  nginx;

# Define the number of worker processes; recommended value is the number of
# cores that are being used by your server
worker_processes  1;

# Define the location on the file system of the error log, plus the minimum
# severity to log messages for
error_log  /var/log/nginx/error.log warn;

# Define the file that will store the process ID of the main NGINX process
pid        /var/run/nginx.pid;


# events block defines the parameters that affect connection processing.
events {
    # Define the maximum number of simultaneous connections that can be opened by a worker process
    worker_connections  1024;
}


# http block defines the parameters for how NGINX should handle HTTP web traffic
http {
    # Include the file defining the list of file types that are supported by NGINX
    include       /etc/nginx/mime.types;

    # Define the default file type that is returned to the user
    default_type  text/html;

    # Define the format of log messages.
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    # Define the location of the log of access attempts to NGINX
    access_log  /var/log/nginx/access.log  main;

    # Define the parameters to optimize the delivery of static content
    sendfile        on;
    tcp_nopush     on;
    tcp_nodelay    on;

    # Define the timeout value for keep-alive connections with the client
    keepalive_timeout  65;

    # Define the usage of the gzip compression algorithm to reduce the amount of data to transmit
    #gzip  on;

    # Include additional parameters for virtual host(s)/server(s)
    include /etc/nginx/conf.d/*.conf;         # ==> 나머지 부분은 default 값으로 nginx를 위한 기본 세팅 값으로 보인다.
                                              # ==> 허나 이부분은 새로 대체할 project.conf 파일을 참조하는 부분이어서 중요해 보인다.
}

 

./nginx/project.conf

# 이 세팅은 /etc/nginx/sites-available/default 이 파일에서 자주 세팅하는 값이다.
server {
    listen 80;                        # ==> 기본적으로 nginx는 80포트로 통신한다 / 이 값에 따라 docker-compose.yml 파일에서 포트 번호를 맞춰줘야 한다
    server_name localhost;

    location / {
        proxy_pass http://fastapi:8000;   # ==> 여기서 fastapi가 중요하다 fastapi는 위 docker-compose.yml 파일에서 정의한 서비스 이름!!

        # Do not change this
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

 

./app/Dockerfile

FROM python:3.8   # ==> python 이미지 사용

WORKDIR /app      # ==> 작업할 기본 디렉토리 설정(빌드할 이미지 내부의 디렉토리를 의미함)
COPY . .   # ==> 현재 빌드 할 파일들을 기본 작업 디렉토리로 이동

RUN pip3 install -r /app/requirements.txt     # ==> 패키지 설치

ENV GOOGLE_APPLICATION_CREDENTIALS /app/airy-runway-344101-9fb121fbeb28.json    # ==> GCS bucket 접근을 위한 환경변수 세팅

💡데크 기본 개념

  • 스택(stack)과 큐(Queue)의 연산을 모두 가지고 있는 복합 자료형 자료구조
  • 데크(Deque) : Double Ended Queue의 줄임말로서 양쪽 끝 데이터를 모두 추출할 수 있는 큐의 형태
  • 데크 자료구조는 양쪽 끝에서 삽입 삭제 모두 가능하다
  • 파이썬에서는 데크 자료구조를 collections 라이브러리를 활용하여 사용 가능하다
  • 데크 자료구조는 이중 연결 리스트로 구현하는 것이 유용하다. 포인터를 2개 활용해서 앞뒤에서 삽입 삭제가 가능하다.

 

💡데크 자료구조 직접 구현

  • 다음 연산을 지원하는 원형 데크를 디자인하라

    • MyCircularDeque(k: int) - k값으로 최대 원형 데크의 사이즈 초기화하기
    • insertFront(value: int) - 입력값(value)을 데크의 앞쪽에 삽입하고, 성공했다면 True, 실패했다면 False 반환하기
    • insertLast(vlaue: int) - 입력값(value)을 데크의 뒤쪽에 삽입하고, 성공했다면 True, 실패했다면 False 
    • deleteFront() - 데크 자료구조의 맨 앞의 값을 삭제하고, 성공했다면 True, 실패했다면 False 
    • deleteLast() - 데크 자료구조의 맨 뒤의 값을 삭제하고, 성공했다면 True, 실패했다면 False 
    • getFront() - 데크 자료구조의 맨 앞의 값을 반환하되 만약 값이 없다면 -1을 반환
    • getLast() - 데크 자료구조의 맨 뒤의 값을 반환하되 만약 값이 없다면 -1을 반환
    • isEmpty() - 데크 자료구조의 값이 비어있다면 True, 아니면 False
    • isFull() - 데크 자료구조의 값이 가득 차 있다면 True, 아니면 False

  • 이중 연결 리스트를 활용하여 원형 데크 자료구조 구현

 

💻 원형 데크 초기화

class MyCircularDeque(object):

    def __init__(self, k):
        """
        :type k: int
        """
        # 왼쪽(head), 오른쪽(tail) index 역할을 할 연결리스트 정의
        self.head, self.tail = ListNode(None), ListNode(None)
        self.head.right, self.tail.left = self.tail, self.head
        
        # 최대 길이와 현재 길이 정보를 담을 변수 정의
        self.maxlen, self.length = k, 0

 

💻 원형 데크 데이터 추가 내장함수

def _add(self, node: ListNode, new: ListNode):
	
    # 삽입할 연결 리스트의 기존 오른쪽 값을 정의한다
    # 아마 None 값일 것으로 예상한다.
    # front : n --> self.head.right.right --> self.tail.right
    # last  n --> self.tail.left.right  --> self.head.right
    n = node.right 
    
    # 연결 리스트의 오른쪽 값을 새로운 값으로 업데이트한다.
    # front : self.head.right.right == ListNode(value)
    # last  : self.tail.left.right  == ListNode(value)
    node.right = new
    
    # 새로운 노드의 left 값은 기존 node의 값으로 정의하고 (즉, head 역할)
    # right 값은 위에서 따로 정의한 None 값으로 정의해준다
    # head - new - None
    new.left, new.right = node, n
    
    # 그리고 n의 left 값을 새로운 값으로 정의한다.
    # front : n = self.head.right.right
    # last  : n = self.tail.left.right
    n.left = new

 

💻 원형 데크 데이터 삭제 내장함수

def _del(self, node: ListNode):
        
    # ? None 값을 미리 정의하는건가?
    n = node.right.right

    # 업데이트
    node.right = n

    # ? n의 왼쪽 값을 입력한 포인터의 값으로 정의? 왜?
    n.left = node

 

💻 원형 데크 앞쪽, 뒤쪽 데이터 추가

def insertFront(self, value):
    """
    :type value: int
    :rtype: bool
    """
    
    # 현재 길이 정보를 확인한다
    # 현재 길이가 최대 길이와 같다면 더이상 삽입할 수 없기에 False를 return
    if self.length == self.maxlen:
    	return False
    
    # 현재 길이가 아직 최대 길이에 도달하지 않았다면 데이터를 삽입할 것이기에
    # 현재 길이를 업데이트 해준다
    self.length += 1
    
    # value값을 원형 데크 자료구조에 추가해준다.
    # 내장 함수 _add 메서드를 사용할 예정
    # value 값을 연결리스트 자료구조 형태에 담아서 추가한다.
    self._add(self.head, ListNode(value))
    
    
def insertLast(self, value):
    """
    :type value: int
    :rtype: bool
    """

    # 현재 길이 확인하여 최대 길이에 도달했다면 False return
    if self.length == self.maxlen:
        return False

    # 현재 길이 업데이트
    self.length += 1

    # 뒤쪽에 값을 삽입할 때는 self.tail 포인터를 활용
    # self.tail.left == 초기에 self.head로 지정했음
    # 업데이트 될 것으로 예상하면서 작업
    self._add(self.tail.left, ListNode(value))

 

💻 원형 데크 앞쪽, 뒤쪽 데이터 삭제

def deleteFront(self):
    # 삭제할 값이 없다면 False return
    if self.length == 0:
        return False

    # 현재 길이 업데이터
    self.length -= 1

    # 삭제 로직
    self._del(self.head)

    return True


def deleteLast(self):
    # 삭제할 값이 없다면 False return
    if self.length == 0:
        return False

    # 현재 길이 업데이터
    self.length -= 1

    # 삭제 로직
    # 뒷 부분을 삭제할 때는 tail 포인터를 활용하는데
    # 왜 left.left의 값을 넣어주는지 궁금함
    self._del(self.tail.left.left)

 

💻 원형 데크 데이터 상태 확인 및 판별

def getFront(self):
    """
    :rtype: int
    맨 앞의 값 반환
    """
    return self.head.right.val if self.length else -1


def getRear(self):
    """
    :rtype: int
    맨 뒤 값 반환
    """
    return self.tail.left.val if self.length else -1


def isEmpty(self):
    """
    :rtype: bool
    데크 자료구조의 값이 비어 있는지 판별
    """
    return self.length == 0


def isFull(self):
    """
    :rtype: bool
    데크 자료구조의 값이 가득 차 있는지 판별
    """
    return self.length == self.maxlen

Pytorch의 기본 연산 구현

import torch, math

# 2 X 3 짜리 랜덤값의 tensor 생성
x = torch.randn(2,3)

# 2를 곱한 후 -1
new_x = x * 2 - 1

# 연산 값 확인
print(new_x)
# 절대값 확인
print(torch.abs(new_x))
# 실수 값에서 올림하여 정수 반환
print(torch.ceil(new_x))
# 실수 값에서 내림하여 정수 반환
print(torch.floor(new_x))
# 특정 범위의 값 안에서만 값이 나올 수 있도록
# 최소값보다 낮으면 최소값으로, 최대값보다 높으면 최대값으로 변환
print(torch.clamp(new_x, -0.5, 0.5))

"""
tensor([[-1.4341,  0.6692, -0.3575],
        [-0.6242, -1.3584,  0.3057]])
tensor([[1.4341, 0.6692, 0.3575],
        [0.6242, 1.3584, 0.3057]])
tensor([[-1.,  1., -0.],
        [-0., -1.,  1.]])
tensor([[-2.,  0., -1.],
        [-1., -2.,  0.]])
tensor([[-0.5000,  0.5000, -0.3575],
        [-0.5000, -0.5000,  0.3057]])
"""


print(new_x)
# torch 최소값
print(torch.min(new_x))
# torch 최대값
print(torch.max(new_x))
"""
tensor([[-1.4341,  0.6692, -0.3575],
        [-0.6242, -1.3584,  0.3057]])
tensor(-1.4341)
tensor(0.6692)
"""

 

+ Recent posts