Baekjoon 1541번 잃어버린 괄호
URL : https://www.acmicpc.net/problem/1541
==============================================
Contents
1. Problem
2. Error & Fail
3. Solution
4. My Code
5. Other's Code
6. Shortest Code
==============================================1. Problem
- 양수, +, -, (, ) 5가지로 구성된 최대 50 길이의 식을 만들었다. 그러나 괄호를 모두 지웠다. => 양수, +, - : 3가지로만 구성된 식이 입력으로 주어진다.
- 적절한 괄호를 쳐서 입력값의 식이 최소가 되도록 만든다.
- 처음과 마지막은 숫자이다.
- 연속해서 연산자를 사용하지 않으며 숫자는 5자리를 넘지 않는다.
- 수는 0으로 시작할 수 있다.
2. Error & Fail
2-1) Operator +, -
우선 type('+') 는 str이지만 operator +,- 는 그 어디에도 속하지 않는다.
즉 +를 무언가로 입력받고 싶지만 사실 operator는 앞과 뒤에 인자가 존재해야만 의미가 있기때문에 다음과 같이 형성될 수 밖에 없다.
int1 + int2 = 계산된 int
int1 - int2 = 계산된 int
str1 + str2 = str1str2
str1 - str2 = 존재할 수 없음
참고로 -는 같은 operator이니 동일하다. 다만 str-str은 존재할 수 없다. 때문에 다음과 같은 에러가 뜬다.
TypeError: unsupported operand type(s) for -: 'str' and 'str'
2-2) Eval
+는 '+'로 밖에 존재할 수 없다면 우리는 eval python 내장함수를 사용해야한다.
eval(equation) = equation 결과값이 나오기 때문에 사용이 가능하다.
그러나 여기서도 문제가 있다.
바로 eval('3+4')와 같은 수식적으로는 문제가 없는 것에서는 바로 계산 결과를 받지만
eval('03+04')와 같이 앞의 숫자에 0이 붙으면 그것은 계산이 불가능하다.
이것이 많은 사람들이 틀린 이유이기도 하다.
2-3) 정수가 아닌 문자를 골라내는 방법
'55-50+30'에서 +나 -를 하나의 문자를 구분하고자 할 때 필요했던 것은 str.isdigit()이었지만 나는 기억이 나지않아 일일이 if c[i] == '+' or c[i] == '-':와 같이 구분할려고 애를 썻지만 간단하게도 str.isdigit()으로 Bool 계산이 가능했다.
2-4) 괄호를 칠 수 있다는 궁극적인 의미
이 문제를 처음 접하고 괄호를 치면되는거지하고 바로 시작해서 list에 '정수', '+', '-' 형태의 string으로 분류할려고 할 것이고 그 다음으로 +나 - 앞에는')'를 뒤에는 '('를 넣을려고 시도했을 수도 있다.
참고로 이것은 내가 그랬다.
결과적으로 다음과 같은 순서의 코드를 짰다.
'55-50+30' => ['55', '-', '50', '+', '30']
다른 list에 append or insert
[['(', '55', '-', '50', ')', '+', '30'],
['(', '55', '-', '50', '+', '30', ')'],
['55', '-', '(', '50', '+', '30', ')']]
각각의 min(sum) 뭐 이런 식이었다.
그러나 결과는? 시간 초과! 혹은 런타임 에러!
그러나 본질은 그것이 아니다.
+, -, 정수의 형태에서 ()를 쳐서 최소값을 구하는 것은 -를 어떻게 바라볼 수 있느냐의 문제이다.
3. Solution
3-1) +,-, 정수의 조합에서 -라는 Operator 의 본질
다음 수식을 문제에서 요구하는 방식으로 풀어보자.
1+2-3+4-5+6-7-8+9+10
=> 1+2-(3+4)-(5+6)-(7)-(8+9+10)
33-70+50-90
=> 33-(70+50)-(90)
특징이 보이는가? 한 개만 더 풀어보자
80+90-110+100+90-80
=> 80+90-(110+100+90)-(80)
내가 왜 - 앞에다만 ()를 쳤는지 이해가 가는가
최소를 가진다는 의미는 맨 앞의 숫자를 더하는 뭉치를 제외하고는 결과적으로 뒤 모두를 -로 계산함으로써 최소가 되게할 수 있다.
다시 쓰면 1+2-(3+4)-(5+6)-(7)-(8+9+10) = 3-7-11-7-27 = 3-(7+11+7+27)이 되므로
3을 제외하고 나머지는 다 뺀 상태가 된다.
두 번째 식을 보자.
33-(70+50)-(90) = 33-120-90 = 33-(120+90)
33을 제외하고 나머지는 다 뺀다.
세 번째 식은?
80+90-(110+100+90)-(80) = 80+90 - (110+100+90+80)
특징이 이제 보이는가?
그럼 우리는 -를 기준으로 괄호를 치면되는 것이고 -가 시작되는 지점을 기준으로 앞은 덧셈 뒤는 뺄셈으로 계산하면 된다.
3-2) Eval('000int'))의 문제
Eval은 양날의 검이라는 말을 자주사용한다. 강력하지만 그만큼 보안상으로 위험하다.
https://ko.wikipedia.org/wiki/Eval
https://namu.wiki/w/eval
https://wikidocs.net/32#eval
그러나 우리는 이것을 사용하고 싶고 그것이 지금 뜻대로 되지않는다.
eval('004+3') 우리가 생각하기에 수식적으로 문제가 없으니 12 아닌가 하겠지만 실행되지 않는다.
그렇다면 우리는 어떻게 해야할까
우선 eval()을 하지 않고 int화 시켜서 각각 계산하는 방법이 있다.
가령 '56-003+044-33'이 있을 때 이것을 int 화 시켜서
int(56)-int(003+044)-int(33)으로 계산이 가능하다.
Operator를 치환할 방법이 없기에 힘들 수도 있지만 split을 통하면 가능하다.
str.split('-')처럼 split()에 우리가 경계로 삼고자하는 것을 토대로 나눠준다. 때문에 3-1의 내용을 기반으로 split('-')로 나눈다면 우리는 a라는 리스트에 -를 기준으로 나눠지고 그것들을 다시 +를 기준으로 b list에 split('+') 한다면 정수만 남게되어 따로 계산 결과를 도출할 수 있다.
표를 보면 이해가 빠르다.
Input='55-030+0040+80-90' | 1 | 2 | 3 |
---|---|---|---|
A | '55' | '030+0040+80' | '90' |
B | int(030)+int(0040)+int(80) | ||
A | 55 | 30+40+80 | 90 |
만약 eval을 쓴다면 위의 방법으로 int한 것을 다시 str로 변형시켜 +로 만들고 다시 집어넣는 방식을 택해야하는데 굳이 그럴 필요가 없다.
물론 내가 모르는 것일 수도 있지만....
4. Code - 371B 56ms
N = input().split('-') val = 0 for i,e in enumerate(N): if i == 0: if e.isdigit(): N[i] = int(e) else: N[i] = sum(list(map(int, N[i].split('+')))) val += N[i] else: if e.isdigit(): N[i] = int(e) else: N[i] = sum(list(map(int, N[i].split('+')))) val -= N[i] print(val)
5. Other's Code - rapaeljin 92B 56ms
n = [sum(int(x) for x in y.split('+')) for y in input().split('-')] print(n[0] - sum(n[1:]))
배울점 1. 입력 자체를 list 안의 for문에서 받아 또한 번의 for 문을 내부에서 수행, 합을 구하는 방식은 저와 내용은 유사하지만 코드길이면에서 압도적으로 짧습니다.
배울점 2. list의 가장 큰 특징 slice를 잘 활용했습니다.
저는 이 방법을 기억하지 못해 i==0일 때는 더하고 그 이외의 것들은 뺀다라는 생각으로 나열했지만 보시는 것과 같이 위와 같이 n[0] 즉 앞에 -가 나오기 전까지의 합을 나타내고 sum(n[1:])은 나머지 뒤에 있는 것들은 싹다 더해 앞의 것에서 뺀다는 관점 놀랍습니다.
6. Shortest Code - sait2000 64ms - 2년전, minkj1992, youngminz 76B 60ms 8개월전
a,*b=[sum(map(int,s.split('+')))for s in input().split('-')];print(a-sum(b))
시간이 지남에 따라 빨라지기도 혹은 느려지기도 하는 코드인데 이 코드는 빨라졌네요.
저는 왜 느려졌는지 파악하지 못했으나 아신다면 댓글 남겨주시면 감사하겠습니다.
아마 map이거나 a, *b의 형태 slice가 시간을 잡아먹은 것은 아닌지 생각합니다만....
배울점 1. a, *b = list의 형태는 첫 번째 값은 a에 나머지는 b에 넣는다는 뜻이라네요.
a,b,*c=[5,6,7,8] # a=5, b=6, c=[7,8]
a,*b,c=[5,6,7,8] # a=5, b=[6,7], c=8 #이런 것도 되네요
Cf) a,*b,*c,d = [1,2,3,4,5,6,7] 이런 것도 되나했는데
SyntaxError: two starred expressions in assignment
역시나 에러가 뜨네요
Baekjoon 1541을 통해 배운 것
- +, - Operator는 str이나 int type 변환이 불가능하다.
- eval()함수는 주의해야하며 수식의 형태가 0이 포함하는 숫자가 되면 실행되지 않는다.
eval('4+003') != 12 #실행되지 않음 - str.split(경계문자)를 통해 나누는 것이 편리하다.
- list에 for문을 돌릴 때 input을 넣어도 큰 복잡도를 가지지 않는다.
- a,*b = []와 같이 리스트에서 나머지를 표현할 때 *를 사용하기도 한다.
단, 두 개의 *는 사용 불가