파이썬 자료구조 1

·2022년 9월 15일
0

TIL

목록 보기
8/46

오늘 수업 내용📒

함수 심화

인자에 기본값 지정해주기

#함수를 선언할 때 인자에 기본값을 지정해 줄 수 있다. 
EXPRESSION = {
    0: lambda x, y: x+y,
    1: lambda x, y: x-y,
    2: lambda x, y: x*y,
    3: lambda x, y: x/y
}

def calc(num1, num2, option=None): #인자로 option이 들어오지 않는 경우 
    """    #docstring
    option 
    - 0: 더하기 
    - 1: 뺴기 
    - 2: 곱하기 
    - 3: 나누기
    """
    return EXPRESSION[option] (num1, num2) if option in EXPRESSION.keys() else False


print(calc(10,20)) #False 
print(calc(10, 20, 0)) # 30
print(calc(10, 20, 1)) # -10
print(calc(10, 20, 2)) # 200
print(calc(10, 20, 3)) # 0.5

args/kwargs에 대한 이해

  • args(arguments)와 keyword arguments(kwargs)는 함수에서 인자로 받을 값들의 갯수가 불규칙하거나 많을 때 주로 사용한다. 인자로 받을 값이 정해져있지 않기 때문에 함수를 더 동적으로 사용할 수 있다. 함수를 선언할 떄 args는 앞에 * 를 붙이고, kwargs는 앞에 ** 를 붙여 명시한다.

  • args 활용 (args는 tuple로 받아 불가변)

def add(*args):
    #args = (1, 2, 3, 4)
    result = 0 
    for i in args:
        result += i 

    return result

print(add())
print(add(1,2,3))
print(add(1,2,3,4))
  • kwargs 활용 (딕셔너리로 받아서 가변)
import pprint #정렬 출력 
def set_profile(**kwargs):
    """
     kwargs = {
        name: "lee",
        gender: "man",
        age: 32,
        birthday: "01/01",
        email: "python@sparta.com"
    }
    """
    profile = {}
    profile['name'] = kwargs.get("name", "-")
    profile['gender'] = kwargs.get("gender", "-")
    profile["birthday"] = kwargs.get("birthday", "-")
    profile["age"] = kwargs.get("age", "-")
    profile["phone"] = kwargs.get("phone", "-")
    profile["email"] = kwargs.get("email", "-")
    return profile

profile = set_profile(
    name = "lee",
    gender ="man",
    age = "32",
    birthday = "01/01",
    email = "python@sparta.com",
)

pprint.pprint(profile)
  • args, kwargs 같이 사용하기
def print_arguments(a, b, *args, **kwargs):
    print(a)
    print(b)
    print(args)
    print(kwargs)

print_arguments(
    1, #a
    2, #b 
    3, 4, 5, 6, #*args
    hello="world", keyword="argument" #kwargs
)

패킹과 언패킹

패킹(packing)과 언패킹(unpacking)은 단어의 뜻 그대로 요소들을 묶어주거나 풀어주는 것

  • list 혹은 dictionary의 값을 함수에 입력할 때 주로 사용
  • list에서의 활용
def add(*args):
    result = 0
    for i in args:
        result += i 
    return result 

numbers = [1,2,3,4]

print(add(*numbers)) #print(add(1,2,3,4))와 동일 
  • dictionary에서의 활용
import pprint

def set_profile(**kwargs):
    profile = {}
    profile["name"] = kwargs.get("name", "-")
    profile["gender"] = kwargs.get("gender", "-")
    profile["birthday"] = kwargs.get("birthday", "-")
    profile["age"] = kwargs.get("age", "-")
    profile["phone"] = kwargs.get("phone", "-")
    profile["email"] = kwargs.get("email", "-")
    return profile

user_profile = {
    "name": "lee",
    "gender": "man",
    "age": 32,
    "birthday": "01/01",
    "email": "python@sparta.com",
}

pprint.pprint(set_profile(**user_profile))

