반응형

팰린드롬이란?

  • 앞뒤가 똑같은 단어나 문장
  • ‘기러기’, ‘오디오’
  • 소주 만 병만 주소

대소문자를 구분하지 않음
- Level
- Anna

영문자와 숫자만을 대상으로 함

  • “A man, a plan, a canal: Panama” → 영문자만 추리면 아래와 같음

  • 팰린드롬 맞음

 

문제에 대한 예시


풀이 1. 리스트로 변환

def is_palindrome_1(s:str)->bool:
    strs = []
		
		# 숫자, 문자만 추출
    for char in s:
        if char.isalnum():
            strs.append(char.lower()) # 소문자로 변환

    while len(strs)>1:
        if strs.pop(0) != strs.pop():
            return False
    return True

 

문제풀이에 필요한 개념

 


String

  • isalnum() 메소드
    • Return True if the string is an alpha-numeric string, False otherwise.
    char1 = 'a'
    print(char1.isalnum()) # True
    
    char2 = '5'
    print(char2.isalnum()) # True
    
    char3 = '$'
    print(char3.isalnum()) # False
    
  • lower() 메소드
    • 대문자를 소문자로 변환해서 반환
    str1 = 'ABC'
    print(str1.lower()) # abc
    
    str2 = 'AbC'
    print(str2.lower()) # abc
    

 

List

  • pop() 메소드
    lists = ['a', 'b', 'c', 'd']
    
    print(lists.pop()) # d
    
    print(lists) # ['a', 'b', c']
    
    print(lists.pop(0)) # 리스트에서 0번째 인덱스(가장 앞)에 해당하는 'a' 반환
    
    print(lists) # ['b', 'c']
    
  • Remove and return item at index (default last).

 

풀이 2. 데크 자료형을 이용한 최적화

def is_palindrome(s:str)->bool:
		import collections
    strs = collections.deque()
		
    # 숫자, 문자만 추출
    for char in s:
        if char.isalnum():
            strs.append(char.lower()) # 소문자로 변환

    while len(strs)>1:
        if strs.popleft() != strs.pop():
            return False
    return True
  • list가 아닌 deque를 이용
if strs.pop(0) != strs.pop(): # strs가 list

if strs.popleft() != strs.pop(): # strs가 deque
  • list의 pop(0)의 시간 복잡도는 O() O(n)
    • [0, 1, 2, 3, 4] pop(0) , [None, 1, 2, 3, 4] → [1, 2, 3, 4]
    • [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    • 맨 앞의 element에 pop()을 적용하면 뒤의 요소들을 한칸씩 왼쪽으로 모두 옮겨야 함
  • 반면 deque의 popleft() 시간 복잡도는 O(1)
    • 내부적으로 doubly linked list으로 삽입/삭제에 효율이 좋음
    • 1→ 2→ 3→4→5→6, 2만 pop하면 1→3→4→5→6 (뒤는 유지)

 

 

풀이 3. 슬라이싱 사용


def is_palindrome_3(s:str) -> bool:
    import re
    s = s.lower()

    s = re.sub('[^a-z0-9]', '', s) # 숫자 문자가 아닌 문자열을 공백으로 변환
    return s==s[::-1]

문제 풀이에 필요한 개념


  • 정규식
- [] : 문자 클래스로 [] 사이의 문자들과 매치
- [a-z] : 소문자 알파벳과 매치
- [a-zA-Z] : 알파벳 모두
- [0-9] : 숫자 모두
- [^a-z] : 소문자 알파벳을 제외한 문자와 매치
- [^a-z0-9] : 소문자 알파벳과 숫자를 제외한 문자와 매치

re.sub(first, second, third)

  • first : 정규식
  • second : first에 매치되는 요소를 second로 치환
  • third : 적용될 문자열
 
str = 'abcABC123!@#'
import re

# 소문자를 공백으로 치환
str_except_small_letter = re.sub('[a-z]', '', str)
print(str_except_small_letter) # ABC123!@#

# 대문자를 공백으로 치환
str_except_big_letter = re.sub('[A-Z]', '', str)
print(str_except_big_letter) # abc123!@#

# 숫자를 공백으로 치환
str_except_numbers = re.sub('[0-9]', '', str)
print(str_except_numbers) # abcABC!@#

# 숫자,문자를 공백으로 치환
str_except_special = re.sub('[a-zA-Z0-9]','', str)
print(str_except_special) # !@#

 

Reference

1) 책 "파이썬 알고리즘 인터뷰"

