“for-loop”사고 학교에서 벗어나려면 어떻게해야합니까?


79

이것은 다소 개념적 질문이지만 이것에 대해 좋은 조언을 얻을 수 있기를 바랍니다. 내가하는 많은 프로그래밍은 ( NumPy ) 배열을 사용합니다. 나는 종종 크기가 다른 두 개 이상의 배열에서 항목을 일치시켜야하며 가장 먼저 갈 것은 for-loop 또는 더 나쁜 것은 중첩 된 for-loop입니다. for 루프는 (최소한 파이썬에서는) 느리기 때문에 가능한 많은 루프를 피하고 싶습니다.

NumPy와 관련하여 많은 것들에 대해 미리 연구해야 할 사전 정의 된 명령이 있지만 (숙련 된 프로그래머로서) 무언가를 반복해야 할 때 생각할 수있는 일반적인 사고 과정이 있습니까?

그래서 나는 종종 이와 같은 것을 가지고 있습니다.

small_array = np.array(["one", "two"])
big_array = np.array(["one", "two", "three", "one"])

for i in range(len(small_array)):
    for p in range(len(big_array)):
        if small_array[i] == big_array[p]:
            print "This item is matched: ", small_array[i]

나는 이것을 달성하기 위해 여러 가지 다른 방법이 있다는 것을 알고 있지만 그것이 존재하는 경우 일반적인 사고 방법에 관심이 있습니다.


10
람다 식, 고차 함수, 식 생성 등 Google의 함수형 프로그래밍을 찾고 있습니다.
Kilian Foth

42
I want to avoid for-loops as much as possible because they are slow (at least in Python).여기에서 잘못된 문제를 해결하는 것 같습니다. 무언가를 반복해야하는 경우 무언가를 반복해야합니다. 어떤 파이썬 구조를 사용하든 비슷한 성능을 발휘합니다. 코드가 느리다면 for루프 가 없기 때문이 아닙니다 . C 측에서 수행 할 수있는 불필요한 작업이나 Python 측 작업을 수행하기 때문입니다. 귀하의 예에서 추가 작업을하고 있습니다. 두 개가 아닌 하나의 루프로 할 수 있습니다.
Doval

24
@Doval 불행히도 NumPy에서는 그렇지 않습니다 . 요소 단위로 덧셈을 수행하는 Python for 루프는 사실적인 배열 크기에 대해 벡터화 된 NumPy 연산자 (C로 작성 될뿐만 아니라 SSE 명령어 및 기타 트릭을 사용함)보다 몇 배나 느릴 수 있습니다 (!).

46
위의 의견 중 일부는 질문을 오해하는 것 같습니다. NumPy로 프로그래밍 할 때 계산을 벡터화 할 수 있으면 최상의 결과를 얻을 수 있습니다. 즉, Python의 명시 적 루프를 NumPy의 전체 배열 연산으로 바꿉니다. 이것은 개념적으로 파이썬의 일반 프로그래밍과는 매우 다르며 배우는 데 시간이 걸립니다. 그래서 OP가 그것을 배우는 방법에 대해 조언을 구하는 것이 합리적이라고 생각합니다.
Gareth Rees

3
@PieterB : 그렇습니다. "벡터화"는 "최상의 알고리즘 선택"과 다릅니다. 그것들은 효율적인 구현을 위해 두 가지 별개의 어려움의 원천이므로 한 번에 하나씩 생각하는 것이 가장 좋습니다.
Gareth Rees

답변:


89

이것은 NumPy를 효과적으로 사용하는 법을 배울 때 일반적으로 어려운 개념 입니다. 일반적으로 Python의 데이터 처리는 반복자 , 메모리 사용률을 낮게 유지, I / O 시스템과의 병렬 처리 기회를 최대화하고 알고리즘의 일부를 재사용 및 조합 할 수 있도록 표현하는 것이 가장 좋습니다 .

그러나 NumPy는 모든 것을 뒤집습니다. 최상의 접근법은 알고리즘을 전체 배열 연산 시퀀스로 표현 하여 느린 Python 인터프리터에서 소비되는 시간을 최소화하고 빠른 컴파일 된 NumPy 루틴에서 소비되는 시간을 최대화하는 것입니다.