"""아래 코드와 동일 
profile = set_profile(
     "name": "lee",
    "gender": "man",
    "age": 32,
    "birthday": "01/01",
    "email": "python@sparta.com",
)
"""

객체지향

객체지향(Object-Oriented Programming)
: 객체를 모델링하는 방향으로 코드를 작성하는 것

  • 특성
    1) 캡슐화: 특정 데이터의 엑세스를 제한해 데이터가 직접적으로 수정되는 것을 방지, 검증된 데이터만 사용 가능
    2) 추상화: 사용되는 객체의 특성 중 필요한 부분만 사용하고 필요하지 않은 부분 제거
    3) 상속: 기존에 작성된 클래스의 내용을 수정하지 않고 그대로 사용하기 위해 사용되는 방식. 클래스 선언할 때 상속받을 클래스를 지정할 수 있다
    4) 다형성: 하나의 객체가 다른 여러 객체로 재구성되는 것. ex) 오버라이드, 오버로드

  • 객체지향의 장/단점
    📌 장점: 클래스 상속을 활용하기 때문에 코드의 재사용성이 높다, 데이터를 검증하는 과정이 있기 때문에 신뢰도가 높다, 모델링을 하기 수월하다, 보안성이 높다
    📌 단점: 난이도가 높다, 코드의 실행 속도가 비교적 느린 편이다, 객체의 역할과 기능을 정의하고 이해해야 하기 때문에 개발 속도가 느려진다.

객체지향 예제 코드

import re

# 숫자, 알파벳으로 시작하고 중간에 - 혹은 _가 포함될 수 있으며 숫자, 알파벳으로 끝나야 한다.
# @
# 알파벳, 숫자로 시작해야 하며 . 뒤에 2자의 알파벳이 와야 한다.
email_regex = re.compile(r'([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+')

class Attendance:
    count = 0
        
    def attendance(self):
        self.count += 1
    
    @property
    def attendance_count(self):
        return self.count

class Profile(Attendance):
    def __init__(self, name, age, email):
        self.__name = name # __를 붙여주면 class 내부에서만 사용하겠다는 뜻
        self.__age = age
        self.__email = email
    
    @property # 읽기 전용 속성
    def name(self):
        return self.__name
    
    @name.setter # 쓰기 전용 속성
    def name(self, name):
        if isinstance(name, str) and len(name) >= 2:
            print("이름은 2자 이상 문자만 입력 가능합니다.")
            
        else:
            self.__name = name
    
    @property
    def age(self):
        return self.__age
    
    @age.setter
    def age(self, age):
        if isinstance(age, int):
            self.__age = age
            
        else:
            print("나이에는 숫자만 입력 가능합니다.")
        
    @property
    def email(self):
        return self.__email
    
    @email.setter
    def email(self, email):
        if re.fullmatch(email_regex, email):
            self.__email = email
        else:
            print("유효하지 않은 이메일입니다.")
        
    def attendance(self): # override
        super().attendance() # 부모 메소드 사용하기
        self.count += 1
    
    def __str__(self):
        return f"{self.__name} / {self.__age}"
    
    def __repr__(self):
        return f"{self.__name}"

오늘 공부한 내용📗

파이썬 리스트

리스트 작업의 의미

  • insert(i, x) x를 리스트의 i번 원소로 삽입
  • append(x) 원소 x를 리스트의 맨 뒤에 추가한다
  • pop(i) 리스트의 i번째 원소를 삭제
  • remove(x) 리스트에서 (처음으로 나타나는) x를 삭제
  • index(x) 원소 x가 리스트의 몇 번 원소인지 알려줌
  • clear() 리스트 청소
  • count(x) 리스트에서 원소 x가 몇 번 나타나는지 알려줌
  • extend(a) 리스트에서 나열할 수 있는 객체 a를 풀어서 추가한다
  • copy() 리스트 복사
  • reverse() 리스트 순서 역으로 변경
  • sort() 리스트 원소 정렬
list = []
list.insert(0, 'test')
list.insert(0, 'sample')
list.insert(1, 'mid')
list.append('end')
print(list) #['sample', 'mid', 'test', 'end']