2) stackoverflow: deque.popleft() and list.pop(0). Is there performance difference?

 

반응형

'코딩테스트' 카테고리의 다른 글

Leetcode 387. First Unique Character in a String  (0) 2024.11.03
반응형

개요

  • 글로벌하게 접근 가능한 하나의 객체를 제공하는 패턴
  • 로깅, 데이터베이스 관련 작업 등 동일한 리소스에 대한 동시 요청의 충돌을 방지하기 위해 하나의 인스턴스를 공유하는 작업에 주로 사용

 

싱글톤 패턴을 사용하는 경우

  1. 데이터의 일관성 유지 위해 DB에 작업을 수행하는 하나의 데이터베이스 객체가 필요한 경우
  2. 여러 서비스의 로그를 한 개의 로그파일에 순차적으로 동일한 로깅 객체를 사용해 남기는 경우에 적합

 

코드 구현


class Singleton:
    def __new__(cls):  # (1)
        if not hasattr(cls, "instance"): # (2)
            cls.instance = super().__new__(cls)
        return cls.instance

s1 = Singleton()
s2 = Singleton()
print('s1: ', s1) # s1:  <__main__.Singleton object at 0x000001A4B84A51D0>
print('s2: ', s2) # S2:  <__main__.Singleton object at 0x000001A4B84A51D0>
print('singleton: ', s1 is s2) # True

(1) __new__ 메소드

  • __new__ 메소드는 객체를 생성할 때 호출됨 (__init__ 메소드는 객체에 속성을 추가할 때 호출됨)
    • 호출순서 : __new__ -> __init__ 메소드

(2) 클래스 객체에 instance 속성이 있는지 확인

  • instance 속성이 없으면 super() 인 object 클래스의 __new__ 메소드를 호출하여 instance 속성 생성 후 반환
    • super() : 부모클래스 객체를 반환하는 함수. 상속받는 클래스가 없는경우 object 클래스를 상속받음

 

Singleton 클래스 인스턴스를 2개 생성했지만 각각의 주소가 같으므로 둘은 같은 인스턴스를 가리키고 있음

 

게으른 초기화


  • 싱글톤 패턴을 기반으로 하는 초기화 방식
  • 인스턴스를 꼭 필요할 떄 생성
    • 모듈을 import 할 때 필요하지 않는 시점에 실수로 미리 객체를 생성하는 경우 방지
class Singleton:
    __instance = None

    def __init__(self):
        if not Singleton.__instance:
            print('__init__ method called')
        else:
            print("Instance already created:", self.get_instance())

    @classmethod
    def get_instance(cls):
        if not cls.__instance:
            cls.__instance = Singleton()
        return cls.__instance
if __name__=='__main__':
    s = Singleton() # __init__ method called 초기화만하고 객체 생성 X

    print('object created', Singleton.get_instance()) # 객체 생성 
  # object created <__main__.Singleton object at 0x00000183220C51D0>
	
    s1 = Singleton() 
  # Instance already created: <__main__.Singleton object at 0x00000246DBAB51D0>
  • 객체를 생성하기 위해서는 Singleton.getInstance() 호출 

 

모듈 싱글톤


  • 파이썬은 모든 모듈을 기본적으로 싱글톤으로 import함.
  • 아래 코드를 통해 싱글톤 여부 확인 가능
