이번에는 아이디어 적인 부분에서 나름 창의적으로 한 것 같아서 풀이를 공유하기 설렌다!

 

내가 생각한 아이디어는 다음과 같다.

 

1. 두 수를 분수꼴로 나타낸다. (이 때, 분수는 기약분수가 된다.)

 

2. 나눠진 수를 분자로 나누면 GCD 등장!

 

이 아이디어면 소수를 따로 판별할 필요 없이 가능하다.

 

예시를 두 개 들어서 설명해 보겠다.

 

숫자 30과 45가 있다고 가정해 보면, 1번에 의해 30/45인 2/3이 나타나게 된다.

 

이후 2번에 의해 30/2 = 15가 나타나게 되는데, 이는 GCD이다.

 

하나 더, 숫자 3과 7이 있다고 가정해 보면, 1번에 의해 3/7이 나타나게 된다.

 

이후 2번에 의해 3/3 = 1이 나타나게 되는데, 이 역시 GCD이다.

 

 

이 행위가 가능한 이유를 보면, "기약 분수"로 표현한다는 것 자체가 GCD로 나눠진 값이다.

 

즉, GCD로 나눠진 값을 원래 값으로 나누면 GCD가 나타나게 되는 것!

 

이 아이디어를 구현하기 위해서 combinations와 Fraction library를 import 하였다.

 

from itertools import combinations    # 수학에서의 조합을 사용할 수 있게 해주는 library
from fractions import Fraction    # 분수식으로 표현할 수 있게 해주는 library
import sys
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

n = int(input())    # 테스트 케이스의 개수
for _ in range(n):
    answer = 0    # 정답을 저장하는 변수
    num, *numbers = map(int, input().strip().split(' '))    # 개수와 숫자들을 모두 입력받는 방법

    for (num1, num2) in list(combinations(numbers, 2)):    # numbers에서 2개씩 뽑는 조합을 진행
        f = Fraction(num1, num2)    # num1과 num2를 분수식으로 표현 (이 때, 기약분수로 표현됨)
        answer += num1 // f.numerator    # num1을 분자로 나눈다면 GCD (num2을 분모로 나눠도 동일)

    print(answer)    # 정답 출력

 

숫자를 입력받는 방법을 모른다면 Asterisk를 검색해보길 권장한다.

다음으로 푼 문제가 쇠막대기 문제이다.

 

레이저만 개수를 세어 주는 것이 아니라 막대기가 레이저에 의해서 나누어지는 것을 주의깊게 보아야 한다.

 

특히, 이번 문제는 조금 관점을 다르게 보면 쉽게 풀 수 있던 문제였다.

 

반복문을 돌면서 레이저를 만나는 경우에는 현재 레이저가 있는 층의 number만큼 막대가 분할되고, 층이 끝난다면 1만 추가해주면 되는 아이디어였다!

 

import sys
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

sticks = input().strip()    # 문장을 입력받는다.
laser_stack = []    # 층또는 레이저가 쌓일 list
answer = 0    # 정답은 0부터 시작

for idx in range(len(sticks)):
    if sticks[idx] == '(':    # 층 또는 레이저가 시작될 경우
        laser_stack.append('(')    # stack에 추가
    else:    # 층이 끝나거나 레이저가 끝난 경우
        laser_stack.pop()    # 층 끝남 및 레이저만 존재한 경우 레이저 제거
        if sticks[idx-1] == '(':    # 즉, 레이저인 경우
            answer += len(laser_stack)    # 레이저가 한 번 발사 될 때 마다 해당 층부터 아래 층까지 조각남
        else:    # 레이저가 아닌 층이 끝난 경우
            answer += 1    # 레이저 두 번이면 쇠 막대기 하나는 세 개로 쪼개지기 때문

print(answer)    # 정답 출력

 

사실 아이디어가 생각나지 않아서 다른 블로그에 올라온 내용을 참고했다.