print("samplie is at", list.index('sample'),'th')
#samplie is at 0 th

list.pop(0) #['mid', 'test', 'end']

연결 리스트

  • 리스트는 데이터에 순서를 매겨 늘어놓은 자료구조
    연결리스트는 예를 들어 A-F까지 연결되어 있다면, 누군가를 건너뛰거나 뒤돌아 앞 사람에게 연결될 수 없는 구조

  • 연결 리스트에서 각각의 원소를 노드라고 한다. 노드가 갖고 있는 것은 데이터와 뒤쪽 노드를 가리키는 포인터이다. 원소를 저장하는 건 item 필드 다음 노드를 가리키는 건 next 필드라고 한다

  • 맨 앞에 있는 노드를 머리 노드 맨 끝에 있는 노드를 꼬리 노드라고 한다

__head #첫 번째 노드에 대한 레퍼런스 
__numItems #연결 리스트에 들어 있는 원소의 총 수 
get(i) #연결 리스트의 i번 원소를 알려준다 
index(x) #원소 x가 연결 리스트의 몇 번 원소인지 알려준다 
isEmpty() #연결 리스트가 빈 리스트인지 알려준다 
size() #연결 리스트의 총 원소 수를 알려준다 
cleae() #연결 리스트를 청소한다 
count(x) #연결 리스트에서 x원소가 몇 번 나타나는지 알려준다 
extend(a) #연결 리스트에서 나열할 수 있는 객체 a를 풀어서 추가한다 
copy() #연결 리스트 복사해서 새 연결 리스트를 리턴 
reverse() #연결 리스트 순서 역으로 변경 
sort() #연결 리스트 원소 정렬 

배열 리스트와 연결 리스트 비교

  • 배열 리스트는 시작부터 고정된 크기를 지정하고, 연결 리스트는 원소가 들어오는 대로 공간을 할당받아 넣는다. 배열 리스트는 정적이고 연결 리스트는 동적이라고 할 수 있다
  • 배열 리스트는 연속된 공간에 원소를 저장하는 반면 연결 리스트는 공간의 연속성이 없다. 이 차이로 인해 정렬이나 검색 작업을 할 때 배열 리스트보다 연결 리스트에서 시간이 더 걸린다
  • 배열 리스트는 i번 원소를 찾을 때 i값으로 즉시 접근할 수 있다. 반면 연결 리스트는 헤드 노드에서부터 시작하여 링크를 i번 따라간 다음 i번 원소를 찾는다

연결 리스트 클래스 LinkedList

연결 리스트 클래스 LinkedList는 다음과 같이 필드 3개로 구성된다

  • no: 리스트에 등록되어 있는 노드의 개수
  • head: 머리 노드에 대한 참조
  • current: 현재 주목하고 있는 노드에 대한 참조. 리스트에서 노드를 검색하여, 그 노드를 주목한 직후에 노드를 삭제하는 등의 용도로 사용

예)

class LinkedList:
    """연결 리스트 클래스"""
    def __init__(self) -> None:
        """초기화"""
        self.no = 0 
        self.head = None
        self.current = None
    
    def __len__(self) -> int:
        """연결 리스트의 노드 개수를 반환"""
        return self.no

느낀점📌

자료구조 수업을 나가기 전에 집에 있는 책으로 혼자 공부를 했다. 알고리즘 성능 부분은 이해하기가 어려웠고 피보나치 수열, 하노이의 탑은 호출 과정이 전반적으로 어떻게 이루어지는 건지 궁금하면서 이해가 가지 않았다. 하노이의 탑은 이동 횟수가 호출 횟수와 같다는데 어떤 방식으로 호출이 이루어지는 건지 모르겠다. 연결 리스트는 천천히 몇 번 읽어보면서 흐름만 이해할 수 있었다. 내일부터는 혼자 책만 붙잡고 있지 말고 강의를 보면서 자료구조 챕터를 이해할 수 있도록 해야겠다.

0개의 댓글