# Singleton.py
shared_variable = "shared variable"
# module1.py
import singleton
print('singleton in module_1: ', singleton.shared_variable)
singleton.shared_variable += "(modified by module_1)"
print('shared_variable was modified in module_1)
# module2.py
import singleton
print('singleton in module2: ', singleton.shared_variable)

위 3개의 .py 파일을 만든 후 모두 import

# test.py
import module1
# singleton in module1:  shared variable

import module2
# singleton in module2:  shared variable(modified by module_1)
  • singleton 모듈을 module1.py, module2.py 에서 각각 import
  • module1.py 에서 수정된 singleton 모듈의 변수(shared_variable)가 module2.py에서도 수정된 채로 확인됨

 

Reference

1) 책 "파이썬 디자인 패턴 2/e"
2) https://wikidocs.net/69361

 

 

반응형
반응형

2022.05.25 - [자료구조] - [자료구조] Linked List - (1) 개념

2022.06.05 - [자료구조] - [자료구조] Linked List - (2) 간단 개념 구현

2022.06.07 - [자료구조] - [자료구조] Linked List - (3) 노드 삽입 (at the end)

2022.06.08 - [자료구조] - [자료구조] Linked List - (4) 노드 삽입 (at the front)

2022.06.12 - [자료구조] - [자료구조] Linked List - (5) 데이터 조회

본 글에서는 Linked List에서 노드 삭제에 대해 다룸. cur이라는 변수를 이용하여 head가 가리키는 노드부터 삭제할 노드를 찾음.

노드 삭제에 대해서 크게 3가지 경우로 나눠서 생각

1) Linked list가 비어있는 경우 : 메소드 바로 종료

cur = self.head

if cur is None:
	return

Linked List가 비어있는 상태

2) head가 가리키는 노드의 값이 삭제하고자하는 값인 경우 (아래 그림에서 data 변수가 3인 경우)

cur = self.head

 

head가 가리키는 노드를 삭제하는 경우

head는 현재 가리키는 노드의 next가 가리키는 노드를 참조해야 함. 그리고 cur에는 None 할당

if cur.data == data: # 첫 노드 삭제
    self.head = cur.next
    cur = None
    return

head가 가리키는 노드 업데이트

3) head가 가리키는 노드가 아닌 다른 노드를 삭제하는 경우 (data에 2가 담긴 노드를 삭제한다고 가정)

cur = self.head

prev 변수를 도입하여 cur이 가리키는 노드를 참조하고, cur 변수는 현재 가리키는 노드의 next가 가리키는 노드 참조.

prev = cur
cur = cur.next

그리고 cur 변수가 가리키는 노드의 data가 삭제할 데이터인지 확인하는 과정을 반복하고 맞으면 반복과정을 종료

while True:
    prev = cur
    cur = cur.next
        
    if cur.data == data:
        break

 

Linked List가 끊기지 않기 위해서는 prev가 가리키는 노드의 next가 cur노드가 가리키는 노드의 next가 가리키는 노드를 참조해야 함 (아래 그림에서 빨간선)

prev.next = cur.next

data에 3이 담긴 노드의 next가 data에 1이 담긴 노드를 가리킴

 

위 3가지 경우가 포함된 delete 메소드는 아래와 같음

    def delete(self, data): # data가 포함된 노드를 linked list에서 삭제
        cur = self.head
        
        if cur == None: # Linked List가 비어있는 경우
            return 

        if cur.data == data: # 첫 노드 삭제
            self.head = cur.next
            cur = None
            return

        while True:
            prev = cur
            cur = cur.next
        
            if cur.data == data: # 중간 노드 삭제 
                break
        
        prev.next = cur.next
        cur = None