참고한 블로그 : https://inuplace.tistory.com/844

 

else문에서 laser_stack.pop()을 먼저 실행한 이유는 레이저든 아니든 pop을 실행해주어야 했기 때문이다.

import sys
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

sentence = input().strip()    # 문장을 입력받는다.
answer = []    # 정답을 입력할 리스트 생성
temp = ''    # <>안에 값이 들어가 있지 않다면 저장할 빈문자열 생성
flag = False    # 해당 문자가 >와 < 사이에 존재한다면 False, <와 >사이에 존재한다면 True

for idx, ch in enumerate(sentence):    # 문장 안에 있는 각각의 문자마다 반복
    if ch == '<':    #  문자가 <인 경우
        if not flag:    # 만약 이전까지 >와 <사이에 존재했었다면
            answer.append(temp[::-1])    # 해당 단어를 거꾸로 반환하여 정답에 저장
            temp = ''    # 빈문자열로 재할당
        answer.append(ch)    # 정답에 < 문자 추가
        flag = True    # <가 시작하므로 flag를 True로 바꿔주어야 함
    elif ch == '>':    # 문자가 > 인 경우
        answer.append(ch)    # 정답에 > 문자 추가
        flag = False    # 다음 문자부터 > 뒤에 해당하므로 False
    elif ch == ' ':    # 문자가 빈칸이라면
        answer.append(temp[::-1])    # temp 속 문자들을 거꾸로 반환하여 정답에 저장
        answer.append(ch)    # 공백 문자도 정답에 저장
        temp = ''    # temp는 다시 빈 단어로 만들어준다.
    else:    # 문자가 일반 문자라면
        if flag:    # flag가 True인 경우. 즉, <와 > 사이에 존재하는 문자일 경우
            answer.append(ch)    # 정답에 문자 바로 추가
        else:    # flag가 False인 경우. 즉, >뒤에 존재하는 문자일 경우
            temp += ch    # temp에 문자 저장
    if idx == len(sentence) - 1:    # 마지막 문자일 경우
        answer.append(temp[::-1])    # temp에 있는 문자 거꾸로 반환하여 정답에 저장

print(''.join(answer))    # 정답을 문자열 형태로 출력

지난 번 단어 뒤집기 문제보다 조금 더 복잡한 문제이다.

 

split으로 가능했던 반면, 이번에는 문자 하나하나에 대한 탐색과 조건 설정을 까다롭게 해주어야 했었다.

 

 

역시나 지난번에 푼 문제에 이어서 collections에 있는 deque를 이용하는 문제이다.

 

이번에는 appendleft()가 추가되었다!

 

문제의 큰 흐름은 앞서 스택에서 했던 부분과 유사하여 크게 설명하지 않고 늘 그렇듯이 주석으로 남겼다.

 

import sys
from collections import deque
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

deque_list = deque()    # collections library 중 deque를 사용하면 queue를 시간 안에 구현 가능
N = int(input())    # command 개수 입력

for _ in range(N):    # command 개수만큼 반복
    command = input().split()    # 줄바꿈 문자를 제외하고 공백을 기준으로 분리
    if command[0] == 'push_front':    # 명령어가 push_front일 경우 deque_list의 가장 처음에 값을 추가
        deque_list.appendleft(int(command[1]))
    elif command[0] == 'push_back':    # 명령어가 push_back인 경우 deque_list의 가장 마지막에 값을 추가
        deque_list.append(int(command[1]))
    elif 'pop' in command[0]:    # 명령어가 pop_front 또는 pop_back인 경우
        if len(deque_list) == 0:    # deque_list가 비어있다면 -1을 출력
            print(-1)
        elif 'front' in command[0]:    # 명령어가 pop_front인 경우 가장 처음 값을 pop
            print(deque_list.popleft())
        else:    # 명령어가 pop_back인 경우 가장 마지막 값을 pop
            print(deque_list.pop())
    elif command[0] == 'size':    # 명령어가 size인 경우 deque_list의 size를 출력
        print(len(deque_list))
    elif command[0] == 'empty':    # 명령어가 empty인 경우
        print(1 if len(deque_list) == 0 else 0)    # deque_list가 빈 값이면 1을 출력하고 아니면 0을 출력
    else:    # 명령어가 front 또는 back일 경우
        if len(deque_list) == 0:    # deque_list가 비어있다면 -1을 출력
            print(-1)
        elif command[0] == 'front':    # 명령어가 front일 경우 가장 처음 값을 출력
            print(deque_list[0])
        else:    # 명령어가 back일 경우 가장 마지막 값을 출력
            print(deque_list[-1])

 