내가 취하는 일반적인 접근 방식은 다음과 같습니다.

  1. 정확성과 속도를 위해 개선 된 버전에 대해 테스트 할 수 있도록 원래 버전의 기능 (정확하다고 확신하는 경우)을 유지하십시오.

  2. 안쪽에서 바깥쪽으로 작업하십시오. 즉, 가장 안쪽 루프로 시작하여 벡터화 할 수 있는지 확인하십시오. 그런 다음 한 단계 밖으로 이동하여 계속하십시오.

  3. NumPy 문서를 읽는 데 많은 시간을 보내십시오 . 거기에는 많은 기능과 작업이 있으며 항상 훌륭하게 이름이 지정되는 것은 아니므로 알 필요가 있습니다. 특히, "만약 그런 기능을 수행 한 기능 만 있다면"10 분 동안 그것을 찾는 것이 좋습니다. 보통 어딘가에 있습니다.

연습을 대신 할 수는 없기 때문에 몇 가지 예를 들어 보겠습니다. 각 문제의 목표는 함수가 완전히 벡터화 되도록 함수를 다시 작성하는 것입니다 . 즉, 원시 파이썬 루프 ( for또는 while명령문, 반복자 또는 이해 가 없음)가없는 전체 배열에 대한 일련의 NumPy 연산으로 구성됩니다 .

문제 1

def sumproducts(x, y):
    """Return the sum of x[i] * y[j] for all pairs of indices i, j.

    >>> sumproducts(np.arange(3000), np.arange(3000))
    20236502250000

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            result += x[i] * y[j]
    return result

문제 2

def countlower(x, y):
    """Return the number of pairs i, j such that x[i] < y[j].

    >>> countlower(np.arange(0, 200, 2), np.arange(40, 140))
    4500

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            if x[i] < y[j]:
                result += 1
    return result

문제 3

def cleanup(x, missing=-1, value=0):
    """Return an array that's the same as x, except that where x ==
    missing, it has value instead.

    >>> cleanup(np.arange(-3, 3), value=10)
    ... # doctest: +NORMALIZE_WHITESPACE
    array([-3, -2, 10, 0, 1, 2])

    """
    result = []
    for i in range(len(x)):
        if x[i] == missing:
            result.append(value)
        else:
            result.append(x[i])
    return np.array(result)

아래 스포일러. 내 솔루션을보기 전에 직접 가면 최고의 결과를 얻을 수 있습니다!

답변 1

np.sum (x) * np.sum (y)

답변 2

np.sum (np.searchsorted (np.sort (x), y))

답변 3

np.where (x == 누락, 값, x)


잠깐, 마지막 답변에 오타가 있습니까, 아니면 NumPy가 파이썬이 코드를 해석하는 방식을 수정합니까?
이즈 카타

1
@Izkata 그 자체로는 아무것도 수정하지 않지만 배열에 적용된 논리 연산은 부울 배열을 반환하도록 정의됩니다.
sapi

아 @sapi, 나는 어떤이의 doctest에서 일어나는 놓친 사람들은 보통 생각했다list
Izkata

APL을 포함시키는 방법이 있을까요?

나는 네가 숙제를하는 방법을 좋아한다.
Koray Tugay 2016

8

더 빠르게 작업하려면 데이터 구조를 읽고 적절한 구조를 사용해야합니다.

작은 배열과 큰 배열의 작은 크기 (작은 = 100 요소와 큰 = 10.000 요소라고 함)의 경우 작은 배열을 정렬 한 다음 큰 배열을 반복하고 이진 검색을 사용하여 일치하는 요소를 찾는 것입니다 작은 배열에서.

이것은 최대 시간 복잡도, O (N log N)를 만들 것입니다 (그리고 작은 작은 배열과 매우 큰 큰 배열의 경우 중첩 루프 솔루션이 O (N ^ 2) 인 O (N)에 더 가깝습니다)

하나. 가장 효율적인 데이터 구조는 실제 문제에 크게 의존합니다.


-3

사전을 사용하여 성능을 크게 최적화 할 수 있습니다

이것은 또 다른 예입니다.

locations = {}
for i in range(len(airports)):
    locations[airports["abb"][i][1:-1]] = (airports["height"][i], airports["width"][i])

for i in range(len(uniqueData)):
    h, w = locations[uniqueData["dept_apt"][i]]
    uniqueData["dept_apt_height"][i] = h
    uniqueData["dept_apt_width"][i] = w

2
이것은 여전히 ​​"for-loop"사고 방식을 사용하고 있습니다.
8bittree
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.