4358번: 생태학
프로그램은 여러 줄로 이루어져 있으며, 한 줄에 하나의 나무 종 이름이 주어진다. 어떤 종 이름도 30글자를 넘지 않으며, 입력에는 최대 10,000개의 종이 주어지고 최대 1,000,000그루의 나무가 주어
www.acmicpc.net
문제를 해석함에 있어서 어려운 부분은 없었다.
입력을 받고 dict()으로 key(나무) value(입력 수) 형태로 받아들인 다음, 백분율을 출력하면 끝.
예제 입력 1을 보았을 때, 입력 제한이 없었다.
그러니까 총 몇 그루를 받아들이는지에 대한 입력이나, 0을 받았을 때 종료하는 그런 식의 조건 말이다.
그렇기에 try-except를 바로 생각했고, 쉽게 풀리리라고 생각했다.
# ---------- Import ----------
import sys
input = sys.stdin.readline
# ---------- Main ----------
tree_cnt = 0
tree_dict = {}
while True:
try:
tree = input().rstrip()
tree_dict[tree] = tree_dict.get(tree, 0) + 1
tree_cnt += 1
except EOFError:
result = sorted(tree_dict.items())
for tree, cnt in result:
print(tree, round(cnt / tree_cnt * 100, 4))
break
문제가 너무 쉬워서 코드에 대한 자세한 설명은 생략하고 개략적으로만 적어두겠다.
tree를 입력받을 때, sys.stdin.readline으로 받았기에, rstrip()으로 \n을 지워준다.
그 아래 get에 대한 자세한 코드 설명은 아래 링크를 확인하자.
└ 문제: https://www.acmicpc.net/problem/18111
└ 정리: https://miny-genie.tistory.com/185
그리고 돌면서 파일에서 EOF을 만났다면 에러를 발생해서 except로 들어간다.
└ 백준의 입력이 파일로 들어온다는 것은 https://miny-genie.tistory.com/169 P.S.1에서 알았다.
except에서 정해진 백분율 계산만 진행하면 완료다.
나는 문제가 생긴다면 아마 또 다시 반올림 차이로 인한 "틀렸습니다." 정도로 예상하고 있었다.
? 시간 초과가 떴다
끽해봐야 틀렸습니다가 뜰 줄 알았는데... 시간 초과?
VSC에서 EOF 에러를 직접 띄워서 테스트를 진행해보았다.
└ VSC 콘솔에서 EOF를 띄우는 방법은 Ctrl + Z이다. ^Z이 EOF를 나타낸다.
이때 문제가 발생했다. EOF를 입력해도 except로 넘어가지 않고 무한 루프를 돈다.
아... 이래서 1%도 못 넘어가고 시간 초과가 발생했구나 싶었다.
# Before
import sys
input = sys.stdin.readline
tree = input().rstrip()
# After
tree = input()
하지만 왜 EOF가 안 되는 거지?라는 생각에 한 가지 가설을 세웠다.
EOF는 말 그대로 파일의 끝일 것이다. 즉 EOF 뒤에는 \n이 오지 않는다.
하지만 tree = input().rstrip()에서 rstrip()이 EOF를 지울 것이다.
그래서 tree에는 공백이 들어가고 무한 루프를 도는 것이다.
이렇게 생각해서 표준 입력 input()을 사용하니 VSC에서 제대로 Ctrl + Z가 먹혀 중단했다.
이러면 되겠군! 하고 제출했다.
...또 시간 초과가 발생했다.
무언가 놓치고 있는 게 있다. 1%도 못 넘어간다는 이야기는 무한 루프라는 이야기이다.
그래서 우선 내가 처음으로 try - except를 배웠던 문제를 찾아가보았다.
└ 문제: https://www.acmicpc.net/problem/10951
위의 사진은 10951 문제와 이번 4358 문제와 입력을 비교한 사진이다.
같지만 살짝 다르다.
10951 문제는 입력에 대한 종료 조건이 정말 아예 없다. 그저 계속 받아들인다고 할 뿐.
하지만 4358은 입력 조건이 있다...! 입력에는 최대 1,000,000 그루의 나무가 주어진다.
즉 최대 1,000,000이라는 입력 이후 종료한다는 것이다.
그래서 생각을 바꿔보았다. EOF가 아닌 입력이 없을 때 종료하면 되려나?
# ---------- Import ----------
import sys
input = sys.stdin.readline
# ---------- Main ----------
cnt = 0
dict_ = {}
while True:
tree = input().rstrip()
if not tree: break
dict_[tree] = dict_.get(tree, 0) + 1
cnt += 1
for t, c in sorted(dict_.items()):
print(t, round(c / cnt * 100, 4))
그래서 처음에 제출한 코드를 살짝만 바꾸었다.
try-except문을 삭제하고, 입력이 없다면(if not tree) break로 반복문을 빠져나온다.
그리고 마지막 백분율 계산을 실행한다.
위에서 생각한 가정이 맞다면 이 코드는 정상적으로 동작하리라 생각했다.
역시나 틀린다면 반올림 때문이겠지.
틀렸다. 그래도 TLE가 아니라는 것에 감사한다.
반올림이라 생각하고 0.000001을 더하는 방식으로 진행해보았다.
└ 이유: https://miny-genie.tistory.com/169
어라? 왜 또 틀렸지?
부동소수점 연산에서 뭔가 틀렸나?? 0.000001이 아니라 확실하게 함수를 작성해주었다.
아니 잠깐잠깐잠깐 이거 왜 이래.
뭔가 놓치고 있는 게 있다.
TLE가 아니라면 if not tree 형식은 맞았다는 건데, 아무리 생각해도 틀릴 부분이 반올림뿐이다.
문제 출력 조건을 읽어보았다.
"주어진 각 종의 이름을 사전순으로 출력하고, 그 종이 차지하는비율을 백분율로 소수점 4째자리까지 반올림"
sorted()로 사전순으로 정렬은 했다. 그럼 남은 건 반올림인데, round...round...round...
아
아
# ---------- Import ----------
import sys
input = sys.stdin.readline
# ---------- Main ----------
cnt = 0
dict_ = {}
while True:
tree = input().rstrip()
if not tree: break
dict_[tree] = dict_.get(tree, 0) + 1
cnt += 1
for t, c in sorted(dict_.items()):
ratio = round(c / cnt * 100, 4)
print(f'{t} {ratio:.4f}')
아 이런... round 함수는 뒤에 존재하는 0을 모두 없애고 출력한다.
나는 4.0000을 원하는 거지 4.0이라는 깔끔한 숫자를 원하는 게 아니다.
그래서 ratio라는 변수에 저장을 한 번 해준 뒤에 f-string으로 출력해주었다.
물론 바로 f-string으로 출력해도 상관없지만, 가독성을 위해 변수에 저장한 뒤 출력해주었다.
다행히 맞았습니다!!가 떴다.
하지만 Python3에서 TLE가 떴다고 PyPy3로 제출하지 않고 Python3으로 방법을 찾는 것처럼,
오기가 발생했다. (또또 이상한 병이 도졌다.)
강렬하게 try-except로 풀고 싶다.
왜 try-except로는 풀 수 없는 거지? 말이 안 되지 않나?
그래서 cpp를 할 줄 아는 친구 한 명을 데려다가 문제를 풀게 시켰다.
우선 EOF로 받아들였을 때 돌아가는지가 궁금했다.
// 반복문을 통해 eof가 들어오면 정지, 이후 정렬
while (!std::cin.fail())
{
getline(std::cin, buf);
array.emplace_back(buf);
}
코드 전문을 가져오기에는 의미가 없어서 핵심만 가져와봤다.
친구놈이 작성해 준 코드이다. 역시나 이놈도 EOF로 문제를 접근했다. 그래서 결과는?
...맞는다. 아니 왜???
왜 Python3에서만 EOF 처리가 안 되는 건데??? 평소에는 잘만 됐는데???
아닌가? 그냥 다른 무한 루프를 돌고 있는 건가?
의문이었다.
그래서 정말 다양한 방법을 시도해보았다.
break가 실은 인식을 못해서 무한루프를 돌고 있다든지, 그래서 exit()으로 강제 종료를 해보았다.
try-except를 위해 강제로 EOFError를 raise했다. 그러기 위해서는 조건이 필요하다.
그래서 문제가 not인지 ""인지 " "인지 확인을 해보았다.
그것도 아니면 str을 int()로 바꾸면 에러가 발생한다. EOF의 타입은 뭐지?
한번 input()을 str()으로 바꾸어서 입력을 받아와볼까?? (int()로 받으면 기존 나무도 못 받아온다.)
실은 EOFError가 아니라 EOFerror였다든가?
그리고 장렬하게 전사했다.
대체 왜... try-except로 해결할 수 없는 거야...
분명 내가 모르는 무언가가 또 있어... 제 2의 사사오입 사태다...
결국 질문을 올리고 잠시 중단하기로 하였다.
?! 그래 질문 게시판에 누군가 올려둔 사람이 있지 않을까?
있다. 코드는 생략했으니 궁금하면 직접 들어가보자
└ https://www.acmicpc.net/board/view/103328
그러니까 정리하자면 sys.stdin.readline은 성능이 너무 좋아서 EOF가 아니라 빈 문자열을 반환한다.
(그래서 not tree, tree == ""는 동작했지만, tree == " "는 동작하지 않았다.)
따라서 try-except를 사용하기 위해서는 input() 표준 입력을 사용해야 하고,
sys.stdin.readline() 입력을 사용하기 위해서는 while false break로 코드를 작성해야 한다는 것이다.
아하...역시 내가 모르는 무언가가 있었던 것이 확실하다.
그래서 마지막으로 input()을 사용하여 제출해보기로 했다.
근데 이거... 처음 2번째로 시도했던 방법 아닌가?
(그보다 djm03178 저분은 예전에 내가 질문달았을 때도 답변주셨는데 참 변하지 않는 사람이다.)
cnt = 0
dict_ = {}
while True:
try:
tree = input()
dict_[tree] = dict_.get(tree, 0) + 1
cnt += 1
except EOFError:
for t, c in sorted(dict_.items()):
ratio = c / cnt * 100
print(f'{t} {ratio:.4f}')
break
아까는 round 문제도 있었으니까 이번에는 아예 새로 코드를 작성했다.
...
P.S.1
착한 일 하면, 누군가 금방 달아주지 않을까...해서 답변 하나 적고 마무리했다.
'PS > 문자열' 카테고리의 다른 글
[백준] No.2607 비슷한 단어 01 (0) | 2023.08.10 |
---|---|
[백준] No.5525 IOIOI 完 (0) | 2023.08.08 |
[백준] No.5525 IOIOI 01 (0) | 2023.08.07 |
[백준] No.4358 생태학 完 (0) | 2023.08.03 |
[백준] No.4358 생태학 02 (0) | 2023.08.03 |