PyTorch에서 NNI를 이용해 auto-ml을 이용해보고 싶었는데, 블로그 글이나 유튜브에 생각보다 마음에 든 내용들이 없었다.

 

부스트캠프 AI Tech 3기에서도 6주차 오피스아워 때 NNI auto-ml 사용이 소개되었는데, 그대로 따라 해 보았지만,,, 에러가 너무 많이 났다.

 

에러를 모두 해결한 지금 auto-ml 사용 방법을 자세히 적어보려 한다.

 

참고로 관련한 내용은 NNI 개발 github에 적혀있는 방법 및 코드 + 오피스아워에서 다뤄진 방법 + 내가 해결한 에러 목록들까지 합쳐서 작성하였다.

 

NNI 개발 github인 https://github.com/microsoft/nni 창을 켜놓고 보면 더 편리할 것 같다.

 

1. NNI 설치

NNI 개발 github에서 권고한 방법대로 NNI를 설치한다.

 

2. 개발 github의 nni/examples/trials/mnist-pytorch/config.yml에 들어가 코드를 전체 복사한다.

# This is the minimal config file for an NNI experiment.
# Use "nnictl create --config config.yml" to launch this experiment.
# Afterwards, you can check "config_detailed.yml" for more explanation.

searchSpaceFile: search_space.json
trialCommand: python3 mnist.py  # NOTE: change "python3" to "python" if you are using Windows
trialGpuNumber: 0
trialConcurrency: 1
tuner:
  name: TPE
  classArgs:
    optimize_mode: maximize
trainingService:
  platform: local

이 코드도 수정을 하긴 할 것이다.

 

3. NNI를 적용시킬 코드가 있는 폴더로 와서 nni 폴더를 하나 생성한 후, config.yml 파일을 똑같이 생성하여 위에서 복사한 코드를 붙여넣는다. (지금 와서 생각해보면, nni폴더는 굳이 필요 없을 것 같긴 하다.)

나의 경우는 이랬다.

 

4. config.yml의 코드를 보면, trialCommand: python3 mnist.py라고 적혀있는데, mnist.py를 NNI를 적용시킬 파일 명으로 바꾸고, nni 폴더를 생성하여 config.yml을 만들었다면 trialCodeDirectory: ../도 추가해 준다. (그렇지 않으면 나중에 NNI를 실행 했을 때 trial이 failed 뜬다..) 혹여나 gpu를 사용하고 싶다면 trialGpuNumber를 수정하고, trainingService:에 useActiveGpu: True를 추가해 주면 된다. 더 추가하고 싶은 것이 있다면 config_detail.yml을 보면 된다.)

searchSpaceFile: search_space.json
trialCommand: python3 train.py  # NOTE: change "python3" to "python" if you are using Windows
trialGpuNumber: 0
trialConcurrency: 1
trialCodeDirectory: ../
tuner:
  name: TPE
  classArgs:
    optimize_mode: maximize
trainingService:
  platform: local

 

5. config.yml을 설치한 것처럼 search_space.json 파일도 하나 생성한다. (nni 폴더에 config.yml을 만들었다면 똑같이 search_space.json도 해당 폴더에 만들라는 의미)

