Python Stack 구현

C++에서는 STL (Standard Template Library)에서 Stack 컨테이너를 지원해주지만
Python에서는 따로 제공해주지 않습니다.
List로 간단히 Stack에서 필요한 연산을 구현할 수 있기 때문인데요.

  • 스택 (Stack)은 LIFO 구조입니다.
  • List에서 관련 함수가 많기 때문에 간단하게 Class를 이용해서 구현하여 사용하거나 아니면 List 자체로 그냥 사용하기도 합니다.
  • Stack에서 많이 활용되는 5가지 함수에 대해서 직접 구현해봅시다.
  • Stack은 괄호, 접시, 짝짓기 등의 문제에 사용됩니다.

Top

top은 제일 마지막에 있는 원소를 반환해주면 됩니다. List의 가장 마지막 원소는 -1 Index를 통해 접근할 수 있습니다.

1
stack[-1]

Push

push는 제일 마지막 위치에 원소를 추가해주면 됩니다. List의 append 함수를 이용해 추가합니다.

1
stack.append(num)

Pop

pop은 제일 마지막 원소를 반환해주면 됩니다. List의 pop 함수를 이용할 수 있습니다.

1
stack.pop()

Size

size는 Stack의 현재 사이즈를 반환합니다. List의 len 함수를 이용하면 됩니다.

1
len(stack)

Empty

empty는 stack이 비었는지 판단합니다. List의 len과 0을 비교해주면됩니다.

1
len(stack) == 0

Class로 직접 구현하면 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Stack():
def __init__(self):
self.stack: list = []

def top(self) -> any:
return self.stack[-1]

def push(self, arg: any) -> None:
self.stack.append(arg)

def pop(self) -> None:
self.stack.pop()

def size(self) -> int:
return len(self.stack)

def empty(self) -> bool:
return len(self.stack) == 0

pod install 오류 해결법

  • pod install 명령어 사용시 아래와 같이 에러가 뜰때가 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
 [!] /bin/bash -c 
set -e
#!/bin/bash
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

set -e

PLATFORM_NAME="${PLATFORM_NAME:-iphoneos}"
CURRENT_ARCH="${CURRENT_ARCH}"

if [ -z "$CURRENT_ARCH" ] || [ "$CURRENT_ARCH" == "undefined_arch" ]; then
# Xcode 10 beta sets CURRENT_ARCH to "undefined_arch", this leads to incorrect linker arg.
# it's better to rely on platform name as fallback because architecture differs between simulator and device

if [[ "$PLATFORM_NAME" == *"simulator"* ]]; then
CURRENT_ARCH="x86_64"
else
CURRENT_ARCH="armv7"
fi
fi

export CC="$(xcrun -find -sdk $PLATFORM_NAME cc) -arch $CURRENT_ARCH -isysroot $(xcrun -sdk $PLATFORM_NAME --show-sdk-path)"
export CXX="$CC"

# Remove automake symlink if it exists
if [ -h "test-driver" ]; then
rm test-driver
fi

./configure --host arm-apple-darwin

# Fix build for tvOS
cat << EOF >> src/config.h

/* Add in so we have Apple Target Conditionals */
#ifdef __APPLE__
#include <TargetConditionals.h>
#include <Availability.h>
#endif

/* Special configuration for AppleTVOS */
#if TARGET_OS_TV
#undef HAVE_SYSCALL_H
#undef HAVE_SYS_SYSCALL_H
#undef OS_MACOSX
#endif

/* Special configuration for ucontext */
#undef HAVE_UCONTEXT_H
#undef PC_FROM_UCONTEXT
#if defined(__x86_64__)
#define PC_FROM_UCONTEXT uc_mcontext->__ss.__rip
#elif defined(__i386__)
#define PC_FROM_UCONTEXT uc_mcontext->__ss.__eip
#endif
EOF

# Prepare exported header include
EXPORTED_INCLUDE_DIR="exported/glog"
mkdir -p exported/glog
cp -f src/glog/log_severity.h "$EXPORTED_INCLUDE_DIR/"
cp -f src/glog/logging.h "$EXPORTED_INCLUDE_DIR/"
cp -f src/glog/raw_logging.h "$EXPORTED_INCLUDE_DIR/"
cp -f src/glog/stl_logging.h "$EXPORTED_INCLUDE_DIR/"
cp -f src/glog/vlog_is_on.h "$EXPORTED_INCLUDE_DIR/"

checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for arm-apple-darwin-strip... no
checking for strip... strip
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for arm-apple-darwin-gcc... /Library/Developer/CommandLineTools/usr/bin/cc -arch armv7 -isysroot
checking whether the C compiler works... no
xcrun: error: SDK "iphoneos" cannot be located
xcrun: error: SDK "iphoneos" cannot be located
xcrun: error: SDK "iphoneos" cannot be located
xcrun: error: unable to lookup item 'Path' in SDK 'iphoneos'
/Users/kyle/Library/Caches/CocoaPods/Pods/External/glog/2263bd123499e5b93b5efe24871be317-40a13/missing: Unknown `--is-lightweight' option
Try `/Users/kyle/Library/Caches/CocoaPods/Pods/External/glog/2263bd123499e5b93b5efe24871be317-40a13/missing --help' for more information
configure: WARNING: 'missing' script is too old or missing
configure: error: in `/Users/kyle/Library/Caches/CocoaPods/Pods/External/glog/2263bd123499e5b93b5efe24871be317-40a13':
configure: error: C compiler cannot create executables
See `config.log' for more details

  • 아래와 같이 xcode-select를 이용하면 됩니다.
1
sudo xcode-select --switch /Applications/Xcode.app
  • Applications 밑에 Xcode.app의 이름이 다를 수도 있습니다.
  • 베타 버전의 경우 Xcode-beta.app와 같이 설정되어있습니다.

Short Circuit Evaluation 개념 및 주의점 (단축 평가)

AND(&&) 연산자 혹은 OR(||) 연산자와 같은 논리 연산자, Logical Operator를 사용할 때, 굳이 모든 연산을 다하지 않더라도 결과를 알게 되는 경우가 있습니다.

하나라도 False를 만족하는 AND 연산자하나라도 True를 만족하는 OR 연산자의 경우입니다.

이처럼 앞 부분의 결과만으로 조건을 판단할 수 있을 때 뒷 부분은 생략하는 것을 Short Circuit Evaluation, 단락(단축) 평가라 합니다.

1
2
3
4
5
if(conditionA && conditionB)
// conditionA가 False인 경우 conditionB 검사없이 넘어감

if(conditionA || conditionB)
// conditionA가 True인 경우 conditionB 검사없이 넘어감

Short Circuit Evaluation은 아래와 같이 응용할 수 있습니다.

1
if (myStack.empty() || myStack.top() != num)

만약 myStack이 empty일때 top을 호출하게 되면 런타임 에러가 발생하게 됩니다.
하지만 위와 같이 empty를 먼저 검사하게되면 Short Circuit Evaluation에 의해 empty일 경우 top이 호출되지 않기 때문에 에러를 막을 수 있습니다.

사용시 주의점

앞 부분으로만 결과가 판단되는 경우에 뒷 부분의 구문은 실행되지 않습니다.

아래 예시 코드를 봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream> 

bool LeftCondition(bool b) {
std::cout << "Left Condition Executed " << b << std::endl;
return b;
}

bool RightCondition(bool b) {
std::cout << "Right Condition Executed " << b << std::endl;
return b;
}

int main(void) {
std::cout << "# Case A (false && true) " << std::endl;
if (LeftCondition(false) && RightCondition(true)) {
// Left
}

std::cout << "# Case B (true && true) " << std::endl;
if (LeftCondition(true) && RightCondition(true)) {
// Left, Right
}

std::cout << "# Case C (false || true) " << std::endl;
if (LeftCondition(false) || RightCondition(true)) {
// Left, Right
}

std::cout << "# Case D (true || true) " << std::endl;
if (LeftCondition(true) || RightCondition(true)) {
// Left
}
}

Case A와 D같은 경우 Short Circuit Evaluation에 의해 Left 조건만 실행됩니다.
다시 말해, Right 함수가 호출이 되지 않을 때가 있습니다.
만약 Right 함수이 꼭 호출되어야 한다면 위와 같이 작성할 경우 문제가 되고, 원인 파악이 어렵습니다.
따라서 앞의 조건과 뒤의 조건을 모두 호출하려는 경우엔 코드를 다른 방식으로 작성하여야 합니다.

ref) https://en.wikipedia.org/wiki/Short-circuit_evaluation

2017 팁스타운 - 짝지어 제거하기

[문제 원본]코딩테스트 연습 - 짝지어 제거하기 | 프로그래머스 (programmers.co.kr)

괄호 문제, 짝 짓는 문제 등은 보통 Stack으로 풀릴 때가 많습니다.

같은 문자가 반복되는 짝을 계속하여 제거하였을 때, 모두 제거가 되는지 판단하는 문제입니다.
아래와 같은 논리로 접근하시면 됩니다.

  • 문자열이 홀수인 경우 모두 짝을 지어도 1개는 꼭 남기때문에 바로 0을 return
  • 짝을 판단할 stack 생성 (STL의 stack 컨테이너 사용)
  • 문자열의 처음부터 iterator를 증가시켜가며 진행합니다.
    Stack이 비었을 경우
  • Stack이 비었을 경우 현재 가리키고 있는 문자를 push
Stack이 비어있지 않은 경우
  • Stack의 top과 비교하였을 때 일치하면 pop으로 제거 (짝 지어짐)
  • 일치하지 않으면 push로 stack에 담기

