본문 바로가기
Always Awake/피로그래밍 12기(19.12.31~20.02.22)

피로그래밍 12기 2주차 활동정리(20.01.07~20.01.13)

by 욕심많은알파카 2020. 1. 13.

화요일(01.07)

bootstrap의 grid system을 이용하여 반응형 웹만들기 by 정성모 선배님

 

container로 전체에 마진을 주고, 그 내부에 row div(한 행의 전체양 ex-12), row div를 다시 column div(행 안에 나눌 양-ex 4,4,4)로 나누어 격자형을 만듬.

 

col-md-n : 노트북 디바이스에 적합하도록 만들어놓은 클래스명으로, 화면을 줄이면 div의 위치가 알아서 바뀜.(반응형 구현)

col-sm-n : 태블릿 디바이스에 적합하도록.

col-lg-n : 노트북/데스크톱에 적합하도록.

col-xl-n : 데스크톱 이상 크기의 화면에 적합하도록.

따로 지정하지 않으면 xs크기, 즉 스마트폰 크기에 맞추어 지정.

즉 default 지정은 xs에대하여.

 

각 클래스 명에 따라 정해진 너비가 있으므로 부트스트랩 사이트 가서 확인할것.

(https://www.it-swarm.net/ko/css/%EB%B6%80%ED%8A%B8-%EC%8A%A4%ED%8A%B8%EB%9E%A9%EC%97%90%EC%84%9C-collg-colmd-%EB%B0%8F-colsm-%EC%82%AC%EC%9D%B4%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9E%85%EB%8B%88%EA%B9%8C/1042992230/ )참조.

 

row 클래스명에 col을 어떻게 넣을건지 다 적어놔도 동작함. ex-row row-cols-1 row-cols-sm-2 row-cols-md-6 (이렇게 할 경우 아래에는 그냥 col이라고만 클래스명을 정하면 됨)

근데 row에 적는것보다 col에 각각 넣어주는게 더 사용하기 쉬움. row는 일괄적으로 아래 col에 적용되며 각 col에 세부적인 조정이 불가능하므로 col 마다 옵션을 넣어주는게 좋음.

 

 

python강의 by 정치영선배님

 

-insert의 인덱스는 ‘들어갈 수 있는 공간’을 기준으로 세기

예를들어 [1,2,3,4] 사이에 insert(3)을 한다면 

[v,1,v,2,v,3,v,4,v]

비어있는 공간 v중 인덱스가 3인 공간, 즉 3과 4사이의 v에 요소를 삽입하게 됨.

 

 

- list의 sort함수 응용

# list methods (3)

P = [(1,2,1), (3,1,5), (4,3,2), (2,-5,1), (7,0,4)]



def Picker(tuple1):   #정렬기준을 반환하는 함수

    return tuple1[1]



P.sort(key=Picker, reverse=True)

print(P) # (4,3,2), (1,2,1), (3,1,5), (7,0,4), (2,-5,1)

 

 위와 같은 코드를 람다식으로도 만들 수 있음.

 

Q = [(1,2,1), (3,1,5), (4,3,2), (2,-5,1), (7,0,4)]

Q.sort(key=lambda elem: elem[1], reverse = True) #튜플의 인덱스1을 기준으로 sort한다

print(Q) # 결과는 위의 코드와 같음

 

- ^(exclusive or)

A^B시 A와 B가 다를경우에만 True, 같은 경우에는 False리턴

 

 

- 딕셔너리의 get(a,b)함수

a는 키값, b는 만약 키값으로 불러온 value값이 없을경우 정하는 디폴트값.

 

 

- 딕셔너리의 items함수

키값과 value값을 둘 다 가져옴. 따라서 할당하려면 두개의 변수 필요

 

 

- 딕셔너리에서 key-value 서로바꾸기

G={} #key-value를 거꾸로 뒤집은, value-key관계로 다시 재설정하는 딕셔너리.

for i, j in D.items():

    G[j] = i

 

근데 문제가 있는게 key는 유니크하지만 value는 unique하지 않기때문에 이걸 사용하면 key값쌍이 몇개 사라질 위험이 있음.

만약 같은 value값들이 몇개 있다면 키로 바뀌는 순간 서로 충돌되어 한쪽이 없어지기 때문.

 

 

- 딕셔너리에서 가장 큰 value값을 가져오는 인덱싱

sorted_items = sorted(D.items(), key=lambda x: x[1]) #람다식을 이용해 value값 기준으로 정렬한 items를 받아옴
max_val = sorted_items[-1][1] #items의 value값중 가장 큰값 가져오기

 

 

-itemgetter()함수 사용법

from operator import itemgetter

f = []
for i, j in sorted(D.items(), key=itemgetter(1,0)):
    f += [(i,j)]
print("all items sorted by value and then key\n", f, "\n")

operator 모듈의 itemgetter함수는 딕셔너리를 정렬해서 출력할 때 기준을 정하게 해주는 함수이다.

sorted(딕셔너리, key=itemgetter(인자)) 형식으로 사용한다.

 

이때, 인자는 딕셔너리 items의 인덱싱이므로 0은 키, 1은 값을 의미한다.

즉, 0을 넣는다면 키를 기준으로 오름차순으로 정렬, 1을 넣는다면 값을 기준으로 오름차순 정렬이다.

 

위 코드에서는 key=itemgetter(1,0)형식으로 사용하였는데, 이 경우에는

1.일단 값 기준으로 오름차순 정렬을 하되

2.값이 같은것들에 대해서는 키를 기준으로 오름차순 정렬하겠다

라는 의미이다.

 

오름차순이 아니라 내림차순으로 정렬하고 싶다면 sorted()함수의 마지막 인자에 reverse=True를 추가하면 된다.

 

 

-*args, **kwargs

인자를 여러개 넣고 싶을때, 함수 식에 인자 개수를 명시할 필요 없이 (*인자)만 적어두면 된다.

**kwargs는 값을 가진 키 인자를 여러개 줄 수 있다.

자세한 내용은 파이썬 정리를 참고한다.

 

- 함수 인자의 순서

def param_arg_kwarg(a, b=None,*args, **kwargs):
    print(a)
    print(b)
    print(args)
    print(kwargs)

여기서 b=none은 만약 안넣으면 none을 디폴트로 넣어주겠다는 말이다. 이런식으로 디폴트를 정해주는 값은 디폴트가 없는 인자보다 뒤쪽에 있어야 한다. 그렇지 않으면 오류 메시지를 출력한다.

*args나 **kwargs같은 인자를 만약 a나 b같은 위치인자 앞에 쓰면, 아무리 많은 인자를 써도 a,b에 할당되는게 아니라 전부 *args나 **kwargs에 할당된걸로 인식한다. 따라서 인자의 성질에 따라 순서를 명확히 해야한다.

 

디폴트가 없는 위치인자 -> 디폴트가 있는 위치인자 -> 키워드인자 -> 가변위치인자 -> 가변키워드인자

 

 

- 데코레이터(decorator)

모든 method마다 제대로 사용되었는지, 어디서 어떻게 호출되는지 알아보기 위해 print를 다 집어넣는것은 귀찮고 힘들다.

그래서 refer함수를 정해놓고 함수마다 refer함수를 합성함수처럼 사용하여 비슷한 작업들을 수행할 수 있게 만들어놓는다.

정확한 설명은 파이썬 정리를 참조한다.

 

- Classmethod,Staticmethod의 차이

 

class에는 field(속성이 들어가는곳)와 method가 있다.

method는 인스턴스가 존재하고 해당 인스턴스에 대해서만 사용할 수 있음. ex-철수.eat()

그러나 Staticmethod와 Classmethod는 인스턴스가 없더라도 클래스 자체에 대해서 사용할 수 있음.

 

static method과 class method의 차이

static method: 클래스 내에 존재하는 스테틱 변수, 즉 class자체에 존재하는 속성에까지 접근할 수 있는 함수. 추가로 인스턴스의 속성까지도 건드릴수있음. 

class method: 해당클래스를 상속한 자식클래스에 대해서도 해당 메소드를 다 사용할수있다. 근데 자손까지 접근하므로 접근하는 범위가 방대하여 잘못 건드릴 수 있어 위험함.

 

자세한 내용은 파이썬 정리와 특성을 참조한다.

 

 

목요일(01.09)

코딩도장 강의 수강 (33강 이후)

 

 

토요일(01.11)

웹프로그래밍의 기본 개념 by 김상엽 선배님

 

IDE(Integrated Development Environment) : 통합개발환경. 코딩, 디버그, 컴파일 ,배포등의 여러 작업을 하나의 프로그램안에서 처리하는 환경을 제공하는 소프트웨어. ex- 이클립스, 비주얼 스튜디오 ...

 

웹개발은 통합개발환경이 아니다.

 

웹사이트 : 웹 문서 형태의 리치 / 미디어로 정보를 제공하는 문서 - 정적인 형태

웹서비스 : 웹 문서를 토해내며 특정 기능을 인터넷 상으로 제공하는 서비스. 어떤 유저가 어떤 시간에 어떤 기기로 접속하느냐에 따라 다른화면을 보여줌 - 동적인 서비스

 

웹서비스 = 서버 사이드 + 클라이언트 사이드(사용자 기기,pc같은 것들)

 

웹서비스는 블랙박스적인 속성을 가지고 있다. —> 내부 동작이 어떤 원리인지 몰라도 유저가 사용할 수 있다.

 

웹 서비스 코딩 = 이를테면 식당을 짓는것 (수업자료 참조)

 

-개발 관련 이야기

 

개발공부방식

익숙한 것(comfort zone)을 벗어나서 남의 일을 해보자 —> 남의 일을 해보자. 잘 모르는 사람의 무리한 요구를 해보려고 노력할 때 실력이 늘어남.

사이드 프로젝트 계속 할것 (ds/challenge) —> 나만의 일도 해보자. 터져도 상관없는… 아이디어를 실현시키려고 노력

기획 개발 디자이너가 다 모이는 곳으로 가라 —> 프로그라피, sopt, yepp, nexters..

교과서식 공부 < 전화번호식 공부 —> 천천히 암기하는 식으로 하지마라. 필요한 기술을 그때 그때 찾아배우는 탐독, 전화번호식 공부.

꼭 개발자가 되어야만 피로그래머는 아니다.

 

요새 트렌드

SPA-Single Page Application (React, Vue 등) —> 요새 프론트엔드의 필수

 

Micro Framework (Node JS, Flask) —> 더 자유롭고 덜해주는, 경량화된 프레임워크

Django는 Full-Stack Framework, 다 해줌(그런 것처럼 보임) —> 굉장히 무거운 프레임워크

근데 마이크로랑 풀스택 프레임워크 각각 하나하나씩 배워두는게 좋다.

 

 

인프라

서버가 터지면 인프라에 대한 고민 시작.

dev ops가 필요한 조직은 이미 성공한 거긴 함.

트래픽이 있는 경험을 해보고싶다고 말하는 선배들이 왜 그런말을 하는지 알것같기도.

시스템, 서버 증설, 개발환경을 조성하고 싶다는 조직은 이미 큰 상태겠지.

 

 

 

코딩문제 풀이 by최범수 선배님

 

1. 분해합(https://www.acmicpc.net/problem/2231)

 

자릿수이므로 str으로 받아주는게 더편함

#1.각 자리수를 더한 값을 구하는 함수

#2. 1을 이용해 분해합을 구하는 함수

#3. 역방향으로 for문 쓰기 range(int_N-1 , 0 -1, -1)

범위안의 num에 대해 각각 분해합을 구하기.

#4. if divide_sum이 주어진 숫자와 같다면 해당 숫자들을 뽑아내고, 최솟값을 리턴하기

(#5. 범위가 너무 크므로 범위 정해주기 ( 자릿수*9)) --> 이게 없으면 시간복잡도에서 탈락.

 

# 내 코드

#분해합이 있는 튜플을 반환하는 함수
def decomposition(M):
    numM = int(M)
    digitM = [int(i) for i in M]
    digitLenM = len(digitM)
    tupleM = (numM, digitM, digitLenM, int(sum(digitM)+numM))
    return tupleM

#생성자 구하기(main)
M = input()
infoM = decomposition(M)
minRange = infoM[0]-(9*infoM[2])    # 검사범위는 최대 (9*자릿수개수)를 뺀 minRange부터.
maxRange = infoM[0]
notExist= False	
if minRange<maxRange and minRange>=0: #주어진 수가 충분히 큰 수인경우 (minRange가 일반적으로 설정 되었을 때)
    for i in range(infoM[0]-(9*infoM[2]),infoM[0]):
        if decomposition(str(i))[3] == infoM[0]: #분해합이 주어진 수와 같으면 프린트하고 반복문 탈출
            print(i)
            notExist=False
            break
        else:
            notExist=True
    if notExist == True:   #분해합이 같은 수가 없으면 0 출력
        print(0)
elif infoM[0] == 1:  #예외:1은 그보다 작은 수가 없으므로 0 출력
    print(0)
else:			#주어진 수가 충분히 크지 않아 minRange가 0보다 작아졌을경우
    notExist=False
    for i in range(0,infoM[0]):
        if decomposition(str(i))[3] == infoM[0]:
            if decomposition(str(i))[3] == 0:
                continue
            else:
                print(i)
                notExist=False
                break
        else:
            notExist=True
    if notExist == True:
        print(0)

내 방식은 좀 더러운듯.

54개의 범위지정은 좋았는데 메인루틴이 너무 복잡해서 가독성이 떨어짐.

역방향 range는 굳이 쓸필요 없는듯 하다.

 

 

2. 1로 만들기(https://www.acmicpc.net/problem/1463)

 

#1.규칙 찾기. 작은 문제로부터 규칙을 찾자

#2.찾아보면.. 이전에 썼던 숫자의 최소숫자에서 한칸만 더 가보면 되네.

#3. result_list = [0,1,1] —>최소 3개는 있어야 규칙이 성립됨. result_list에 지금까지 숫자들의 최소연산횟수를 기록해놓는다.

#4. 우리가 구할 숫자를 세가지(그래서 아까 최소 3개는 있어야 된다고 한거) 경우로 나눈다. (/3), (/2), (-1) 그리고 세가지 경우의 숫자들은 최소연산횟수가 이미 기록되어있으므로 그대로 가져와서 비교한다.

#5. 3개를 min함수로 비교해서 가장 작은거 가져오고, 그 값을 다시 result_list에 append

#6. if문으로 3개의 경우의 수 거치면서 최소 값 가져오기. 그리고 그 최소값에서 1을 더해서 다시 result_list에 append.(3개의 경우의 수들 중 가장 작은 값에서 한칸 더 가면 최소값이다)

 

x = int(input())

min_cal_list = [0,1,1] #최초의 세가지 케이스는 제공

for i in range(3,x):  #4부터 x(인덱스 3부터 x-1)까지의 최소연산횟수를 min_cal_list에 추가시킬 반복문
    current_min = 1000000 #최소연산횟수 최초설정
    num = i+1 #최소연산횟수를 얻으려는 수
    if num%3 == 0:
        current_min = min(current_min,min_cal_list[int(num/3)-1]+1) # 최초설정과 num/3의 최소연산횟수+1중 어느게 더 적은지 비교하여 할당
    if num%2 == 0:
        current_min = min(current_min,min_cal_list[int(num/2)-1]+1) # 현재까지 최초연산횟수와 num/2의 최소연산횟수+1 중 어느게 적은지 비교하여 할당
    current_min = min(current_min,min_cal_list[(num-1)-1]+1)
    min_cal_list.append(current_min) #num의 최소연산횟수를 리스트에 추가


print(min_cal_list[x-1]) #x의 최소연산횟수

 

처음에는 문제를 풀 알고리즘이 생각조차 나지 않아서 한시간이 넘도록 끙끙거리다 넘겼다.

결국 못풀고 넘겼는데 이후에 들어보니 DP(Dynamic Programming) 유형의 문제라고 한다.

주어진 숫자로부터 세가지 케이스를 나누어 내려가면 최대 삼진트리가 되는데,  매번 트리를 따라 내려갈 때 이전에 이미 찾아놓은 최소연산횟수를 다시 찾아야한다. 그 방식으로 모든 경우의 수를 탐색하는데 굉장히 많은 시간이 걸리므로 입력값이 커진다면 사실상 불가능하다.

따라서 이전에 미리 찾아놓았던 특정 숫자의 최소연산횟수를 이용해야한다. n의 숫자가 주어진다면 n 아래의 숫자들의 최소연산횟수를 미리 다 구해놓고, 그 아래 숫자들에서 연산을 한번 더하여 n을 만드는 식으로 (즉, n의 최소연산횟수는 이전 숫자의 최소연산횟수 +1) 사용한다.

처음에는 n의 범위가 굉장히 커서 그 숫자들의 정보들을 다 저장해두는 방법은 메모리 낭비라고 생각해 당연히 배제했는데, DP형식의 문제에서는 시간복잡도를 위해 공간복잡도를 포기한다고.... 그리고 리스트를 만들어도 생각보다 메모리를 많이 차지하지 않는다.

유연하게 생각하자. 시간복잡도를 아예 해결 못할만큼 연산이 어마어마하다면 공간복잡도를 늘려 연산을 최대한 줄여야한다.

(실제로 위처럼 리스트를 만들어두고 연산하면 코드도 굉장히 짧고, 연산속도도 빠르다) 

 

근데 백준에 제출하고 나니 생각보다 시간도 오래 나왔다. 비슷한 다른 사람들의 코드는 나보다 반절이나 시간이 줄던데.. 어떤 이유일까?

 

 

3. 괄호(https://www.acmicpc.net/problem/9012)

 

#1.stack이용을 알아야한다. stack 구현

#1.5 stack클래스 내에 private속성을 줘본다. 인스턴스에서는 직접 접근이 불가능하지만, get메서드를 클래스 내에 만들면 리턴할 수 있다.

#2. 스택클래스를 구현했다면 괄호를 입력받아 검사하자.

#3. '('이면 push, ')'이면 pop.

#4. stack이 empty인지 확인하기.

#5. 몇개나 input받을건지 확인해서 그숫자만큼 for문 돌리기.

 

SLlength = int(input())  #괄호문자열을 몇개나 받을것인가?

SList=[]		#괄호문자열들을 저장해두는 리스트
for i in range(SLlength):
    SList.append(input())

VPSList = []	#각 괄호문자열들의 VPS여부를 기록해놓는 리스트

for i in range(len(SList)):
    stack=[]
    VPS=None
    for j in range(len(SList[i])):
        if SList[i][j] == '(':			#열린 괄호이면 o(open)을 stack에 넣기
            stack.append('o')
        else:  #SList[i][j] == ')'		#닫힌 괄호이면
            if stack != []:				#그리고 스택에 o가 들어있으면 스택 안의 o를 빼내기
                stack.pop()
            else:						#스택에 아무것도 없는데 닫힌괄호가 있다면 괄호의 짝이 안맞는다는 소리이다
                VPS = False				#따라서 VPS가 아니다
                break
    if stack == [] and VPS != False:
        VPS = True
    else:
        VPS = False
    VPSList.append(VPS)

for i in VPSList:
    if i == True:
        print('YES')
    elif i == False:
        print("NO")

stack을 모른다면 바로 풀 생각이 나지 않았을테지만, 다행히도 1학년 때 들어둔 데이터구조에서 stack의 대표적인 사례로 괄호판단을 배웠어서 문제를 보고 stack을 떠올릴 수 있었다.

최범수 선배님은 c에서처럼 stack 클래스와 pop,push 메서드를 다 구현했지만, 이전에 파이썬은 리스트의 append와 pop함수로 이미 스택구조가 구현되어있다는 언급을 들은 적 있기 때문에 간단히 stack구조를 구현했다.

 

 

 

4. 문자열 압축 (2020 카카오 공채 1번, https://programmers.co.kr/learn/courses/30/lessons/60057)

 

#1.cut_size의 최대 범위는 int(len(인풋)/2) 까지.

#2.compress_word함수에  for문을 넣고 range(0, len(word), cut_size) 하면 0부터 끝까지 cutsize크기만큼 잘라서 확인(cut_size크기만큼 건너뛰는거임)

#3.컷된 인덱스만큼 슬라이싱한 문자열을 cutted_words에 반환

#4.while문 돌려서 지금 원소와 다음원소가 같은지 확인하고 같으면 앞에걸 숫자 2로 바꿔줌.

근데 다음원소가 또 같으면 2를 3으로 올려야하므로..

#5. 다음원소가 또 같으면 숫자를 또 바꿔야하므로 while문 바깥에 last_matched라는 압축문자를 저장해두는 변수를 만들어둠.

#6. 다음것과 last_matched가 같으면 앞에있던 숫자를 1올려주고 다음 원소를 없앤다.

 

def check_length(s,cut_size):
    cutted_s = []
    for i in range(0,len(s),cut_size): #s를 cut_size길이로 슬라이싱하여 cutted_size에 추가하는 반복문
        if i+cut_size<len(s):
            cutted_s.append(s[i:i+cut_size])
        else:
            cutted_s.append(s[i:])

    i = 0
    comp_count_list=[]
    while i <len(cutted_s)-1:
        comp_count=1
        if cutted_s[i] == cutted_s[i+1]:  #다음 요소와 지금 요소가 같으면
            j = i
            #start = j
            while cutted_s[j] == cutted_s[j+1]: #어디까지 같은지 확인하여 압축개를 지정함
                comp_count +=1
                j+=1
                next_start=j          #next_start는 이전과 요소가 달라지는(압축범위 이후의 첫)
                if j+1 == len(cutted_s): #검사하는 범위가 리스트의 끝인덱스를 넘어서지 않도록 예외처리
                    next_start = len(cutted_s)-1
                    break
            if comp_count >1:
                comp_count_list.append(comp_count) #압축개수(최소2)를 리스트에 추가시킨다
            i = next_start
        else:   #다음 요소와 같지 않아서 next_start가 정해지지 않았으면 i를 한칸 이동
            i+=1
    comp_length=len(s)
    for i in comp_count_list:
        comp_length -= cut_size*(i-1)   #1개빼고 나머지는 다 압축시키니 길이에서 빠진다
        comp_length += len(str(i))      #압축개수숫자도 길이에 포함시킨다
    return comp_length


def solution(s):
    comp_length_list = []

    for i in range((len(s) // 2) + 1):  # 압축문자열 길이는 문자열 길이의 반까지(반을 넘으면 같은 길이의 문자열이 2개이상 존재하지 않음)
        comp_length_list.append(check_length(s, i + 1))  # s를 주고 i+1길이(1부터 시작하여 문자열길이의 반까지)로 문자열을 잘라 압축한 길이를 반환하는 함수

    return min(comp_length_list)

 

최범수 선배님이 풀어주신 풀이가 있는데, last_matched의 사용과 압축개수를 올리는 방법을 구현하기가 힘들었다.

정확히 압축해서 압축한 모양을 출력하라고 하면 선배님의 방식대로 해야겠지만, 가장 짧은 압축문자열의 길이를 반환하기만 하면 되었기 때문에, 문자열을 압축하지 않고 압축문자열의 길이만 반환하는 코드를 작성해서 풀었다.

 

카카오 1번 문제이고 정답률이 30퍼가 안되지만 가장 쉬운문제라고 한다. 5시간내에 7문제중 적어도 반절이상은 풀어야 붙을 기대를 할 수 있다고 하는데, 혼자 따로 풀었는데 이 문제만 3시간을 넘겨서 풀었다. 갈길이 멀다는 것도 느끼고, 리스트의 인덱싱에 내가 굉장히 약하다는 걸 느낀다. 문제를 보면 풀 방법은 길어봤자 1시간 이내로 떠올릴 수 있는데, 내가 원하는 요소를 정확히 뽑아오는 인덱싱에 약하다. 때문에 구현에 시간이 많이 걸리고, 그 과정에서 실수도 잦다. 문제를 풀기 전에 노트에 로드맵을 잡아놓고 시작하는데도 그렇다. 비슷한 코딩 문제를 많이 풀어봐야 할 것 같다.

 

 

5. 괄호 변환 (2020 카카오 공채 2번,https://programmers.co.kr/learn/courses/30/lessons/60058 ) (+20.01.21 추가)

def solution(p):
    answer = ''
    if p == '':
        return p

    len_p = len(p)

    while 1:
        stack = []
        stackFilled=None
        check=0
        for i in range(len(p)):
            if p[i] == '(':
                if stack:
                    if 2 in stack:
                        stack.remove(2)
                    else:stack.append(1)
                else:
                    stack.append(1)

            else:
                if stack: #stack에 이미 들어간 '('가 있으면
                    if 1 in stack:
                        stack.remove(1) #remove로 뺀다
                    else:stack.append(2)
                # stack이 비어있지도 않고 for문이 다 돌지도 않았는데 빠져나오는 조건
                else :
                    stack.append(2)
            stackFilled= True
            if stackFilled == True and not stack :
                check = i
                break
            check = i
        #print("check:",check)
        u = p[:check+1]
        v = p[check+1:]
        #print("u:",u,"\nv:",v)

        uBalance=False
        for i in range(len(u)):
            if u[i] == '(':
                stack.append(1)
            else:
                if stack:
                    stack.pop()
            if i == len(u)-1 and not stack:
                uBalance=True



        if uBalance == True :#u가 올바른 괄호 문자열이라면
            p = v# v에 대해서 수행하도록 돌아가자
            answer = ''.join([answer,u])
        else: #만약 올바르지 않은 문자열이라면
            #괄호 교정 시작
            s = ''.join(['(',solution(v),')'])
            u = u[1:-1]
            u = list(u)
            for i in range(len(u)):
                if u[i] == '(':
                    u[i] = ')'
                else:
                    u[i] = '('
            u= ''.join(u)
            answer = ''.join([answer,s,u])
        if len(answer) == len_p:
            break
    return answer

내 풀이의 로직은 이렇다.

solution 함수는

1. w를 균형잡힌 문자열 u,v로 분리한다.

2. 분리한 u에 대해서 올바른 괄호 문자열인지 검사한다.

3. u는 올바른 문자열인가?

그렇다->3-1. 리턴할 값(answer)에 u를 붙이고 다시 v에 대해 올바른 괄호문자열인지 검사하도록 while문으로 복귀한다.

아니다->3-2. v에 대한 solution 재귀 호출 후 u에 대해 괄호를 교정하여 합친다.

4. while문이 1번 돌때마다 리턴할 문자열의 길이와 주어졌던 문자열의 길이를 비교해 같으면 반복문을 탈출 후 답을 반환한다.

 

이에 대해 선배님 풀이의 로직은 이렇다. (내 풀이에 비교하여)

1,2,3번을 통째로 아우르는 recursive_converter 함수를 만든다.

하위 로직으로 1번을 split_to_uv함수로 떼어낸다.

하위 로직으로 2번을 수행하는 check-right함수를 만든다.

하위 로직으로 3번을 수행하는 convert-rest함수를 만든다.

(주어진 문자열에 대해 재귀를 통해 크게 한번의 함수를 돌기 때문에 4번의 문자열 길이 비교를 할 필요가 없다.)

 

결국 핵심은 문제 해결 로직을 크게 잡아놓은 후, 세부 로직을 하나하나씩 만들어가는 것이다. 그렇게 하면 pseudo코드를 짠 것처럼 큰 로직들이 한번에 술술 읽히고, 어떤 부분을 구현하고 고쳐야 할지 눈에 보인다.

그에 비해서 내 풀이는 커다란 while문을 돌림으로써 풀이가 처음부터 끝까지 죽 이어지고, 중간에 변수도 여러번 생성되고 재할당되기 때문에 눈으로 따라가기 어렵다(아마 유지보수도 어려울 것이다)

while문 끝에 길이를 체크하는 부분도 재귀함수를 잘 사용했다면 굳이 필요없는 부분이고, 3-1의 while문 복귀도 원래는 재귀함수를 잘 사용했어야했다.

또, stack의 경우 위의 문제풀이 방식과는 다르게 굉장히 유연한 방식으로 접근했다. Stack을 빈리스트로 만들어 비어있는지, 안에 무엇이 있는지를 체크하는 게 아니라 스택을 변수처럼 이용하여 -1,+1로 스택의 비어있음 상태와 괄호방향까지 한번에 표현했다. 5번 풀이를 처음 프로그래머스에 내고 저 방식을 보고 난 뒤 굉장한 자괴감을 느꼈다. 나는 stack문제를 풀어봤음에도 이번 문제에서 stack을 사용가능하게 구현하는데 45분이 넘게 걸렸는데... 심지어 내 방식보다 처리속도도 훨씬 빠를것이다.

 

반성해야 할 점은 2가지 인것 같다.

1. 한 템포에 로직을 욱여넣지 말것. 커다란 로직 안에 여러 기능들을 구현하여 내가 지금 어떤 부분을 구현하고 있는지 상기하고, 문제 해결 시간을 linear하게 만들자.

2. 아는 자료구조의 사용법이라도 유연하게 받아들이자. 내가 사용하고 있는 언어는 python이지 C가 아니다. 원리만 알고 있으면 구조의 사용법은 무궁무진하다.

댓글