BOJ 괄호의 값 - 2504

www.acmicpc.net/problem/2504

 

2504번: 괄호의 값

4개의 기호 ‘(’, ‘)’, ‘[’, ‘]’를 이용해서 만들어지는 괄호열 중에서 올바른 괄호열이란 다음과 같이 정의된다. 한 쌍의 괄호로만 이루어진 ‘()’와 ‘[]’는 올바른 괄호열이다.  만일

www.acmicpc.net

 

괄호와 관련해서 원래 스택의 기본 문제가 짝이 지어지느냐 마느냐인데, 이 문제는 연산까지 더해집니다. 괄호는 (, ), [, ]를 사용하며 A와 B는 이미 연산된 수를 의미합니다. 연산의 규칙은 다음과 같습니다. 

1. () = 2

2. [] = 3

3. (A) = 2*A

4. [A] = 3*A

5 AB = A+B

 

스택을 이용하면서 연산까지 같이 하면서 집어넣어줘야합니다. 어떻게 할 수 있을까요? 입력 예를 보면서 어떻게 풀어나갈지 생각해봅시다.

(()[[]])([])

 

큰 틀은 아래와 같은데요. 

1. 우리가 여는 괄호를 만나면 우선 스택에 집어넣습니다.

2. 닫는 괄호가 나오면 매칭되는 괄호가 나올때까지 이전에 계산한 수를 모두 더합니다. 그리고 여는 괄호가 '('이냐, '[' 이냐에 따라서 2를 곱하거나 3을 곱해서 다시 스택에 집어넣습니다.

3. 모든 괄호를 전부 만났다면 스택에 있는 모든 수를 더합니다.

 

이제 그림을 통해서 보도록 하겠습니다. 우선 처음 ( ( 는 여는 괄호이므로 전부 스택이 집어 넣습니다. 이 후 최초로 닫는 괄호 )가 나오니 연산해서 다음 데이터를 집어넣을 차례입니다.

 

여는 괄호 (를 만날때까지 스택에 있는 모든 수를 더하지만 아직 더할 수는 없습니다. 그래서 ()에 해당하는 2를 집어넣습니다. 하지만 여기서는 음수로 집어넣지요. 다음 '[', '[' 역시 여는 괄호이기 때문에 스택에 집어넣습니다.

 

그 후 닫는 괄호 ']'가 나오니까 여는 괄호가 나올때까지 있는 수를 모두 더하고 3을 곱한 수를 넣어야하지만 없으므로 []에 해당하는 -3을 집어넣습니다. 그리고 다시 닫는 괄호 ]이 나오네요. 이때 이제 그 전에 더할 수 -3이 있지요. 

 

그래서 -3을 더하고 난 후 [X]에 해당하는 3을 곱해줍니다. -9가 되겠네요. 이 값을 다시 스택에 집어넣습니다. 다시 닫는 괄호 )가 나오면 앞에 있던 방법으로 모두 더한후 (X)에 해당하는 연산을 하면 -22가 스택에 들어가게 됩니다.

 

 

남아있는 ([])는 아래와 같이 연산됩니다. 

 

이제 괄호의 식이 전부 끝났죠? 그러면 스택에 남아있는 모든 수를 더하고 -1을 곱하면 그게 답입니다. 아래는 위의 풀이를 코드로 구현한 것입니다. 큰 틀은 이렇습니다. N은 문자열의 길이입니다. 여는 괄호가 나오면 모두 스택에 집어넣습니다. 닫는 괄호가 나오면 getNextSum()을 통해서 스택에 있는 모든 수를 더하고 난 이후 맞는 괄호에 해당하는 수를 곱한 값을 받아옵니다. 이후 이 값을 스택에 다시 집어넣죠.

stack<int> st;

int main() {
	char str[31];
	scanf(" %s", str);

	int N = strlen(str);
	for (int i = 0; i < N; i++) {
		char next = str[i];
		switch (next) {
			case '(':
			case '[':
				st.push(next);
				break;

			case ')':
			case ']':
			{
				int s = getNextSum(next);
				if (s == IMPOSSIBLE) {
					printf("0\n");
					return 0;
				}
				st.push(s);
				break;
			}
		}
	}
    
    //...//
 }

 

getNextSum() 함수는 아래와 같이 구현이 되었습니다. 만약 next에 해당하는 데이터가 문자(양수)라면 여는 괄호를 찾고 음수이면 그 수를 더합니다. 여기서 왜 숫자를 음수로 집어넣는지 이해가 가나요? 문자 자체도 ascii 코드로 숫자로 표시될 수 있기 때문에 문자와 숫자를 구분하기 위해서 음수로 집어넣었던 것입니다.

 

만약 아무런 값도 들어있지 않다면 (), []이기 때문에 -2나 -3을 출력해주면 되고 값이 있다면 모두 음수로 더해서 2, 3을 곱한 값을 출력해주면 됩니다. 이때 식은 (X) 이거나 [X] 에 해당합니다.

 