문자열의 길이만큼 Iteration 후 stack이 비어있는지를 return하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <string>
#include <stack>

using namespace std;

int solution(string s) {
if (s.size() % 2)
return 0;

stack<char> st;

for (auto it = s.begin(); it != s.end(); ++it) {
if (st.empty() || st.top() != *it)
st.push(*it);
else
st.pop();
}
return st.empty();
}

2017 팁스타운 - 예상 대진표

[문제 원본] https://programmers.co.kr/learn/courses/30/lessons/12985

간단한 규칙 찾기 및 구현 문제입니다.

A 참가자와 B 참가자가 토너먼트에 참가했을때, 만나게 되는 round의 값을 리턴해주면 되는 문제입니다.

이겼을 때 다음 번호의 규칙성을 파악하면 쉽게 풀립니다.

  • 1번의 참가자가 이겼을 경우) 다음 번호는 1번
  • 4번의 참가자가 이겼을 경우) 다음 번호는 2번
  • 3번의 참가자가 이겼을 경우) 다음 번호는 2번
  • 15번의 참가자가 이겼을 경우) 다음 번호는 8번
  • 16번의 참가자가 이겼을 경우) 다음 번호는 8번

-> (1) 나누기 2를 한 다음 (2) 올림을 하는 규칙임을 파악할 수 있습니다.
loop를 돌면서 a, b가 같은 번호일 때의 round를 return하면 됩니다.

1
2
3
4
5
6
7
8
9
10
#include <iostream>

int solution(int n, int a, int b) {
for (int round = 1; round < 20; round++) {
a = a / 2 + a % 2;
b = b / 2 + b % 2;
if (a == b)
return round;
}
}

Namespace

네임스페이스는 코드에서 이름이 서로 충돌하는 문제를 해결하기 위해 나온 개념입니다. 예를 들어 foo라는 이름의 함수를 만들어서 사용하다가 외부 라이브러리를 참조했을 때, 거기에도 foo라는 함수가 있다면 충돌이 발생합니다. 이럴 경우, 컴파일러는 foo가 어떤 함수를 가리키는지 알 수 없게 됩니다.

아래와 같은 간단한 예제 코드를 살펴봅시다.

[namespace 예제 코드]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

namespace nameA {
void foo() {
std::cout << "It is in nameA" << std::endl;
}
};

namespace nameB {
void foo() {
std::cout << "It is in nameB" << std::endl;
}
};

int main(void) {
nameA::foo();
nameB::foo();

return 0;
}

[실행 결과]

1
2
It is in nameA
It is in nameB

🎊 Scope Resolution Operator인 :: 기호를 이용해서 사용하면 됩니다.

nested namespace라고 해서 namespace를 중첩해서 사용하는 것도 가능합니다. namespace alias로 이름을 다르게 표현할 수 있는데 아래 예제를 통해 두가지를 한번에 확인해봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

namespace A {
namespace B {
namespace C {
void foo() {
std::cout << "Nested namespace example" << std::endl;
}
}
}
}

namespace a::b::c {
void foo() {
std::cout << "Supported C++17" << std::endl;
}
}

int main(void) {
namespace ABC = A::B::C;
namespace abc = a::b::c;

A::B::C::foo();
a::b::c::foo();
ABC::foo();
abc::foo();

return 0;
}
1
2
3
4
Nested namespace example
Supported C++17
Nested namespace example
Supported C++17

🎊 만약 사용하는 빈도수가 높은 경우 using 이라는 지시자를 통해 컴파일러에 처리하도록 하면 됩니다. 보통 c++ 코드에서 쓰이는 using namespace std; 도 std라는 namespace를 :: 기호 없이 사용하겠다는 뜻으로 쓰인 구문입니다.

[using 예제 코드]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>

using std::cout;

namespace nameA {
void foo() {
cout << "It is in nameA" << std::endl;
}
};

namespace nameB {
void foo() {
cout << "It is in nameB" << std::endl;
}
};

using namespace nameA;

int main(void) {
foo();
nameB::foo();

cout << "using std::cout (not endl)" << std::endl;

return 0;
}
1
2
3
It is in nameA
It is in nameB
using std::cout (not endl)

using namespace를 통해 nameA:: 를 생략하여 사용하였습니다. (using을 했어도 nameA를 붙여도 무방합니다.) 또한, using namespace가 아니라 using 후 특정 항목만 지정해서 사용가능합니다. using std::cout 을 통해 컴파일러에게 지시를 하여 cout은 std::없이 사용했지만 endl의 경우 std::endl로 사용하였습니다.

🧨 웬만하면 using 지시자는 사용하지 않는 방향으로 개발하시는 것이 좋습니다. 예상치 못한 변수나 함수명이 겹치는 경우가 발생할 수 있기 때문입니다.

ref) https://en.cppreference.com/w/cpp/language/namespace