Làm thế nào để tôi rời khỏi trường tư tưởng của nhóm for for loop loop?


79

Đây là một câu hỏi khá khái niệm, nhưng tôi đã hy vọng tôi có thể nhận được một số lời khuyên tốt về điều này. Rất nhiều chương trình tôi làm là với các mảng ( NumPy ); Tôi thường phải ghép các mục trong hai hoặc nhiều mảng có kích thước khác nhau và điều đầu tiên tôi làm là một vòng lặp for hoặc thậm chí tệ hơn, một vòng lặp for lồng nhau. Tôi muốn tránh các vòng lặp càng nhiều càng tốt, vì chúng chậm (ít nhất là trong Python).

Tôi biết rằng đối với rất nhiều thứ với NumPy, có những lệnh được xác định trước mà tôi chỉ cần nghiên cứu, nhưng bạn (như những lập trình viên có kinh nghiệm hơn) có một quá trình suy nghĩ chung xuất hiện trong đầu khi bạn phải lặp đi lặp lại điều gì không?

Vì vậy, tôi thường có một cái gì đó như thế này, đó là khủng khiếp và tôi muốn tránh nó:

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]

Tôi biết có nhiều cách khác nhau để đạt được điều này nói riêng, nhưng tôi quan tâm đến một phương pháp tư duy chung, nếu nó tồn tại.


10
Bạn đang tìm kiếm lập trình chức năng : biểu thức lambda, hàm bậc cao hơn, tạo biểu thức, v.v.
Kilian Foth

42
I want to avoid for-loops as much as possible because they are slow (at least in Python).Âm thanh như bạn đang giải quyết vấn đề sai ở đây. Nếu bạn cần lặp đi lặp lại một cái gì đó, bạn cần lặp đi lặp lại một cái gì đó; bạn sẽ có một hiệu suất tương tự cho dù bạn sử dụng Python nào. Nếu mã của bạn chậm thì không phải vì bạn có forvòng lặp; đó là bởi vì bạn đang làm những việc không cần thiết hoặc làm việc ở phía Python có thể được thực hiện ở phía C. Trong ví dụ của bạn, bạn đang làm thêm; bạn có thể thực hiện nó với một vòng thay vì hai vòng.
Doval

24
@Doval Thật không may - trong NumPy . Một Python cho vòng lặp thực hiện phép cộng phần tử có thể dễ dàng chậm hơn nhiều lần (!) So với toán tử NumPy được vector hóa (không chỉ được viết bằng C mà sử dụng các lệnh SSE và các thủ thuật khác) cho các kích thước mảng thực tế.

46
Một số ý kiến ​​trên dường như hiểu sai câu hỏi. Khi lập trình trong NumPy, bạn sẽ nhận được kết quả tốt nhất nếu bạn có thể vector hóa tính toán của mình - nghĩa là thay thế các vòng lặp rõ ràng trong Python bằng các hoạt động toàn mảng trong NumPy. Đây là khái niệm rất khác với lập trình thông thường trong Python và cần có thời gian để tìm hiểu. Vì vậy, tôi nghĩ rằng OP hợp lý khi yêu cầu tư vấn về cách học tập để làm điều đó.
Gareth Rees

3
@PieterB: Vâng, đúng vậy. "Vectorization" không giống như "chọn thuật toán tốt nhất". Chúng là hai nguồn khó khăn riêng biệt trong việc đưa ra các triển khai hiệu quả và vì vậy tốt nhất bạn nên nghĩ về chúng cùng một lúc.
Gareth Rees

Câu trả lời:


89

Đây là một khó khăn khái niệm phổ biến khi học cách sử dụng NumPy một cách hiệu quả. Thông thường, xử lý dữ liệu trong Python được thể hiện tốt nhất theo các trình vòng lặp , để giữ mức sử dụng bộ nhớ thấp, để tối đa hóa cơ hội song song với hệ thống I / O và để cung cấp cho việc sử dụng lại và kết hợp các phần của thuật toán.