전체 코드

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    # linked list 끝에 노드 추가
    def append(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.head = new_node
        else:
            self.tail.next = new_node
            
        self.tail = new_node

    # linked list 처음에 노드 추가
    def push(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.tail = new_node
        else:
            new_node.next = self.head
        
        self.head = new_node

    def delete_all(self):
        while self.head is not None:
            cur = self.head
            self.head = self.head.next
            cur = None

    # data가 포함된 노드를 linked list에서 삭제
    def delete(self, data):
        cur = self.head
        
        if cur == None: # Linked List가 비어있는 경우
            return 

        if cur.data == data: # 첫 노드 삭제
            self.head = cur.next
            cur = None
            return

        while True:
            prev = cur
            cur = cur.next
        
            if cur.data == data: # 중간 노드 삭제
                break
        
        prev.next = cur.next
        cur = None

    def print(self):
        cur = self.head

        while cur:
            print(cur.data, end=" ")
            cur = cur.next
        print()

if __name__ == "__main__":
    linked_list = LinkedList()
    linked_list.print() # 출력 없음
    linked_list.push(1)
    linked_list.print() # 1
    linked_list.push(2)
    linked_list.print() # 2 1
    linked_list.push(3)
    linked_list.print() # 3 2 1

    linked_list.delete(2)
    linked_list.print() # 3 1

 

 

 
 
 
반응형
반응형

2022.06.08 - [자료구조] - [자료구조] Linked List - (4) 노드 삽입 (at the front)

 

[자료구조] Linked List - (4) 노드 삽입 (at the front)

2022.06.07 - [자료구조] - [자료구조] Linked List - (3) 노드 삽입 (at the end) 본 글에서는 위의 글에 이어서 새로운 노드를 Linked List 맨 앞에 추가하는 방법을 다룸. (1) 새로운 노드를 Linked List 맨 앞..

wschoi.tistory.com

본 글에서는 Linked List에 삽입된 노드들의 데이터를 조회하는 메소드를 다룸.

Linked list에 연결된 노드들의 data를 조회 

1) head가 가리키는 노드부터 순차적으로 출력하기 위해 cur 변수를 사용해 head가 참조하는 노드를 같이 참조

cur = self.head

2) cur 변수가 가리키는 노드의 data 출력

print(cur.data, end=" ") # 3

3) cur이 가리키는 노드를 연결된 노드로 이동

cur = cur->next

4) cur이 가리키는 노드의 data 출력 

print(cur.data, end=" ") # 2

위 과정을 print 메소드로 묶어서 표현하면 아래와 같음

def print(self):
        cur = self.head

        while cur:
            print(cur.data, end=" ")
            cur = cur.next
        print()

최종 코드

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

	# linked list 끝에 노드 추가
    def append(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.head = new_node
        else:
            self.tail.next = new_node
            
        self.tail = new_node

	# linked list 처음에 노드 추가
    def push(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.tail = new_node
        else:
            new_node.next = self.head
        
        self.head = new_node

    def print(self):
        cur = self.head

        while cur:
            print(cur.data, end=" ")
            cur = cur.next
        print()

if __name__ == "__main__":
    linked_list = LinkedList()
    linked_list.print() # 출력 없음
    linked_list.push(1)
    linked_list.print() # 1
    linked_list.push(2)
    linked_list.print() # 2 1
    linked_list.push(3)
    linked_list.print() # 3 2 1
반응형
반응형

2022.06.07 - [자료구조] - [자료구조] Linked List - (3) 노드 삽입 (at the end)

본 글에서는 위의 글에 이어서 새로운 노드를 Linked List 맨 앞에 추가하는 방법을 다룸. 

(1) 새로운 노드를 Linked List 맨 앞에 추가
(2) 새로운 노드를 Linked List 맨 뒤에 추가 

1) LinkedList 클래스를 정의

  • 클래스의 instance 변수로 head와 tail이 있고 모두 None값으로 초기화
class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

LinkedList의 초기 상태

 

head가 None을 가리키면, LinkedList에 아직 Node가 하나도 없는 상태.

2) 빈 Linked List에 새로운 node 추가

