3 분 소요

스택은 배열의 한 쪽 끝에서만 데이터에 접근할 수 있는 선형 자료구조이다. (스택은 파이썬의 list 자료형과 달리, 인덱싱을 통해 중간에 있는 요소를 조회, 삽입할 수 없다) 스택은 한 쪽 끝이 막힌 긴 원통형 멘토스 포장 용기라고 볼 수 있겠다. 긴 원기둥 구조에 한 쪽 끝은 막혀 있고 한 쪽은 뚫려 있어 그 쪽을 통해 멘토스(데이터)가 삽입되거나 바깥으로 뺄 수 있다. 이러한 선천적 구조로 인해 마지막으로 들어간 데이터가 맨 처음으로 빠져나갈 수 있는 후입선출(last in, first out, LIFO) 구조이다.

스택이 가질 수 있는 동작에는 다음이 존재하며, 해당 동작들의 시간복잡도는 모두 O(1)이다.

  • push : 스택 맨 끝(맨 위)에 요소를 삽입한다. (멘토스 한 알 추가)
  • pop : 스택 맨 끝 요소를 반환하면서 동시에 스택에서 해당 항목을 제거한다. (맨 위에 있는 멘토스 한 알을 꺼낸다)
  • top/peek : 스택 맨 끝 요소를 조회한다. (맨 끝에 어떤 색깔의 멘토스가 있는지 확인한다)
  • empty : 스택이 비어있는지를 확인한다. (멘토스 다 먹었나?)
  • size: 스택 크기를 확인한다. (멘토스 얼마나 있나?)

파이썬에서는 리스트의 append(), pop() 메서드를 이용하여 스택을 구현할 수 있다. 다음은 앞서 언급한 스택의 모든 동작들을 구현하는 예제이다.

from typing import Any

class StaticStack():
    """
    스택의 크기를 고정시킨 스택.
    """
    def __init__(self, size_: int):
        """
        size_ : 스택 최대 크기 설정.
        """
        self.stack: list = []
        self.size_: int = size_

    def is_empty(self) -> bool:
        return self.stack == []

    def push(self, one_element) -> bool:
        """
        맨 끝에 요소 삽입. 
        정해진 size_만큼만 삽입할 수 있다.\n
        return type)\n
        True: 삽입 성공.\n
        False: 삽입 실패.\n
        """
        if self.getLength() == self.size_:
            print("스택의 최대 크기에 도달하여 요소를 추가할 수 없습니다.")
            return False
        self.stack.append(one_element)
        return True

    def pop(self) -> Any | None:
        """
        맨 끝 요소 추출 후 스택에서 제거.
        """
        try:
            return self.stack.pop()
        except IndexError:
            print("스택이 비어있어 반환할 아이템이 없습니다.")
            return
        
    def peek(self):
        """
        맨 끝 요소 조회. 
        """
        return self.stack[-1]
    
    def getLength(self) -> int:
        """
        현재 스택에 들어있는 요소들의 수 반환.
        """
        return len(self.stack)
    
    def setNewSize(self, new_size: int):
        """
        스택의 최대 크기 재설정. 
        현재 스택에 들어있는 요소들의 수보다 더 작게 설정하지 못하도록 함.
        """
        if new_size < self.getLength():
            print("스택 내 항목들의 수보다 더 작게 설정할 수 없습니다.")
            print(f"현재 스택 내 항목들의 수: {self.getLength()}")
            return
        self.size_ = new_size

    def clear(self):
        """
        스택을 모두 비운다.
        """
        del self.stack
        self.stack = []
    
    def __repr__(self):
        return repr(self.stack)
    

class DynamicStack(StaticStack):
    """
    크기에 제한을 두지 않고 무한정 크기를 동적으로 늘릴 수 있는 스택.
    """
    def __init__(self):
        self.stack: list = []

    def push(self, one_element):
        """
        맨 끝에 요소 삽입. 
        """
        self.stack.append(one_element)

    def setNewSize(self):
        """
        DynamicStack에서는 사용하지 않는 메서드. 
        """
        pass

def test_static_stack():
    stack = StaticStack(10)
    data = ["초록색", "분홍색", "노란색", '하늘색', '흰색']
    print("멘토스를 준비합니다.")
    for item in data:
        stack.push(item)
    print(f"멘토스 준비 완료. 상태: {stack}")
    print(f"현재 멘토스 양: {stack.getLength()}")
    print(f"맨 마지막에 있는 멘토스의 색은? {stack.peek()}")
    one_item = stack.pop()
    print(f"맨 마지막에 있는 {one_item} 멘토스를 꺼내 먹었다.")
    print(f"맨 마지막에 있는 멘토스의 색은? {stack.peek()}")
    print(f"멘토스 다 먹었나? {stack.is_empty()}")
    print(stack)
    
    