이때 매칭이 되지 않는 괄호는 impossibleOpen이라고 하며 스택을 돌다가 이 괄호가 걸리게 되면 IMPOSSIBLE이라는 -1을 반환합니다. 

#define IMPOSSIBLE -1
int getNextSum(char next) {
	int sum = 0;
	char open = next == ')' ? '(' : '[';
	char impossibleOpen = next == ')' ? '[' : '(';

	while (!st.empty()) {
		int prev = st.top();
		st.pop();
		if (prev == open) 
			return sum == 0 ? (next == ')' ? -2 : -3) : (next == ')' ? sum * (-2) : sum * (-3));
		
		if (prev == impossibleOpen)  return IMPOSSIBLE;
		
		sum -= prev;
	}
	
	return IMPOSSIBLE;
}

 

마지막 과정에는 스택에 괄호가 있는지 없는 지 검사한 후 괄호가 있으면 이 괄호식은 매칭이 되지 않는 식이므로 0을 출력하고 종료하면 되고, 그렇지 않고 숫자만 있는 상태면 모두 더해서 출력해주면 됩니다.

	int ans = 0;
	while (!st.empty()) {
		if (st.top() == '[' || st.top() == ']' || st.top() == '(' || st.top() == ')') {
			printf("0\n");
			return 0;
		}
		ans -= st.top();
		st.pop();
	}

	printf("%d\n", ans);

 

전체코드입니다.

#include <cstdio>
#include <stack>
#include <string.h>
using namespace std;

stack<int> st;
#define IMPOSSIBLE -1
int getNextSum(char next) {
	int sum = 0;
	char open = next == ')' ? '(' : '[';
	char impossibleOpen = next == ')' ? '[' : '(';

	while (!st.empty()) {
		int prev = st.top();
		st.pop();
		if (prev == open) 
			return sum == 0 ? (next == ')' ? -2 : -3) : (next == ')' ? sum * (-2) : sum * (-3));
		
		if (prev == impossibleOpen)  return IMPOSSIBLE;
		
		sum -= prev;
	}
	
	return IMPOSSIBLE;
}

int main() {
	char str[31];
	scanf(" %s", str);

	int N = strlen(str);
	for (int i = 0; i < N; i++) {
		char next = str[i];
		switch (next) {
			case '(':
			case '[':
				st.push(next);
				break;

			case ')':
			case ']':
			{
				int s = getNextSum(next);
				if (s == IMPOSSIBLE) {
					printf("0\n");
					return 0;
				}
				st.push(s);
				break;
			}
		}
	}

	int ans = 0;
	while (!st.empty()) {
		if (st.top() == '[' || st.top() == ']' || st.top() == '(' || st.top() == ')') {
			printf("0\n");
			return 0;
		}
		ans -= st.top();
		st.pop();
	}

	printf("%d\n", ans);

}

 

반응형
블로그 이미지

REAKWON

와나진짜

,

파이썬 파일 다루기(File Handling)

모든 언어에서 파일을 다루는 것은 매우 중요한 일이고 필수적으로 알아두어야합니다. 프로그래밍에서 파일을 읽고 분석하는 작업, 그리고 결과를 기록하여 보관하는 작업 등 매우 필수적이기 때문이죠. 여러분이 어떤 실행파일을 설치했는데 그 설정된 이력을 어떻게 보관할 수 있을까요? 눈치 채셨겠지만 파일의 형태로 어딘가에 저장하고 읽고 수정합니다. 텍스트 파일로든 이진 파일로든 말이죠. 이제 파이썬에서 파일을 어떻게 다룰수 있는지 여러 예를 보면서 알아보도록 하겠습니다.

1. 파일 열기 - open

파일을 사용하기 위해선 가장 먼저 해야하는 일이 파일을 여는 것입니다. 파일을 열때 사용하는 함수가 open함수입니다. 이름이 너무 직관적이라서 기억하기도 쉽군요. open은 두가지 인자를 받을 수 있습니다. 하나는 파일 이름, 다른 하나는 file open mode입니다. mode는 아래에 표를 참고해주세요.

mode desc
"r" Read를 뜻하며 파일을 수정하는 용도가 아니라 읽기 전용으로 엽니다. 파일이 없으면 에러가 발생합니다.
"w" Write를 뜻하며 파일을 수정할때 사용하지만, 이미 파일에 내용이 있다면 새로 다시 씁니다. 파일이 존재하지 않으면 새로 생성합니다.
"a" Append를 뜻하고 파일에 내용을 덧붙일때 사용하는 mode입니다. "w" 모드는 새롭게 덮어쓰는 것이고, "a" 모드는 뒤에 추가한다는 점이 다릅니다. 역시 파일이 존재하지 않으면 새롭게 생성합니다.
"x" Create를 의미하며 파일을 생성합니다. 파일이 존재하면 에러를 반환합니다.

 

여기에 추가적으로 파일이 이진 파일이냐, 사람이 읽을 수 있는 텍스트 파일이냐에 따른 mode도 존재합니다.

