본문 바로가기

개발일지/TIL

TIL 23-05-03 팀원과의 코드 리뷰 - 전국 대회 선발 고사

https://github.com/sdoram/Algorithm

https://github.com/sdoram/algorithm_solving_process

 

GitHub - sdoram/algorithm_solving_process: 알고리즘 풀이 과정 python 파일

알고리즘 풀이 과정 python 파일. Contribute to sdoram/algorithm_solving_process development by creating an account on GitHub.

github.com

 

1. 팀원과의 코드 리뷰 - 전국 대회 선발 고사

 문제점

https://school.programmers.co.kr/learn/courses/30/lessons/181851

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

# rank는 등수, attendance는 참석 여부
 # 1등부터 차례대로 참석가능한 3명 구하기

 시도해 본 것들

최초의 방법

def solution(rank, attendance):
    # rank는 등수, attendance는 참석 여부
    # 1등부터 차례대로 참석가능한 3명 구하기
    a_list = []
    # 오름차순으로 1등부터 확인
    for r in sorted(rank):
        # print(r, rank.index(r)) # 등수와 그 등수가 위치한 인덱스 확인
        # attendance의 [r의index와 동일한 index]가 True라면
        if attendance[rank.index(r)]:
            # index 번호를 추가 
            a_list.append(rank.index(r))
        # True인 index만 추가했기 때문에 0번부터 2번까지 뽑아서 사용
    return a_list[0]*10000+a_list[1]*100+a_list[2]

 

문제 지문은 어려워 보였지만, 최근에 사용빈도가 늘어난 index()를 사용해서 어렵지 않게 풀 수 있었다.

 

리팩토링

def solution(rank, attendance):
    a_list = [rank.index(r) for r in sorted(rank) if attendance[rank.index(r)]]
    return a_list[0]*10000+a_list[1]*100+a_list[2]

for문이 존재했기에 for문이 List Comprehension 안으로 들어가면 시간복잡도의 개선도 기대할 수 있어서 리팩터링 했다. 

 

팀원과의 코드 리뷰 

팀원의 dict 사용 코드

def solution(rank, attendance):
    answer = 0
    obj={}
    for i in range(len(rank)):
        if attendance[i]==True:
        # dict에 key로 등수, value로 index 넣기 
            obj[rank[i]]=i
    # key값을 기준으로 오름차순 정렬
    player=dict(sorted(obj.items()))
    print(player)
    # key값을 기준으로 오름차순 정렬 후 key만 뽑아내기
    key=list(sorted(obj.keys()))
    
    return player[key[0]]*10000+player[key[1]]*100+player[key[2]]

등수와 index를 dict에 담아서 풀어낸 코드이다.  

 

팀원의 인상 깊은 range 사용 

def solution(rank, attendance):
    answer = []
    # rank가 1등부터 시작하므로 range == rank의 원소
    for i in range(1, len(rank)+1):
        num = rank.index(i)
        if attendance[num] is True:
            answer.append(num)
            # 참석하는 3등까지 구하면 return
            if len(answer) == 3:
                return answer[0]*10000 + answer[1]*100 + answer[2]

이 문제에서 주어지는 rank가 무조건 1등부터 시작하며 끝은 len(rank)와 같다는 성질을 잘 이용한 코드였다. 

 

코드 리팩토링

def solution(rank, attendance):
    obj={}
    for i in range(len(rank)):
        if attendance[i]:
        # dict에 key로 등수, value로 index 넣기 
            obj[rank[i]]=i
    # key값을 기준으로 오름차순 정렬
    obj=sorted(obj.items())
    return obj[0][1]*10000+obj[1][1]*100+obj[2][1]

obj을 list로 sorted하고 거기서 원소 뽑아내서 사용하기 

 

 해결 방법

def solution(rank, attendance):
    # rank는 등수, attendance는 참석 여부
    # 1등부터 차례대로 참석가능한 3명 구하기
    a_list = []
    # 오름차순으로 1등부터 확인
    for r in sorted(rank):
        print(r, rank.index(r))
        # attendance의 [r의index와 동일한 index]가 True라면
        if attendance[rank.index(r)]:
            # index 번호를 추가 
            a_list.append(rank.index(r))
            if len(a_list) == 3:
                # True인 index만 추가했기 때문에 0번부터 2번까지 뽑아서 사용
                return a_list[0]*10000+a_list[1]*100+a_list[2]

팀원의 코드를 보고 3등까지 구하면 더 이상 코드가 실행되지 않아도 되기 때문에 return 하는 코드 추가

List Comprehension에서 추가할 수 있으면 좋겠지만, list를 만드는 도중에 조건이 충족되면 멈출 수 있는지 모르겠다. 

 알게 된 점

모든 상황에 통하는 방법이 아니라 이 문제에서만 사용가능한 range(1, len(rank)+1) 이 코드가 인상 깊었다. 문제를 읽고 핵심이 될 코드를 정하는 과정도 좋지만, 이렇게 이 문제에서만 사용가능한 방법이 존재하는지 찾는 것도 공부가 될 것 같다. 

 

 

1. 프로그래머스 입문 - 안전지대 

 문제점

 
# 8개의 칸을 위험 지역으로 바꾸기
# -1이 되거나 길이를 벗어나는 경우 컨트롤

 시도해 본 것들

