Python: วิธีที่เร็วที่สุดในการสร้างรายการ n รายการ


98

ดังนั้นฉันจึงสงสัยว่าจะสร้างรายการว่างให้ดีที่สุดได้อย่างไร:

[[],[],[]...]

เนื่องจาก Python ทำงานกับรายการในหน่วยความจำจึงไม่ได้ผล:

[[]]*n

สิ่งนี้สร้าง[[],[],...]แต่แต่ละองค์ประกอบเป็นรายการเดียวกัน:

d = [[]]*n
d[0].append(1)
#[[1],[1],...]

สิ่งที่เหมือนกับการทำความเข้าใจรายการทำงาน:

d = [[] for x in xrange(0,n)]

แต่ใช้ Python VM สำหรับการวนซ้ำ มีวิธีใดบ้างในการใช้การวนซ้ำโดยนัย (โดยใช้ประโยชน์จากการเขียนด้วย C)?

d = []
map(lambda n: d.append([]),xrange(0,10))

นี่ช้ากว่าจริง :(


1
d = [[] for x in xrange(0,n)]ฉันจะต้องแปลกใจถ้ามีอะไรที่มีนัยสำคัญได้เร็วกว่า คุณต้องวนซ้ำอย่างชัดเจนใน Python หรือเรียกใช้ฟังก์ชัน Python / lambda ซ้ำ ๆ (ซึ่งควรจะช้ากว่า) แต่ก็ยังหวังว่าจะมีคนโพสต์บางอย่างที่แสดงว่าฉันผิด :).
MAK

1
เมื่อคุณวัดด้วยtimeitสิ่งเหล่านี้คุณได้เรียนรู้อะไร?
ล็อต

ฉันเพิ่งยืนยันว่าmap(lambda x: [], xrange(n))ช้ากว่าความเข้าใจในรายการ
Andrew Clark

คำตอบ:


105

อาจเป็นวิธีเดียวที่เร็วกว่าเล็กน้อย

d = [[] for x in xrange(n)]

คือ

from itertools import repeat
d = [[] for i in repeat(None, n)]

ไม่ต้องสร้างintออบเจ็กต์ใหม่ในทุก ๆ การทำซ้ำและเร็วขึ้นประมาณ 15% บนเครื่องของฉัน

แก้ไข : การใช้ NumPy คุณสามารถหลีกเลี่ยงการวนซ้ำ Python โดยใช้ไฟล์

d = numpy.empty((n, 0)).tolist()

แต่จริงๆแล้วช้ากว่าความเข้าใจรายการ 2.5 เท่า


แล้วไงmap(lambda x:[], repeat(None,n))?
PaulMcG

3
@ พอล: นั่นจะช้าลงอีกครั้งเนื่องจากการเรียกฟังก์ชันเหนือศีรษะของนิพจน์แลมบ์ดา
Sven Marnach

ตอนนี้ไม่ควรอัปเดตช่วงที่แตกต่างกันใน Python 3 หรือไม่
beruic

@beruic ฉันแค่อ้างรหัสจากคำถามในบล็อกรหัสแรกดังนั้นจึงไม่สมเหตุสมผลที่จะเปลี่ยนสิ่งนั้น
Sven Marnach

12

comprehensions รายการจริงจะดำเนินการได้อย่างมีประสิทธิภาพมากขึ้นกว่าการวนลูปอย่างชัดเจน (ดูเอาท์พุทสำหรับฟังก์ชั่นตัวอย่าง ) และวิธีที่มีการเรียกวัตถุ callable ophaque ทวนทุกซึ่งเกิดขึ้นจากค่าใช้จ่ายค่าใช้จ่ายมากdismap

ไม่ว่า[[] for _dummy in xrange(n)]จะเป็นวิธีที่ถูกต้องในการทำและไม่มีความแตกต่างด้านความเร็วเล็ก ๆ น้อย ๆ (ถ้ามีอยู่เลย) ความแตกต่างของความเร็วระหว่างวิธีอื่น ๆควรมีความสำคัญ เว้นแต่คุณจะใช้เวลาส่วนใหญ่ในการทำสิ่งนี้ - แต่ในกรณีนี้คุณควรทำงานกับอัลกอริทึมของคุณแทน คุณสร้างรายการเหล่านี้บ่อยแค่ไหน?


5
กรุณาอย่า_ใช้เป็นชื่อตัวแปร! ไม่งั้นตอบดี :)
Sven Marnach

11
@Sven: ทำไมไม่? โดยทั่วไปจะใช้สำหรับตัวแปรที่ไม่ได้ใช้ (ถ้ามีการเรียกใช้iI for one จะมองหาตำแหน่งที่ใช้) ข้อผิดพลาดประการเดียวก็คือมันทำให้เงาของการ_ถือครองผลลัพธ์สุดท้ายใน REPL ... และนั่นเป็นเพียงกรณีใน 2.x ที่ความเข้าใจในรายการรั่วไหล

ไม่บ่อยนักซึ่งเป็นสาเหตุที่ฉันใช้ความเข้าใจในรายการ คิดว่ามันน่าสนใจที่จะเห็นสิ่งที่ผู้คนพูดถึง ฉันเห็น Dropbox พูดคุยที่ PyCon ด้วยการใช้ itertools.imap แทนที่จะเป็น for loop เพื่ออัปเดตแฮช md5 เป็นจำนวนมากอย่างไร้เหตุผลและฉันก็หมกมุ่นอยู่กับ C ลูปตั้งแต่นั้นมา
munchybunch