mode desc
"t" Text를 뜻하며 텍스트 모드로 파일을 엽니다. open에서 mode를 지정하지 않으면 테스트 모드로 읽습니다.
"b" Binary를 뜻하며 이진 파일을 읽습니다. 예를 들면 이미지같은 파일을 의미하는 것이죠.

 

open은 호출이 완료되면 파일 객체를 반환해줍니다. 그리고 이 객체를 통해서 읽기, 쓰기 작업이 이루어질 수 있죠. 파일에 대한 작업을 완료하면 파일 객체의 close 메소드로 받드시 닫아주어야합니다.

※close() 를 반드시 해야하는 이유

보통 close()를 안하시고 중요하게 생각하지 않는 사람들이 많은데 그것은 우리들의 프로그램이 금방 끝나기 때문입니다. 아주 빈번하게 발생하는 문제점은 이런 상황입니다. 한글 파일을 연 상태에서 그 파일을 삭제시켜 보세요. 어디에서 열려있다고 하면서 삭제 동작을 하지 않습니다. 마찬가지로 프로그램 내에서 파일을 다 사용했는데 열어놓으면 다른 쓰레드나 프로세스가 쓸 수 없는 상황이 발생합니다. 이해하시겠죠? 아래 영화폴더는 저의 보물창고입니다.

 

아래의 코드는 exam.txt라는 파일을 읽기 전용으로 텍스트 모드로 열고 닫는 예를 보여줍니다.

f = open("exam.txt","rt")
f.close()

 

2. 파일 쓰기 - write, writelines

write를 통해서 파일에 내용을 쓸 수 있습니다. 이때 여러 줄을 쓸때는 List 자료형이나 Tuple을 사용할 수 있는 writelines 메소드도 존재합니다. 아래는 파일에 문자열을 쓰는 코드의 사용방법을 보여줍니다.

f = open("exam.txt","wt")
#일반적으로 쓰는 write
f.write("============write test===============\n")

# 리스트로 한번에 넣어버리기
lines = ["write\n","list\n","lines\n"]
f.writelines(lines)

# 튜플로 한번에 넣어버리기
lines = ("write\n","tuple\n","lines\n")
f.writelines(lines)

f.close()

 

위 코드를 작성하고 실행하면 프로젝트와 같은 디렉토리에 파일이 생겨납니다. 우리가 썼던 그 텍스트 내용인것을 확인할 수 있습니다.

write

 

3. read, readline

이제 이 파일을 읽어보도록 할까요? 읽기 위해서는 파일 객체의 read류의 메소드들을 사용하여 읽을 수 있습니다. read는 기본적으로 한글자씩 읽어오는 메소드입니다. 활용하는 방식은 아래와 같이 파일의 내용이 없을때까지 출력합니다. read()는 더 이상 읽을 내용이 없으면 빈 문자열을 반환합니다. 그리고 컴퓨터 공학에서는 이것을 파일의 끝(EOF: End Of File)이라고 합니다. C언어와 같은 언어에서는 EOF는 -1이라는 것은 그냥 참고만 하세요.

f=open("exam.txt","rt")
while True:
    c = f.read()
    if c == '':
        break
    print(c, end='')

f.close()
============write test===============
write
list
lines
write
tuple
lines

 

혹은 그냥 줄 단위로 가져오고 싶지는 않으신가요? 그럴땐 readline을 사용하여 가져올 수 있습니다. readline 역시 더 이상 읽을 데이터가 없다면 빈 문자열을 반환합니다.

f=open("exam.txt","rt")

while True:
    line = f.readline()
    if line == '' :
        break
    print(line, end='')

f.close()

 

writelines와 마찬가지로 리스트 형태로 여러 줄들을 읽어올 수도 있습니다. 그래서 for문으로 그 줄들을 순회할 수 있습니다.

f=open("exam.txt","rt")

lines = f.readlines()
for line in lines:
    print (line, end='')

f.close()

 

4. 파일 삭제 - remove

파일을 생성하고 쓰는 방법은 알았는데, 파일을 삭제하려면 어떤 방법으로 삭제를 할까요? os 모듈을 통해서 파일이 존재하는지 확인할 수 있고 삭제할 수 있습니다. 아래 코드는 해당 파일이 존재한다면 삭제하고, 아니라면 존재하지 않는 다는 출력을 해주지요. 

import os
if os.path.exists("exam.txt"):
  os.remove("exam.txt")
else:
  print("not exist")

 

해당 코드를 실행하면 우리가 지금까지 썼던 exam.txt 파일은 삭제되었음을 알 수 있습니다.

 

여기까지 파이썬으로 파일을 다루는 아주 기초적인 활용 예제들을 보았습니다. 파이썬이 다른 언어에 비해서 파일을 다루는 게 단순한 편입니다. 잘 숙지하시고 연습많이 하세요.

반응형
블로그 이미지

REAKWON

와나진짜

,