node가 새롭게 추가되면 head가 새로 추가된 node를 가리킴.

총 1개의 node만 있으므로 tail로 새로 추가된 node를 가리킴.

new_node = Node(1) # 새로운 노드 추가

if self.head is None: # head가 None을 가리키면,
    self.head = new_node
    self.tail = new_node

Linked List에 노드가 하나도 없는 상황에서 노드가 추가되는 과정

3) Node가 있는 Linked List에 새로운 node 추가

여기에 다시 새로운 node를 추가.

new_node = Node(2)

새로운 노드 추가

 

Linked List로 연결되기 위해서는 새로운 노드의 next가 head가 가리키는 노드를 가리켜야 함

new_node.next = self.head

새로운 노드의 next가 기존 노드를 가리킴

그리고 head는 새로 추가된 노드를 가리켜야함. 

self.head = new_node

head가 참조하는 노드 업데이트

 

마지막으로 새로운 노드 하나 더 추가.

new_node = Node(3)

세번째 노드 추가

새로운 노드의 next가 head가 가리키는 노드를 가리켜야 함.

new_node.next = self.head

 

마지막으로 head가 새로 추가된 노드를 가리키도록 업데이트

self.head = new_node

 

위의 과정을 'push' 라는 이름의 method로 변환하면 아래와 같음.

# linked list 처음에 노드 추가
def push(self, new_data):
    new_node = Node(new_data)

    if self.head is None:
        self.tail = new_node
    else:
        new_node.next = self.head
        
    self.head = new_node
  • push 메소드는 항상 처음에 노드를 추가하기 때문에 Time complexity가 항상 O(1)이다.

최종코드

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

	# linked list 끝에 노드 추가
    def append(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.head = new_node
        else:
            self.tail.next = new_node
            
        self.tail = new_node

	# linked list 처음에 노드 추가
    def push(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.tail = new_node
        else:
            new_node.next = self.head
        
        self.head = new_node

 

 
 
 
 
 
 
반응형
반응형

2022.06.05 - [자료구조] - [자료구조] Linked List - (2) 간단 개념 구현

 

[자료구조] Linked List - (2) 간단 개념 구현

본 글에서는 이전 Linked List 글에서 언급한 Node를 구현. 최대한 간단히 Node 클래스만을 이용해서 어떻게 node들이 연결될 수 있는지에 대해 설명. Node 클래스 정의 class Node: def __init__(self, data=None..

wschoi.tistory.com

 

본 글에서는 위의 글에 이어서 Linked List에 노드를 추가하는 방법을 다룸. 노드를 추가하는 방법이 크게 2가지가 있음.

(1) 새로운 노드를 Linked List 맨 앞에 추가
(2) 새로운 노드를 Linked List 맨 뒤에 추가 (본 글에서 다룸)

1) LinkedList 클래스를 정의

  • 클래스의 instance 변수로 head와 tail이 있고 모두 None값으로 초기화
class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

LinkedList의 초기 상태

 

head가 None을 가리키면, LinkedList에 아직 Node가 하나도 없는 상태.

2) 빈 Linked List에 새로운 node 추가

node가 새롭게 추가되면 head가 새로 추가된 node를 가리킴.

총 1개의 node만 있으므로 tail로 새로 추가된 node를 가리킴.

new_node = Node(1) # 새로운 노드 추가

if self.head is None: # head가 None을 가리키면,
    self.head = new_node
    self.tail = new_node

Linked List에 노드가 하나도 없는 상황에서 노드가 추가되는 과정

3) Node가 있는 Linked List에 새로운 node 추가

여기에 다시 새로운 node를 추가.

new_node = Node(2)

새로운 노드 추가

Linked List로 연결되기 위해서는 head와 tail이 가리키는 노드의 next가 새로운 노드를 가리켜야함. 

self.tail.next = new_node

기존의 노드의 next가 새로운 노드를 가리킴

그리고 tail은 새로 추가된 노드를 가리켜야함. 