폭탄이 위치한 곳 리스트로 만들기 

def solution(board):
	# board를 깊은 복사로 가져오기
    new_board = board[::]
    boom_list = []
    # 이차원 배열에서 리스트 가져오기
    for i1, b1 in enumerate(board):
    	# 리스트에서 원소 가져오기 
        for i2, b2 in enumerate(b1):
        	# 폭탄인 경우 추가하기 
            if b2 == 1:
                boom_list.append([i1, i2])
    for boom in boom_list:
        print(boom)

    return new_board

폭탄이 위치한 곳을 리스트로 만들었지만, 활용할 방법이 생각나지 않음

 

조건문으로 안전지대 제거하기  

def solution(board):
    new_board = board[::]
    boom_list = []
    for i1, b1 in enumerate(board):
        for i2, b2 in enumerate(b1):
            if b2 == 1:
            # 이차원 배열의 최솟값과 최댓값을 넘는지 체크
                if i1 != 0 and len(board)-1 != i1:
                    new_board[i1-1][i2], new_board[i1+1][i2] = 1, 1
                if i2 != 0 and len(board)-1 != i2:
                    new_board[i1][i2-1], new_board[i1][i2+1] = 1, 1
                    print(id(board[i1][i2+1]), i1) # id 같음 확인
    return new_board
    
print(solution([[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [
      0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0]]))

# id :140721320817448, list index : 3
# id :140721320817448 list index : 4

값이 바뀌는 게 이상해서 확인해본 결과 [::]이 깊은 복사로 다른 id값을 가진다고 알고 있었지만, 확인한 결과는 동일한 id를 지니고 있었다. 심지어 

if i1 != 0 and len(board)-1 != i1:
    board[i1-1][i2], board[i1+1][i2] = 1, 1
if i2 != 0 and len(board)-1 != i2:
    board[i1][i2-1], board[i1][i2+1] = 1, 1
    print(id(board[i1][i2+1]), i1)

처음에는 이렇게 new_board가 아니라 board를 조작하고 있었는데 new_board까지 조작이 되고 있었다. 이 의미는 new_board와 board는 다른 id를 바라보고 있어도 new_board [0]과 board [0]은 같은 id를 바라보고 있다는 뜻이 된다. 

 

4중 for문 사용하기 

def solution(board):
    new_board = board[::]
    for i1, b1 in enumerate(board):
        for i2, b2 in enumerate(b1):
        	# 폭탄일 때
            if b2 == 1:
            	# 상하 index를 range로 표현
                for n1 in range(i1-1, i1+2):
                	# 좌우 index를 range로 표현
                    for n2 in range(i2-1, i2+2):
                        print(n1, n2) # 위험 지대 확인
    return new_board

2중 for문을 마지노선으로 생각하고 이리저리 생각해봤지만 답이 나오지 않아서 for문을 마음껏 늘려서 사용했다. 

한 번에 많은 경우를 처리하지 말고 연산을 추가하더라도 모든 경우의 수를 확인했다. 

 

얕은 복사 해결하기 

new_board = [[0]*len(board) for _ in board]

모든 경우의 수를 확인하기로 마음을 먹었기 때문에 크기만 같은 이차원 배열을 만들어서 id가 같은 경우를 해결했다.

 

이차원 배열밖으로 나가는 경우 방지 하기

if len(board) > n2 >= 0:

 

 해결 방법

def solution(board):
    # 얕은 복사를 방지하기 위해서 for문을 사용한 이차원 배열 만들기
    new_board = [[0]*len(board) for _ in board]
    # index의 위치를 알기 위한 enumerate사용
    for i1, b1 in enumerate(board):
        for i2, b2 in enumerate(b1):
            # 폭탄이 있으면
            if b2 == 1:
                # 상하 index를 range로 표현
                for n1 in range(i1-1, i1+2):
                    # 이차원 배열 범위 밖으로 나가는 경우 방지
                    if len(board) > n1 >= 0:
                        # 좌우 index를 range로 표현
                        for n2 in range(i2-1, i2+2):
                            # 이차원 배열 범위 밖으로 나가는 경우 방지
                            if len(board) > n2 >= 0:
                                # print(n1, n2) # 위험 지대 확인
                                new_board[n1][n2] = 1
    # board가 n*n의 이차원 배열이므로 len() **2으로 총 원소의 개수 구하기
    # [sum(n) for n in new_board]으로 폭탄이 1이므로 각 리스트에서 폭탄 개수 구하기
    # sum()으로 리스트의 결과값 더하기 
    return len(board)**2-sum([sum(n) for n in new_board])

sum()을 한 번에 두 번 써보는 특이한 방식으로 정답을 구했다. 

 알게 된 점

문제를 해결하고 깊은 복사에 대해서 찾아본 결과 이차원 배열 같은 mutable안에 mutable이 존재하면 id(board)와 id(new_board)는 다르지만 new_board [0]과 board [0]는 같은 주소를 사용하는 객체가 사용된다. 

이차원 배열을 [::]으로 복사하면 결국 VSCode에서 Live Share를 사용하는 것처럼 다른 id지만 그 안을 조작하면 모두가 바뀌는 상태가 된다. 

변수 선언을 잘해서 이상한 곳에서 값을 넣지 않도록 하자.