Nhưng NumPy biến tất cả những gì từ trong ra ngoài: cách tiếp cận tốt nhất là diễn đạt thuật toán như một chuỗi các hoạt động toàn mảng , để giảm thiểu lượng thời gian dành cho trình thông dịch Python chậm và tối đa hóa lượng thời gian dành cho các thói quen NumPy được biên dịch nhanh.

Đây là cách tiếp cận chung mà tôi thực hiện:

  1. Giữ phiên bản gốc của chức năng (mà bạn tự tin là chính xác) để bạn có thể kiểm tra nó so với các phiên bản cải tiến của bạn cả về tính chính xác và tốc độ.

  2. Làm việc từ trong ra ngoài: nghĩa là, bắt đầu với vòng lặp trong cùng và xem liệu có thể được vector hóa không; sau đó khi bạn đã làm điều đó, di chuyển ra một cấp và tiếp tục.

  3. Dành nhiều thời gian để đọc tài liệu NumPy . Có rất nhiều chức năng và hoạt động trong đó và chúng không phải lúc nào cũng được đặt tên rực rỡ, vì vậy thật đáng để làm quen với chúng. Đặc biệt, nếu bạn thấy mình suy nghĩ, "nếu chỉ có một chức năng đã làm như vậy," thì thật đáng để dành mười phút để tìm kiếm nó. Nó thường ở đó đâu đó.

Không có thay thế cho thực hành, vì vậy tôi sẽ cung cấp cho bạn một số vấn đề ví dụ. Mục tiêu cho mỗi vấn đề là viết lại hàm sao cho nó được vector hóa hoàn toàn : nghĩa là nó bao gồm một chuỗi các hoạt động NumPy trên toàn bộ mảng, không có vòng lặp Python nguyên gốc (không có forhoặc các whilecâu lệnh, không lặp hoặc hiểu).

Vấn đề 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

Vấn đề 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

Vấn đề 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)

Kẻ phá hoại dưới đây. Bạn sẽ nhận được nhiều kết quả tốt nhất nếu bạn tự mình đi trước khi xem xét các giải pháp của tôi!

trả lời 1

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

Trả lời 2

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

Trả lời 3

np.where (x == thiếu, giá trị, x)


Đợi đã, có một lỗi đánh máy trong câu trả lời cuối cùng hay NumPy có sửa đổi cách Python diễn giải mã không?
Izkata

1
@Izkata Nó không sửa đổi bất cứ điều gì theo từng se, nhưng các hoạt động logic được áp dụng cho các mảng được xác định để trả về các mảng boolean.
sapi

@sapi Ah, tôi đã bỏ lỡ những gì đang diễn ra trong các học thuyết, nghĩ rằng đó là những điều đơn giảnlist
Izkata

Có lẽ nên có một cách để nhúng APL?

Tôi thích cách bạn cho bài tập về nhà.
Koray Tugay

8

Để làm cho mọi thứ nhanh hơn, bạn phải đọc các cấu trúc dữ liệu của bạn và sử dụng các cấu trúc dữ liệu phù hợp.

Đối với các kích thước không nhỏ của mảng nhỏ và mảng lớn (giả sử small = 100 phần tử và big = 10.000 phần tử) một cách để làm là sắp xếp mảng nhỏ, sau đó lặp qua mảng lớn và sử dụng tìm kiếm nhị phân để tìm các phần tử khớp trong mảng nhỏ.

Điều này sẽ làm cho độ phức tạp thời gian tối đa, O (N log N) (và đối với các mảng nhỏ nhỏ và mảng lớn rất lớn, nó gần với O (N)) trong đó giải pháp vòng lặp lồng nhau của bạn là O (N ^ 2)

Tuy nhiên. cấu trúc dữ liệu nào hiệu quả nhất phụ thuộc nhiều vào vấn đề thực tế.


-3

Bạn có thể sử dụng từ điển để tối ưu hóa hiệu suất đáng kể

Đây là một ví dụ khác:

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
Điều này khá rõ ràng vẫn đang sử dụng trường phái tư tưởng "vòng lặp".
8bittree
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.