나의 경우에는 이렇게 했다.

 

6. 개발 github의 nni/examples/trials/mnist-pytorch/search_space.json에 들어가 코드를 전체 복사하고, 본인이 실험하고 싶은 hyper-parameter에 맞게 수정한다.

 

7. 개발 github의 nni/examples/trials/mnist-pytorch/mnist.py에 들어가서 nni.report_intermediate_result(test_acc)와 nni.report_final_result(test_acc) 코드 두 개를 복사해서 자신의 코드 중 test_acc를 출력하는 코드에 각각 삽입한다.

해당 이미지 기준 118번 째 줄과 123번 째 줄 코드만 복사하면 된다.

 

8. 다시, mnist.py의 제일 하단에 보면 nni를 이용하는 코드 두 개가 있는데, 복사하여 자신의 parser 적용이 끝난 이후의 코드에 붙여 넣는다. (단, 자신의 코드 중에 params를 인자로 받아서 vars(params)를 적용하는 코드가 존재한다면, 161번 코드를 그대로 복사 붙여넣기 하지 말고, vars를 제외하여 붙여 넣는다.)

해당 이미지 기준 159번, 161번 코드이다.

9. NNI 개발 github에서 권고한 방법으로 실행 시키면 된다. (여기서, --port 8080 처럼 port를 지정할 수 있다. 또한, 서버를 사용하는 사람은 NNI를 실행했을 때 뜨는 ip가 아니라 개인이 쓰고 있는 서버 ip를 써야 한다.)

당연하게도 경로는 각자에 맞게 설정해준다.

 

끝!

Black : English  /  Blue : Korean

 

 

When you use NNI auto-ml in your code which assigned the hyperparameter through a 'parser' in train.py, AttributeError is occured.

train.py에서 parser를 통한 hyperparameter를 지정한 경우에 NNI auto-ml을 사용하는 경우 아래와 같은 에러가 발생하였다.

 

In my case, data_dir was declared like data_dir = args.data_dir, so I modified my code data_dir = args['data_dir']

data_dir 코드가 data_dir = args.data_dir로 작성되어 있었는데 이를 data_dir = args['data_dir']로 고쳐주면 error 자체는 사라진다.

 

Of course, you should not only modify that but also others (like shuffle=args.dataset -> shuffle=args['dataset'])

data_dir만 수정해주는 것이 아니라 shuffle=args.data 처럼 args.xx로 선언되어 있는 모든 것들을 args['']로 수정해주어야 한다.

지난 문제에 이어서 collections에 존재하는 deque를 적극 이용하는 문제였다.

 

처음에는 deque를 쓰지 않고 해결해볼까 생각했지만, 시간 복잡도나 여러 면에서 deque가 효율적일 것 같아서 처음부터 설계를 deque를 고려하여 진행하였다.

 

deque에는 rotate라는 기능이 존재했고, 지난 번에도 다뤘던 popleft라는 기능도 존재하여 이를 적절하게 사용하면 될 것이라고 생각했다.

 

rotate는 deque를 회전시키는 기능으로, 음수를 입력하면 왼쪽으로 회전한다고 한다.

 

즉, deque([1, 2, 3, 4, 5])가 존재하면 rotate(-1)을 할 경우 deque([2, 3, 4, 5, 1])가 되고, rotate(1)을 할 경우 deque([5, 1, 2, 3, 4])가 된다.

 

이를 적용하여 코드를 완성하였다. (list comprehension도 적극 이용해보았다!)

import sys
from collections import deque
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

N, K = map(int, input().split())    # 입력을 받을 때 공백을 기준으로 분리한 후 각각을 int 타입으로 변환
num_list = deque(x for x in range(1, N+1))    # number를 1부터 N까지 담고있는 list 생성
result = []    # 결과를 저장하는 list