if __name__ == '__main__':
    test_static_stack()
멘토스를 준비합니다.
멘토스 준비 완료. 상태: ['초록색', '분홍색', '노란색', '하늘색', '흰색']
현재 멘토스 : 5
 마지막에 있는 멘토스의 색은? 흰색
 마지막에 있는 흰색 멘토스를 꺼내 먹었다.
 마지막에 있는 멘토스의 색은? 하늘색
멘토스  먹었나? False
['초록색', '분홍색', '노란색', '하늘색']
  • 테스트 코드

      import unittest
      from my_stack import StaticStack
        
      class TestStack(unittest.TestCase):
          def setUp(self) -> None:
              self.st = StaticStack(10)
              self.desc = self.shortDescription()
              self.dataset = ['사과', '바나나', '당근', '참외']
        
              if self.desc == "need_dataset":
                  for data in self.dataset:
                      self.st.push(data)
        
          def test_is_empty(self):
              """
              빈 스택 테스트.
              """
              self.assertEqual(self.st.__repr__(), '[]')
              self.assertEqual(self.st.is_empty(), True)
              self.assertEqual(self.st.getLength(), 0)
        
          def test_push(self):
              """
              스택에 항목 삽입 테스트.
              """
              # test1
              data = '오렌지'
              self.st.push(data)
              self.assertEqual(self.st.__repr__(), "['오렌지']")
              self.assertEqual(self.st.getLength(), 1)
              self.assertEqual(self.st.is_empty(), False)
        
              # test2
              for data in self.dataset:
                  self.st.push(data)
              expected_result = "['오렌지', '사과', '바나나', '당근', '참외']"
              self.assertEqual(self.st.__repr__(), expected_result)
              self.assertEqual(self.st.getLength(), 5)
              self.assertEqual(self.st.is_empty(), False)
        
              # test3
              st2 = StaticStack(0)
              actual_result = st2.push('코끼리')
              self.assertEqual(actual_result, False)
              self.assertEqual(st2.is_empty(), True)
        
          def test_pop(self):
              """
              need_dataset\n
              pop 테스트.
              """
              # test1
              st2 = StaticStack(5)
              self.assertEqual(st2.pop(), None)
        
              # test2
              self.assertEqual(self.st.pop(), '참외')
              self.assertEqual(self.st.getLength(), 3)
        
          def test_peek(self):
              """
              need_dataset\n
              스택 맨 끝 항목 조회 기능 테스트.
              """
              # test1
              st2 = StaticStack(0)
              with self.assertRaises(IndexError):
                  st2.peek()
        
              # test2
              self.assertEqual(self.st.peek(), '참외')
              self.assertEqual(self.st.getLength(), 4)
        
          def test_set_new_size(self):
              """
              need_dataset\n
              setNewSize 메서드 테스트.
              """
              # test1
              self.st.setNewSize(20)
              self.assertEqual(self.st.size_, 20)
        
              # test2
              self.st.setNewSize(5)
              self.assertEqual(self.st.size_, 5)
        
              # test3
              self.st.setNewSize(3)
              self.assertEqual(self.st.size_, 5)
        
          def test_clear(self):
              """
              need_dataset\n
              스택을 모두 비우는 테스트.
              """
              # test1
              self.st.clear()
              self.assertEqual(self.st.is_empty(), True)
              self.assertEqual(self.st.getLength(), 0)
              self.assertEqual(getattr(self.st, 'stack'), [])
              self.assertEqual(self.st.__repr__(), '[]')
        
      if __name__ == '__main__':
          unittest.main()
    
      ...스택이 비어있어 반환할 아이템이 없습니다.
      .스택의 최대 크기에 도달하여 요소를 추가할  없습니다.
      .스택  항목들의 수보다  작게 설정할  없습니다.
      현재 스택  항목들의 : 4
      .
      ----------------------------------------------------------------------
      Ran 6 tests in 0.001s
        
      OK
    

스택은 깊이 우선 탐색 (DFS)에서 유용하게 사용된다고 한다. 깊이 우선 탐색은 나중에 다른 페이지에서 다룰 예정.


Reference

[1] 지은이: 미아 스타인, 옮긴이: 최길우, “파이썬 자료구조와 알고리즘”, (2019, 한빛미디어)

This content is licensed under CC BY-NC 4.0

댓글남기기