나는 이렇게 학습한다/Algorithm & SQL

같은 숫자는 싫어, 그리고 깨달은 세 가지

daco2020 2022. 2. 6. 11:14
반응형

문제 설명

배열 arr가 주어집니다. 배열 arr의 각 원소는 숫자 0부터 9까지로 이루어져 있습니다. 이때, 배열 arr에서 연속적으로 나타나는 숫자는 하나만 남기고 전부 제거하려고 합니다. 단, 제거된 후 남은 수들을 반환할 때는 배열 arr의 원소들의 순서를 유지해야 합니다. 예를 들면,

arr = [1, 1, 3, 3, 0, 1, 1] 이면 [1, 3, 0, 1]을 return 합니다.
arr = [4, 4, 4, 3, 3] 이면 [4, 3] 을 return 합니다.
배열 arr에서 연속적으로 나타나는 숫자는 제거하고 남은 수들을 return 하는 solution 함수를 완성해 주세요.

 

 

제한 사항

배열 arr의 크기 : 1,000,000 이하의 자연수
배열 arr의 원소의 크기 : 0보다 크거나 같고 9보다 작거나 같은 정수

 

 

해결 방법

1. arr의 요소 수만큼 반복문을 돌린다.

2. 해당 인덱스의 요소와 다음 인덱스의 요소가 같은지 비교한다.

3. 같으면 그대로 두고 같지 않으면 answer리스트에 추가한다.

4. 결과 값을 반환한다.

 

 

def solution(arr):
    answer = []
    i = 0
    
    while True:
        try:
            value = arr[i]
            if not value == arr[i+1]:
                answer.append(value)
            i += 1
        
        except IndexError:
            answer.append(value)
            break
            
    return answer


효율성  테스트
테스트 1 〉	통과 (116.60ms, 27.9MB)
테스트 2 〉	통과 (107.03ms, 27.9MB)
테스트 3 〉	통과 (116.49ms, 27.9MB)
테스트 4 〉	통과 (115.47ms, 27.9MB)

일단 이 문제의 가장 큰 걸림돌은 arr[i+1] 부분에서 배열 인덱스 범위를 초과하기 때문에 에러가 발생한다는 것이다. 

 

나는 이 에러를 해결하기위해 [try ~ except ~ ] 예외처리 구문을 이용하였다. 하지만 이 경우에는 코드가 불필요하게 길어지고 내가 상정하지 못한 인덱스 에러까지도 잘못 핸들링될 수 있다는 문제가 생길 수 있었다. 

 

 

 

 

그래서 다음처럼 슬라이싱과 컴프리핸션을 활용하여 더 간결한 코드를 작성하였다.

 

def solution(arr):
    answer = [arr[i] for i in range(len(arr)) if [arr[i]] != arr[i+1:i+2]]
    return answer

효율성  테스트
테스트 1 〉	통과 (213.16ms, 27.8MB)
테스트 2 〉	통과 (212.03ms, 27.9MB)
테스트 3 〉	통과 (188.66ms, 27.9MB)
테스트 4 〉	통과 (211.45ms, 27.9MB)

슬라이싱을 활용할 경우 인덱스 에러가 발생하지 않기 때문에 값을 구할 수 있었다. 그런데 막상 효율성 테스트를 확인해보니 위에 코드보다 효율이 약 2배가량 낮았다.

 

고민이 들었다. 간결한 코드와 효율성 높은 코드... 실무였다면 나는 무슨 코드를 작성해야 할까?

 

 

 

 

상황에 따라 다르겠지만 만약 트래픽이 많고 당장 일정이 급하다면 위에 코드를, 그게 아니라면 아래 코드를 선택했을 것 같다. 나는 이 고민을 다른 동료들에게도 공유해보았다. 

 

 

아래는 민혁 님이 작성해준 코드이다.

 

def solution(arr):
    result = []
    
    for i in arr:
        if result and result[-1]==i:
            continue
        result.append(i)
        
    return result
    
효율성  테스트
테스트 1 〉	통과 (69.24ms, 27.9MB)
테스트 2 〉	통과 (69.28ms, 27.8MB)
테스트 3 〉	통과 (61.87ms, 27.9MB)
테스트 4 〉	통과 (69.26ms, 27.9MB)

민혁 님의 경우 인덱스를 활용하지 않고 요소를 반복하며 직접 비교하는 방법을 택했다. 

 

이렇게 하니 효율성이 훨씬 더 좋아졌다!!!

 

 

 

 


 

 

 

 

 

추가로, 반드시 짧은 코드가 좋은 코드는 아닌 것 같다는 걸 느꼈다.

예를 들어 컴프리핸션을 사용하면 코드의 줄 수는 줄지만 연산은 증가되는 경우가 있다.

 

아까 위에 있던 한 줄짜리 컴프리핸션 코드를 풀어보면 아래 코드와 같다.

 

def solution(arr):
    answer = []
    
    for i in range(len(arr)):
        value = arr[i]
        
        if [value] != arr[i+1:i+2]:
            answer.append(value)
            
    return answer

 

이 코드의 경우 arr [i]의 값을 변수에 저장하고 재활용한다. 하지만 컴프리핸션의 경우 변수에 저장할 틈이 없다. 즉, 이미 구한 값을 다시 구하는 연산을 수행해야 한다. 

 

 

 

 

이번 문제를 풀며 내가 깨달은 점은 다음 세 가지이다.

 

1. 양자택일을 고민하지 말고, 효율성과 가독성 모두 좋은 코드를 고민해라.

2. 인덱스는 생각보다 효율성에 큰 영향을 준다.

3. 코드 줄 수를 줄이는 것보다 연산 횟수를 줄이는 것이 더 좋은 코드이다.

 

 

 

 


 

 

출처: 프로그래머스 코딩 테스트 연습, https://programmers.co.kr/learn/challenges

반응형