[TIL] numpy도 집합 연산이 가능하다는 사실 알고계신가요?

3627 단어 pythonnumpynumpy

컴퓨터 과학은 시간과의 싸움이다.

이게 무슨의미인지 대충은 알 것이다.

똑같은 연산을 하더라도 효율적인 것을 사용하는게 더 좋다는 이야기이다.

특히 sql 쿼리문등등 큰 데이터를 처리할 때 이러한 것들이 더욱더 빛을 발한다.

우리가 항상 고민해야 할 문제: 선형탐색 이게 가장 효율적인 것일까?

선형탐색 은 국밥이다.

내가 생각하는 선형 탐색은 대체로 자료의 갯수에 시간이 비례하므로 좋지도 나쁘지도 않은 탐색 방법이다.

때문에 먼저 고민하는 방법은 선형탐색일 것이다.

그러나 우리는 더 효율적으로, 더 빠르게, 더 저렴하게 해당되는 데이터를 찾고 싶다.

그렇다면 우리는 자료의 구조를 한번 더 생각해 볼 필요가 있다.

numpy에서 set에 대한 함수가 구현이 되어 있다.

이글을 읽는 독자라면 어느 정도 set에 대해 많이 알고 있겠지만, set의 강점을 간단히 해보면

  • 집합 연산이 가능하다.
  • 중복이 존재 하지 않는다.
  • 해시 테이블의 특징을 갖고 있다.

그럼 구체적으로 문제를 풀면서 생각해 보자.

인덱스를 리턴하는 문제

그렙월드의 T익스플로러는 몸무게와 키에 대하여 다음과 같은 이용 제한을 두고 있습니다.

키는 150cm 이상 195cm 이하
몸무게는 140kg 미만
그러나 관광객의 대다수는 이용 제한을 읽지 않고 줄을 기다리다 타기 직전, 탑승 불가 통보를 받아 불만을 제기하고 있습니다. 이를 방지하고자 줄 서 있는 사람들을 조사하여 탑승 불가한 손님들에게 미리 정보를 전달하려 합니다. 줄 서 있는 사람들의 순서에 맞춰 그들의 키와 몸무게는 info에 다음과 같이 담겨있습니다.

info 첫 번째 행에는 사람들의 키 정보가 담겨있다.
info 두 번째 행에는 사람들의 몸무게 정보가 담겨있다.
info가 numpy.ndarray타입의 2차원 배열로 주어질 때, 이용 제한에 걸리는 손님들의 인덱스를 list에 담아 반환하는 함수를 구현하세요.

제한 사항
info에는 np.float64 타입의 원소들이 담겨있다.
입출력 예
info return
[[151.4 172.45 138.65 177.63 207.46] [ 44.64 163.5 112.35 73.55 97.83]][1, 2, 4]

how to solve?

생각 없이 문제의 말 그대로 로직을 짜보면 이렇게 생각할 수 있다.

첫번째 사람에 대한 값 조건과 비교 > 두번째 사람에 대한 값 조건과 비교 > 세번째 사람에 대한 값 조건과 비교 > ...... > N번째 사람에 대한 값 조건과 비교

진짜 국밥 처럼 든든하고 우직하게 처음부터 끝까지 모두 비교해서 찾아 냈다.

코드로 구현하면 아래와 같다.

import numpy as np

def solution(info):
    answer = []
    length = info.shape[1]  ### 우리가 반복해야 할 횟수
    for i in range(length):
        if (info[0, i] >= 150) & (info[0, i] <= 195) & (info[1, i] < 140):
            continue
            
        else:
            answer.append(i)
    return answer

이렇게 하면 해가 주어진다.

그렇다면 나는 묻고 싶다. 이게 정말 최선일까?

numpy.where()과 numpy.union1d() 함수

우리는 넘파이를 쓰는 이유를 망각해서는 안된다.

최대한 반복문을 쓰지 않고 연산을 해야한다.

이제 묻고 싶다. 위의 코드는 넘파이의 목적에 위배되지 않았는가?

for 문이 들어가 있다. for문을 없앨 수 없을까?

우리는 이미 이러한 함수를 다 구현해 놓았다.

numpy.where()는 인덱스를 리턴해준다.

자 기본적인 무기는 주어졌다. 코드를 짜면 된다.

def solution(info):
    height = info[0,] 
    weight = info[1,]
    answer = np.where((height < 150) | (height > 195) | (weight >= 140))
    
    answer = list(answer[0])
    return answer

코드가 반복문을 사용하지 않고도 효율적으로 솔루션을 구했다.

하지만 나는 여기서 만족하지 않고 answer를 분해하고 싶었다.

어느날 기계가 갑자기 힘이 세져서 140 이상인 사람들도 탈 수 있게 될 수도 있으니까.

def solution(info):
    height = info[0,]
    weight = info[1,]
    set_h = np.where((height < 150) | (height > 195)) # 키 때문에 안되는 사람들
    set_w = np.where((weight >= 140)) # 몸무게 때문에 안되는 사람들
    answer = list(np.union1d(set_h, set_w)) # 모두 합치기
    return answer

위의 결과는 아래와 같이 시뮬레이션을 해보았다.
분할case vs 반복문

통합case vs 반복문

결론

통합 case가 확실히 더 빠르다.

하지만 반복문에 비해서 두 케이스가 월등히 빠르다

따라서 가독성이나, 시스템 사양 여부등을 고려해서 활용하는 것이 좋을 것 같다.

좋은 웹페이지 즐겨찾기