ฉันจะย้ายออกจากโรงเรียนแห่งความคิด“ for-loop” ได้อย่างไร?


79

นี่เป็นคำถามที่ค่อนข้างเป็นแนวคิด แต่ฉันหวังว่าฉันจะได้รับคำแนะนำที่ดีเกี่ยวกับเรื่องนี้ การเขียนโปรแกรมจำนวนมากที่ฉันทำคือกับอาร์เรย์( NumPy ); ฉันมักจะต้องจับคู่รายการในสองอาร์เรย์ขึ้นไปที่มีขนาดแตกต่างกันและสิ่งแรกที่ฉันไปคือ for-loop หรือแย่กว่านั้นเป็นซ้อนสำหรับ for-loop ฉันต้องการหลีกเลี่ยงการวนซ้ำมากที่สุดเพราะมันช้า (อย่างน้อยใน Python)

ฉันรู้ว่าสิ่งต่าง ๆ มากมายกับ 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).ดูเหมือนว่าคุณกำลังแก้ไขปัญหาที่ผิดที่นี่ หากคุณต้องการทำซ้ำสิ่งใดสิ่งหนึ่งคุณจำเป็นต้องทำซ้ำสิ่งใดสิ่งหนึ่ง คุณจะได้รับประสิทธิภาพที่คล้ายคลึงกันไม่ว่าคุณจะใช้ Python แบบไหน หากรหัสของคุณช้าไม่ใช่เพราะคุณมีforลูป เป็นเพราะคุณกำลังทำงานที่ไม่จำเป็นหรือทำงานด้าน Python ที่สามารถทำได้ในฝั่ง C ในตัวอย่างของคุณคุณกำลังทำงานพิเศษ คุณสามารถทำได้ด้วยหนึ่งวงแทนสอง
Doval

24
@Doval แต่น่าเสียดายที่ไม่ได้ - ใน NumPy Python สำหรับการวนลูปที่มีการเพิ่ม elementwise สามารถช้ากว่าตัวดำเนินการ vectorized NumPy (ซึ่งไม่เพียง แต่เขียนใน C เท่านั้น แต่ใช้คำสั่ง SSE และเทคนิคอื่น ๆ ) สำหรับขนาดอาร์เรย์ที่เหมือนจริง

46
ความคิดเห็นบางส่วนด้านบนดูเหมือนจะเข้าใจผิดคำถาม เมื่อการเขียนโปรแกรมใน NumPy คุณจะได้รับผลลัพธ์ที่ดีที่สุดหากคุณสามารถคำนวณการคำนวณแบบเวกเตอร์ของคุณได้นั่นคือแทนที่ลูปอย่างชัดเจนใน Python ด้วยการดำเนินการทั้งอาร์เรย์ใน NumPy นี่เป็นแนวคิดที่แตกต่างจากการเขียนโปรแกรมทั่วไปใน Python และต้องใช้เวลาในการเรียนรู้ ดังนั้นฉันคิดว่ามันสมเหตุสมผลสำหรับ OP ที่จะขอคำแนะนำเกี่ยวกับวิธีการเรียนรู้ที่จะทำ
Gareth Rees

3
@PieterB: ใช่ถูกต้อง "Vectorization" นั้นไม่เหมือนกับ "การเลือกอัลกอริทึมที่ดีที่สุด" พวกเขาเป็นสองแหล่งแยกของความยากลำบากในการคิดหาการใช้งานที่มีประสิทธิภาพและดังนั้นจึงควรคิดถึงพวกมันทีละคน
Gareth Rees

คำตอบ:


89

นี่เป็นปัญหาทางความคิดร่วมกันเมื่อเรียนรู้ที่จะใช้NumPyอย่างมีประสิทธิภาพ โดยปกติแล้วการประมวลผลข้อมูลใน Python จะแสดงออกได้ดีที่สุดในแง่ของตัววนซ้ำเพื่อให้การใช้งานหน่วยความจำต่ำเพื่อเพิ่มโอกาสในการขนานกับระบบ I / O และเพื่อนำมาใช้ใหม่และการรวมกันของส่วนต่างๆของอัลกอริธึม