for _ in range(N):    # N개의 원소를 모두 꺼내므로 N만큼 반복
    num_list.rotate(-K+1)    # deque에 존재하는 rotate 함수를 이용하여 list 회전 (K번째 index를 꺼내므로 회전은 K-1번만 진행)
    result.append(num_list.popleft())    # 가장 좌측에 있는 값을 pop

print('<' + str(result).strip('[]') + '>')    # 예제 출력에 맞게 결과 출력

'Algorithm Test > BaekJoon' 카테고리의 다른 글

BaekJoon 17413 - 단어 뒤집기 2 (Python)  (0) 2022.03.03
BaekJoon 10866 - 덱 (Python)  (0) 2022.03.02
BaekJoon 10845 - 큐 (Python)  (0) 2022.02.28
BaekJoon 1406 - 에디터 (Python)  (0) 2022.02.25
BaekJoon 1847 - 스택 수열  (0) 2022.02.24

앞서 다룬 스택과는 다르게 큐는 LILO ( Last In Last Out )이라는 특징을 가지고 있다.

 

Last In Last Out은 글자 그대로 나중에 들어온 값이 나중에 나간다는 것이다.

 

이번에 코드를 짜면서 역시 시간 초과 문제가 발생할 것을 염두하여 collections module에 존재하는 deque를 사용하였다.

 

처음에 한 번 deque를 써보지 않고 해보려 했지만, 먼저 들어온 값을 내보내는 코드에서 reversed(queue_list).pop()도 불가능했고, queue_list[::-1].pop()을 해도 기존의 list가 변하지 않기에 del이나 remove를 사용해야 해서 결국 시간 복잡도가 증가하기에 ( O(N)으로 증가한다. ) deque를 사용할 수 밖에 없었다.

 

import sys
from collections import deque
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

queue_list = deque()    # collections library 중 deque를 사용하면 queue를 시간 안에 구현 가능
M = int(input())    # command 개수 입력

for _ in range(M):    # command 개수만큼 반복
    command = input().split()    # 줄바꿈 문자를 제외하고 공백을 기준으로 분리
    
    if command[0] == 'push':    # 명령어가 push인 경우
        queue_list.append(command[1])    # push 다음에 존재하는 숫자를 queue에 추가
    elif command[0] == 'pop':    # 명령어가 pop인 경우
        if len(queue_list) == 0:    # 빈 queue라면 -1을 출력
            print(-1)
        else:    # 빈 queue가 아니라면 deque에 존재하는 popleft()를 이용하여 가장 처음 들어온 값을 pop
            print(queue_list.popleft())
    elif command[0] == 'size':    # 명령어가 size인 경우 queue의 길이 출력
        print(len(queue_list))
    elif command[0] == 'empty':    # 명령어가 empty인 경우
        if len(queue_list) == 0:    # 빈 queue라면 1 출력
            print(1)
        else:    # 빈 queue가 아니라면 0 출력
            print(0)
    elif command[0] == 'front':    # 명령어가 front인 경우
        if len(queue_list) == 0:    # 빈 queue라면 -1을 출력
            print(-1)
        else:    # 빈 queue가 아니라면 가장 앞에 있는 값 출력
            print(queue_list[0])
    else:    # 명령어가 back인 경우
        if len(queue_list) == 0:    # 빈 queue라면 -1을 출력
            print(-1)
        else:    # 빈 queue가 아니라면 가장 마지막에 있는 값 출력
            print(queue_list[-1])

'Algorithm Test > BaekJoon' 카테고리의 다른 글

BaekJoon 10866 - 덱 (Python)  (0) 2022.03.02
BaekJoon 1158 - 요세푸스 문제 (Python)  (0) 2022.03.01
BaekJoon 1406 - 에디터 (Python)  (0) 2022.02.25
BaekJoon 1847 - 스택 수열  (0) 2022.02.24
BaekJoon 9012 - 괄호  (0) 2022.02.23

+ Recent posts