Do.

Swift - Stack 구현하기 본문

Algorithm

Swift - Stack 구현하기

Hey_Hen 2022. 2. 9. 15:48

 

Stack(스택)은 나중에 입력된 것이 먼저 출력되는 LIFO(Last-In First-Out) 데이터 구조를 나타낸다.

접시에 팬케이크를 쌓아올리면 가장 나중에 만들어서 올린 팬케이크를 가장 먼저 먹는 다는 것이다. 일반적으로 탑같은 모양으로 스택을 표현하기도 한다.

스택은 배열과 유사하지만 배열이 요소에 자유롭게 접근이 가능한 반면 스택은 데이터의 참조를 오로지 끝단에서만 가능하도록 제한한다.

경험상 LIFO 이 특성을 잘 인지하고 있으면 문제를 해결할 때 큰 도움이 된다. 먼저 들어온게 가장 나중에 나가도록 해야하네? 라고 생각한다면 자료를 스택 구조로 저장하면 된다는 것을 생각할 수 있기 때문이다.

 

필수 연산자

Stack의 필수 연산으로 다음 두가지 연산이 있다.

•Push : 스택에 요소를 추가한다. 당연히 탑을 올리듯 현재 요소의 위에 올린다.

•Pop : 가장 맨 위의 요소를 제거하고 출력한다.

public struct Stack<Element> {
  private var storage: [Element] = []
  public init() {}

  public mutating func push(_ element: Element) {
    storage.append(element)
  }

  @discardableResult
  public mutating func pop() -> Element? {
    storage.popLast()
  }
}
 

storage는 직접적인 접근을 제한해야 하기 때문에 private로 선언한다. 또 이번 Stack은 기본적인 배열을 기반으로 하여 구현한다,

 

비 필수 연산자

비 필수 연산자는 반드시 필요하진 않지만 보통 편리한 사용을 위해서 구현하는 기능이다.

•peek : 스택의 가장 꼭대기 값을 제거하지 않고 호출한다.

•isEmpty : 스택의 요소가 비어있는지 확인하는 속성

외에도 필요에 따라 count나 isFull 등을 구현할 수 도 있다.

public func peek() -> Element? {
  storage.last
}

public var isEmpty: Bool {
  peek() == nil
}
 

storage가 배열을 기반으로 하기 때문에 배열에서 구현하는 기능을 그대로 가져올 수 있다.

isEmpty의 경우 메소드로 구현해도 되지만 함수는 가능 이고 속성은 말그대로 속성 이기 때문에 isEmpty는 이 구조체의 일종의 속성임을 뜻하므로 Computed Property로 구현합니다.

https://softwareengineering.stackexchange.com/questions/304077/swift-functions-vs-computed-properties

이미지 썸네일 삭제
Swift functions vs computed properties

Say I have have a class Event as follows: class Event { private var attendees: [Person] = [] // Case 1 //******* // Should I use a func… func countOfAttendees() -> Int { ...

softwareengineering.stackexchange.com

 

편리한 이용을 위한 커스텀

CustomStringConvertible

디버깅 할 때 편의를 주기위함

var stack = Stack<Int>()
for element in 1...10 {
  stack.push(element)
}
print(stack)

//Stack<Int>(storage: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
 

위와같이 CustomStringConvertible을 구현하지 않으면 복잡하게? 표시가 되는데 좀더 편리한 디버깅을 위해 원하는 형식으로 print 결과물을 낼 수 있다.

extension Stack: CustomStringConvertible {
  public var description: String {
    """
    === TOP ===
    \(storage.map { "\($0)" }.reversed().joined(separator: "\n"))
    === === ===
    """
  }
}
 
print(stack)
=== TOP ===
10
9
8
7
6
5
4
3
2
1
=== === ===
 

이미 선언된 배열을 그대로 스택으로 전달하기

let array = [1, 2, 3, 4, 5]

var stack = array
 

위 처럼 사용하기 위함으로 생성자를 만들어 주어 구현할 수 있다.

//in Stack struct
public init(_ elements: [Element]) {
  storage = elements
}
 

ArrayLiteral 표현하기

ArrayLiteral 표현하기는 쉽게말해

var stack1: Stack<Double> = [1, 2, 3, 4, 5]
var stack2: Stack<Int> = []
 

이런식으로 ArrayLiteral을 그댜로 전달하거나 빈 자료로 초기화 하기 위해 사용하는 기능이다.

extension Stack: ExpressibleByArrayLiteral {
  public init(arrayLiteral elements: Element...) {
    storage = elements
  }
}
 

또 for-in 같은 interator는 굳이 구현하지 않았는데

while let _ = stack1.peek() {
  print(stack1.pop())
}
 

위 처럼 그냥 쓸 수 있기 때문이다.

 

Stack.swift

public struct Stack<Element> {
  private var storage: [Element] = []
  public init() {}

  public init(_ elements: [Element]) {
    storage = elements
  }

  public mutating func push(_ element: Element) {
    storage.append(element)
  }

  @discardableResult
  public mutating func pop() -> Element? {
    storage.popLast()
  }

  public func peek() -> Element? {
    storage.last
  }

  public var isEmpty: Bool {
    peek() == nil
  }
}

extension Stack: CustomStringConvertible {
  public var description: String {
    """
    === TOP ===
    \(storage.map { "\($0)" }.reversed().joined(separator: "\n"))
    === === ===
    """
  }
}

extension Stack: ExpressibleByArrayLiteral {
  public init(arrayLiteral elements: Element...) {
    storage = elements
  }
}
 

 

 

Comments