แต่ NumPy กลับกลายเป็นว่าสิ่งที่อยู่ข้างใน: วิธีที่ดีที่สุดคือการแสดงอัลกอริธึมเป็นลำดับของการดำเนินการทั้งอาเรย์เพื่อลดระยะเวลาที่ใช้ในล่าม Python ที่ช้าและเพิ่มจำนวนเวลาที่ใช้ในรูทีน

นี่คือวิธีการทั่วไปที่ฉันใช้:

  1. รักษาฟังก์ชั่นเวอร์ชั่นดั้งเดิม (ซึ่งคุณมั่นใจว่าถูกต้อง) เพื่อให้คุณสามารถทดสอบกับรุ่นที่ปรับปรุงแล้วของคุณทั้งเพื่อความถูกต้องและความเร็ว

  2. ทำงานจากภายในสู่ภายนอก: นั่นคือเริ่มต้นด้วยการวนรอบด้านในสุดและดูว่าสามารถเวกเตอร์; จากนั้นเมื่อคุณทำเช่นนั้นย้ายออกหนึ่งระดับและดำเนินการต่อ

  3. การใช้จ่ายจำนวนมากเวลาอ่านเอกสาร NumPy มีฟังก์ชั่นและการทำงานมากมายอยู่ในนั้นและพวกมันไม่ได้ตั้งชื่อให้เก่งเสมอไปดังนั้นมันจึงคุ้มค่าที่จะทำความรู้จักกับมัน โดยเฉพาะอย่างยิ่งหากคุณพบว่าตัวเองกำลังคิดว่า "ถ้ามีเพียงฟังก์ชั่นที่ทำเช่นนั้น" แล้วมันก็คุ้มค่าที่จะใช้เวลาสิบนาทีเพื่อค้นหามัน มันมักจะอยู่ที่นั่นที่ไหนสักแห่ง

ไม่มีอะไรมาทดแทนการฝึกฝนดังนั้นฉันจะยกตัวอย่างปัญหาให้คุณ เป้าหมายของแต่ละปัญหาคือการเขียนฟังก์ชั่นใหม่เพื่อให้มันเป็นเวกเตอร์เต็มรูปแบบนั่นคือเพื่อให้มันประกอบด้วยลำดับของการดำเนินงาน NumPy ในอาร์เรย์ทั้งหมดโดยไม่มี Python แบบโลป (ไม่มีforหรือwhileข้อความไม่มีตัววนซ้ำหรือความเข้าใจ)

ปัญหาที่ 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 แก้ไขวิธีที่ Python ตีความรหัสหรือไม่
Izkata

1
@Izkata มันไม่ได้แก้ไขอะไรเลย แต่การดำเนินการทางตรรกะที่ใช้กับอาร์เรย์นั้นถูกกำหนดไว้เพื่อส่งกลับอาร์เรย์แบบบูล
sapi

@sapi Ah, ฉันพลาดสิ่งที่เกิดขึ้นในการสอนคิดว่าสิ่งเหล่านั้นเรียบง่ายlist
Izkata

อาจมีวิธีฝัง APL หรือไม่

ฉันรักวิธีที่คุณให้การบ้าน
Koray Tugay

8

เพื่อให้สิ่งต่าง ๆ เร็วขึ้นคุณต้องอ่านโครงสร้างข้อมูลของคุณและใช้สิ่งที่เหมาะสม

สำหรับขนาดเล็กที่ไม่สำคัญของอาเรย์ขนาดเล็กและอาเรย์ใหญ่ (สมมุติว่า small = 100 element และ big = 10.000 elements) วิธีหนึ่งในการทำคือการเรียงอาเรย์ขนาดเล็กจากนั้นวนซ้ำไปยัง big-array และใช้ binary search เพื่อค้นหาองค์ประกอบที่ตรงกัน ในอาร์เรย์ขนาดเล็ก

สิ่งนี้จะทำให้เกิดความซับซ้อนของเวลาสูงสุด O (N log N) (และสำหรับอาร์เรย์ขนาดเล็กขนาดเล็กและอาร์เรย์ขนาดใหญ่ที่ใหญ่กว่านั้นจะอยู่ใกล้กับ O (N)) ซึ่งโซลูชันลูปซ้อนของคุณคือ O (N ^ 2)

อย่างไรก็ตาม โครงสร้างข้อมูลใดที่มีประสิทธิภาพมากที่สุดนั้นขึ้นอยู่กับปัญหาที่เกิดขึ้นจริง


-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.