12
เหตุผลที่สำคัญที่สุดที่จะไม่ใช้ก็คือมันมีแนวโน้มที่จะทำให้ผู้คนสับสนทำให้พวกเขาคิดว่ามันเป็นไวยากรณ์พิเศษบางประเภท และนอกเหนือจากความขัดแย้ง_ในล่ามแบบโต้ตอบแล้วมันยังขัดแย้งกับนามแฝง gettext ทั่วไป หากคุณต้องการให้ชัดเจนว่าตัวแปรเป็นตัวแปรหุ่นเรียกว่าไม่dummy _
Sven Marnach

12

ต่อไปนี้เป็นสองวิธีวิธีหนึ่งที่เรียบง่ายและเรียบง่าย (และแนวความคิด) อีกวิธีเป็นทางการมากขึ้นและสามารถขยายได้ในสถานการณ์ต่างๆหลังจากอ่านชุดข้อมูลแล้ว

วิธีที่ 1: แนวความคิด

X2=[]
X1=[1,2,3]
X2.append(X1)
X3=[4,5,6]
X2.append(X3)
X2 thus has [[1,2,3],[4,5,6]] ie a list of lists. 

วิธีที่ 2: เป็นทางการและขยายได้

อีกวิธีหนึ่งที่ยอดเยี่ยมในการจัดเก็บรายการเป็นรายการของตัวเลขที่แตกต่างกันซึ่งอ่านจากไฟล์ (ไฟล์ที่นี่มีชุดข้อมูลรถไฟ) Train คือชุดข้อมูลที่มีคำพูด 50 แถวและ 20 คอลัมน์ กล่าวคือ. Train [0] ให้แถวที่ 1 ของไฟล์ csv, train [1] ให้แถวที่ 2 ไปเรื่อย ๆ ฉันสนใจที่จะแยกชุดข้อมูลที่มี 50 แถวเป็นรายการเดียวยกเว้นคอลัมน์ 0 ซึ่งเป็นตัวแปรที่ฉันอธิบายไว้ที่นี่ดังนั้นจึงต้องลบออกจากชุดข้อมูลรถไฟดั้งเดิมจากนั้นจึงปรับขนาดรายการตามรายการ - นั่นคือรายการของรายการ . นี่คือรหัสที่ทำเช่นนั้น

โปรดทราบว่าฉันกำลังอ่าน "1" ในวงในเนื่องจากฉันสนใจตัวแปรที่อธิบายเท่านั้น และฉันเริ่มต้น X1 = [] อีกครั้งในลูปอื่นอีกครั้ง X2.append ([0: (len (train [0]) - 1)]) จะเขียน X1 ซ้ำแล้วซ้ำอีก - นอกจากนี้หน่วยความจำจะมีประสิทธิภาพมากขึ้น

X2=[]
for j in range(0,len(train)):
    X1=[]
    for k in range(1,len(train[0])):
        txt2=train[j][k]
        X1.append(txt2)
    X2.append(X1[0:(len(train[0])-1)])

4

ในการสร้างรายการและรายชื่อใช้ไวยากรณ์ด้านล่าง

     x = [[] for i in range(10)]

สิ่งนี้จะสร้างรายการ 1-d และเริ่มต้นให้ใส่หมายเลขใน [[หมายเลข] และกำหนดความยาวของรายการใส่ความยาวในช่วง (ความยาว)

  • ในการสร้างรายการใช้ไวยากรณ์ด้านล่าง
    x = [[[0] for i in range(3)] for i in range(10)]

สิ่งนี้จะเริ่มต้นรายการด้วยมิติข้อมูล 10 * 3 และมีค่า 0

  • ในการเข้าถึง / จัดการองค์ประกอบ
    x[1][5]=value

2

ดังนั้นฉันจึงทำการเปรียบเทียบความเร็วเพื่อให้ได้วิธีที่เร็วที่สุด ความเข้าใจในรายการนั้นเร็วมาก วิธีเดียวที่จะเข้าใกล้ได้คือหลีกเลี่ยงการถูก exectuded bytecode ระหว่างการสร้างรายการ ความพยายามครั้งแรกของฉันคือวิธีการต่อไปนี้ซึ่งโดยหลักการแล้วจะเร็วกว่า:

l = [[]]
for _ in range(n): l.extend(map(list,l))

(สร้างรายการความยาว 2 ** n แน่นอน) โครงสร้างนี้ช้าเป็นสองเท่าของความเข้าใจรายการตามเวลาสำหรับรายการทั้งแบบสั้นและแบบยาว (หนึ่งล้านรายการ)

ความพยายามครั้งที่สองของฉันคือใช้ starmap เพื่อเรียกตัวสร้างรายการสำหรับฉันมีโครงสร้างหนึ่งซึ่งดูเหมือนจะเรียกใช้ตัวสร้างรายการด้วยความเร็วสูงสุด แต่ก็ยังช้ากว่า แต่เพียงเล็กน้อยเท่านั้น:

from itertools import starmap
l = list(starmap(list,[()]*(1<<n)))

เวลาดำเนินการที่น่าสนใจเพียงพอแสดงให้เห็นว่าเป็นการเรียกรายการสุดท้ายที่ทำให้โซลูชัน starmap ช้าเนื่องจากเวลาดำเนินการเกือบจะเท่ากับความเร็วของ:

l = list([] for _ in range(1<<n))

ความพยายามครั้งที่สามของฉันเกิดขึ้นเมื่อฉันรู้ว่ารายการ (()) สร้างรายการด้วยดังนั้นฉันจึงลองใช้วิธีง่ายๆอย่างเห็นได้ชัด:

l = list(map(list, [()]*(1<<n)))

แต่มันช้ากว่าการโทรแบบสตาร์แมป

สรุป: สำหรับคนบ้าความเร็ว: ใช้ความเข้าใจในรายการ ฟังก์ชั่นการโทรเท่านั้นถ้าคุณต้องการ ใช้บิลท์อิน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.