self.tail = new_node

tail이 참조하는 노드 업데이트

마지막으로 새로운 노드 하나 더 추가.

new_node = Node(3)

세번째 노드 추가

 

tail이 가리키는 노드의 next가 새로운 노드를 가리켜야함.

self.tail.next = new_node

두번재 node의 next가 새로운 노드를 가리킴

 

마지막으로 tail이 새로 추가된 노드를 가리키도록 업데이트

self.tail = new_node

 

위의 과정을 'append' 라는 이름의 method로 변환하면 아래와 같음.

# linked list 끝에 노드 추가
def append(self, new_data):

    new_node = Node(new_data)

    if self.head is None:# 기존에 node가 없는 경우
        self.head = new_node
    else: # 기존에 node가 있는 경우
        self.tail.next = new_node
    
    self.tail = new_node

 

  • self.tail = new_node는 if-else 문에서 공통이므로 바깥으로 빼줌

최종코드

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    # linked list 끝에 노드 추가
    def append(self, new_data):
        new_node = Node(new_data)

        if self.head is None:# 기존에 node가 없는 경우
            self.head = new_node
        else: # 기존에 node가 있는 경우
            self.tail.next = new_node
    
        self.tail = new_node

 

참고)

1. Linked List 클래스에서 tail 이라는 인스턴스 변수를 사용함으로써, append 메소드의 time complexity가 O(1)을 유지한다. 변수를 사용하지 않는 경우는 head 노드부터 끝 노드까지 loop를 이용해 찾은 후 node를 추가해야하기 때문에 time complexity가 O(n)이 된다. (n은 노드 수)

 
 
반응형
반응형

공식문서에 따르면 Python에서는 함수나 변수 타입의 annotation을 강제하지 않는다. annotation이란 type hint를 생각하면 된다.

[c++]

int do_something(int x)
{
	return x + 1;
{

do_something이란 함수에 대해서 input parameter와 함수의 return 타입은 모두 int형으로 이를 어길시 에러가 발생한다.

[Python]

def do_something(x: int) -> int:
	return x + 1


print(do_something(1.1))  # 2.1

하지만 python의 경우 type hint를 어겨도 에러가 발생하지 않고, 위의 예와 같이 input parameter의 float 인 1.1을 넣어줘도 함수의 내용에 따라 2.1이 반환된다. 

 

하지만 annotation으로 에러가 발생하는 경우도 있는데 아래와 같이 forward reference와 연관된 경우이다. Forward reference란 메소드에서 변수를 먼저 사용하고, 그 이후에 변수를 정의하는 것이다. 

class A:
    def __init__(self, a: A):
        pass

# NameError: name 'A' is not defined

class A를 정의할 때, __init__ 메소드에서 parameter로 a를 받고 이는 class A라고 annotation이 표시되어있다. 하지만 위의 코드와 같이 작성하면 NameError가 발생하는데 그 이유는 class A의 정의가 끝나지 않는 상태에서 __init__ 메소드에서 사용하기 때문이다. 

위 문제를 해결하기 위해 크게 2가지가 있다.

1) type hint를 string으로 작성

class A:
    def do_something(self, a: 'A'):
        pass

아래의 코드를 통해서 해당 메소드의 annotation을 확인할 수 있다. 

print(A().do_something.__annotations__)  # {'a': 'A', 'return': None}

 

2) Python 3.7 이상 버전에서 from __future__ import annotations 추가

from __future__ import annotations

class A:
    def do_something(self, a: A) -> None:
        pass

class A의 정의가 끝나지 않는 상황에서 A를 annotation으로 사용하지만 에러가 발생하지 않는다.

아래의 코드를 통해 해당 메소드의 annotation들을 확인해보면 자동으로 string으로 변환된 것을 알 수 있다.

print(A().do_something.__annotations__) # {'a': 'A', 'return': None}

 

반응형

+ Recent posts