Python: รายการ vs Dict เพื่อค้นหาตาราง


169

ฉันมีค่าประมาณ 10 ล้านที่ฉันจำเป็นต้องใส่ในตารางค้นหาบางประเภทดังนั้นฉันจึงสงสัยว่ารายการหรือdict ที่มีประสิทธิภาพมากกว่านี้คืออะไร

ฉันรู้ว่าคุณสามารถทำสิ่งนี้ได้ทั้ง:

if something in dict_of_stuff:
    pass

และ

if something in list_of_stuff:
    pass

ความคิดของฉันคือ dict จะเร็วขึ้นและมีประสิทธิภาพมากขึ้น

ขอบคุณสำหรับความช่วยเหลือของคุณ.

แก้ไข 1
ข้อมูลเพิ่มเติมเล็กน้อยเกี่ยวกับสิ่งที่ฉันพยายามจะทำ ออยเลอร์ปัญหา 92 ฉันกำลังสร้างตารางค้นหาเพื่อดูว่าค่าที่คำนวณได้พร้อมคำนวณทั้งหมดแล้วหรือยัง

แก้ไข 2
ประสิทธิภาพสำหรับการค้นหา

แก้ไข 3
ไม่มีค่าที่ถูกรวมเข้ากับค่า ... ดังนั้นชุดจะดีกว่าหรือไม่


1
ประสิทธิภาพในแง่ของอะไร แทรก? ค้นหา? การใช้หน่วยความจำ? คุณกำลังตรวจสอบว่ามีค่าเกินจริงหรือมีเมตาดาต้าที่เกี่ยวข้องหรือไม่?
truppo

ในฐานะที่เป็นบันทึกย่อด้านข้างคุณไม่จำเป็นต้องมีรายการ 10 ล้านรายการหรือ dict สำหรับปัญหาเฉพาะนั้น แต่มีขนาดเล็กกว่ามาก
sfotiadis

คำตอบ:


222

ความเร็ว

การค้นหาในรายการคือ O (n) การค้นหาในพจนานุกรมจะถูกตัดจำหน่าย O (1) โดยคำนึงถึงจำนวนรายการในโครงสร้างข้อมูล หากคุณไม่จำเป็นต้องเชื่อมโยงค่าให้ใช้ชุด

หน่วยความจำ

ทั้งพจนานุกรมและชุดใช้การแปลงแป้นพิมพ์และใช้หน่วยความจำมากกว่าสำหรับการจัดเก็บวัตถุ ตามที่ AM Kuchling ในรหัสที่สวยงามการใช้งานพยายามที่จะทำให้แฮชเต็ม 2/3 ดังนั้นคุณอาจเสียหน่วยความจำบางส่วน

หากคุณไม่ได้เพิ่มรายการใหม่ในทันที (ซึ่งคุณทำตามคำถามที่อัปเดตของคุณ) มันอาจจะคุ้มค่าที่จะเรียงลำดับรายการและใช้การค้นหาแบบไบนารี นี่คือ O (บันทึก n) และมีแนวโน้มว่าจะช้าลงสำหรับสตริงเป็นไปไม่ได้สำหรับวัตถุที่ไม่มีลำดับตามธรรมชาติ


6
ใช่ แต่เป็นการดำเนินการครั้งเดียวหากเนื้อหาไม่เคยเปลี่ยนแปลง การค้นหาแบบไบนารีคือ O (บันทึก n)
Torsten Marek

1
@ John Fouhy: ints ไม่ได้ถูกเก็บไว้ในตาราง hash มีเพียงพอยน์เตอร์เท่านั้นนั่นคือ hou มี 40M สำหรับ ints (ไม่จริง ๆ เมื่อพวกมันมีขนาดเล็ก) และ 60M สำหรับ hash table ฉันยอมรับว่ามันไม่ได้เป็นปัญหามากในปัจจุบันยังคงคุ้มค่าที่จะจำ
Torsten Marek

2
นี่เป็นคำถามเก่า แต่ฉันคิดว่าO (1) ที่ตัดจำหน่ายอาจไม่เป็นจริงสำหรับชุด / dicts ที่มีขนาดใหญ่มาก สถานการณ์กรณีที่เลวร้ายที่สุดตามwiki.python.org/moin/TimeComplexityคือ O (n) ฉันเดาว่ามันขึ้นอยู่กับการใช้งาน hashing ภายในที่จุดใดเวลาเฉลี่ย diverges จาก O (1) และเริ่มบรรจบกับ O (n) คุณสามารถช่วยประสิทธิภาพการค้นหาโดยแบ่งชุดทั่วโลกออกเป็นส่วนย่อย ๆ โดยอิงจากคุณลักษณะที่มองเห็นได้ง่าย (เช่นค่าตัวเลขแรกจากนั้นสองสามและอื่น ๆ ตราบเท่าที่คุณต้องการขนาดที่เหมาะสมที่สุด) .
Nisan.H

3
@TorstenMarek สิ่งนี้ทำให้ฉันสับสน จากหน้านี้การค้นหารายการคือ O (1) และการค้นหา dict คือ O (n) ซึ่งตรงข้ามกับที่คุณพูด ฉันเข้าใจผิดหรือเปล่า?
temporary_user_name

3
@Aerovistae ฉันคิดว่าคุณอ่านข้อมูลผิดในหน้านั้น ภายใต้รายการฉันเห็น O (n) สำหรับ "x in s" (ค้นหา) นอกจากนี้ยังแสดงการค้นหา set และ dict เป็นตัวพิมพ์เล็กโดยเฉลี่ย O (1)
Dennis

45

dict เป็นตารางแฮชดังนั้นจึงรวดเร็วในการค้นหาคีย์ ดังนั้นระหว่าง dict และ list dict จะเร็วขึ้น แต่ถ้าคุณไม่มีค่าที่จะเชื่อมโยงจะเป็นการดียิ่งขึ้นที่จะใช้ชุด มันเป็นตารางแฮชโดยไม่มีส่วน "ตาราง"


แก้ไข: สำหรับคำถามใหม่ของคุณใช่ชุดจะดีกว่า เพียงสร้าง 2 ชุดชุดหนึ่งสำหรับลำดับที่ลงท้ายด้วย 1 และอีกชุดสำหรับลำดับที่ลงท้ายด้วย 89 ฉันแก้ไขปัญหานี้ได้สำเร็จโดยใช้ชุด



31

ฉันทำการเปรียบเทียบและปรากฎว่า dict นั้นเร็วกว่ารายการและตั้งค่าสำหรับชุดข้อมูลขนาดใหญ่โดยใช้ python 2.7.3 บน i7 CPU บน linux:

  • python -mtimeit -s 'd=range(10**7)' '5*10**6 in d'

    10 ลูปที่ดีที่สุดคือ 3: 64.2 msec ต่อลูป

  • python -mtimeit -s 'd=dict.fromkeys(range(10**7))' '5*10**6 in d'

    10,000,000 ลูปที่ดีที่สุดคือ 3: 0.0759 usec ต่อวง

  • python -mtimeit -s 'from sets import Set; d=Set(range(10**7))' '5*10**6 in d'

    1000000 ลูปที่ดีที่สุดคือ 3: 0.262 usec ต่อวง

อย่างที่คุณเห็น dict นั้นเร็วกว่ารายการมากและเร็วกว่าที่ตั้งไว้ประมาณ 3 เท่า ในบางแอพพลิเคชั่นคุณอาจต้องการเลือกเซ็ตเพื่อความงามของมัน และถ้าชุดข้อมูลมีขนาดเล็กมาก (<1,000 องค์ประกอบ) รายการจะทำงานได้ค่อนข้างดี


มันควรจะตรงกันข้ามหรือไม่? รายการ: 10 * 64.2 * 1000 = 642000 usec, dict: 10,000000 * 0.0759 = 759000 usec และชุด: 1000000 * 0.262 = 262000 usec ... ดังนั้นชุดจะเร็วที่สุดตามด้วยรายการและมี dict เป็นตัวสุดท้ายในตัวอย่างของคุณ หรือฉันกำลังพลาดอะไรอยู่?
andzep

1
... แต่คำถามสำหรับฉันที่นี่คือ: เวลานี้สิ่งที่วัดจริง? ไม่ใช่เวลาเข้าถึงสำหรับรายการ dict หรือ set ที่กำหนด แต่อีกมากเวลาและลูปเพื่อสร้างรายการ dict ตั้งค่าและสุดท้ายเพื่อค้นหาและเข้าถึงค่าเดียว ดังนั้นสิ่งนี้เกี่ยวข้องกับคำถามหรือไม่? ... มันน่าสนใจ ...
andzep

8
@ และคุณเข้าใจผิด-sตัวเลือกคือการตั้งค่าtimeitสภาพแวดล้อมนั่นคือจะไม่นับรวมในเวลาทั้งหมด -sตัวเลือกที่มีการเรียกใช้เพียงครั้งเดียว บน Python 3.3 ฉันได้ผลลัพธ์เหล่านี้: gen (range) -> 0.229 usec, list -> 157 msec, dict -> 0.0806 usec, set -> 0.0807 usec การตั้งค่าและประสิทธิภาพ dict เหมือนกัน Dict แต่ต้องใช้เวลาอีกนานในการเริ่มต้นกว่าชุด (รวมเวลา 13.580s วี 11.803s.)
sleblanc

1
ทำไมไม่ใช้ชุด builtin? จริง ๆ แล้วฉันได้รับผลลัพธ์ที่เลวร้ายยิ่งกับเซต Set () มากกว่ากับ builtin set ()
Thomas Guyot-Sionnest

2
@ ThomasGuyot-Sionnest ชุดในตัวได้รับการแนะนำใน python 2.4 ดังนั้นฉันไม่แน่ใจว่าทำไมฉันไม่ใช้มันในโซลูชันที่เสนอ ฉันได้รับประสิทธิภาพที่ดีเมื่อpython -mtimeit -s "d=set(range(10**7))" "5*10**6 in d"ใช้ Python 3.6.0 (10,000,000 ลูป, ดีที่สุดใน 3: 0.0608 usec ต่อลูป), โดยประมาณเหมือนกับมาตรฐาน dict ดังนั้นขอขอบคุณสำหรับความคิดเห็นของคุณ
EriF89

6

คุณต้องการ dict

สำหรับรายการ (ไม่ได้เรียงลำดับ) ใน Python การดำเนินการ "in" ต้องใช้เวลา O (n) --- ไม่ดีเมื่อคุณมีข้อมูลจำนวนมาก ในทางกลับกัน dict เป็นตารางแฮชดังนั้นคุณจึงสามารถคาดหวังเวลาค้นหา O (1)

ดังที่คนอื่น ๆ ได้กล่าวไว้คุณอาจเลือกชุด (dict ชนิดพิเศษ) แทนหากคุณมีคีย์มากกว่าคู่คีย์ / ค่า

ที่เกี่ยวข้อง:

  • Python wiki : ข้อมูลเกี่ยวกับความซับซ้อนของเวลาของการดำเนินการคอนเทนเนอร์ Python
  • SO : เวลาการทำงานของคอนเทนเนอร์ Python และความซับซ้อนของหน่วยความจำ

1
แม้สำหรับรายการที่เรียงลำดับ "ใน" คือ O (n)

2
สำหรับรายการที่เชื่อมโยงใช่ --- แต่ "รายการ" ใน Python เป็นสิ่งที่คนส่วนใหญ่จะเรียกว่าเวกเตอร์ซึ่งให้การเข้าถึงดัชนีใน O (1) และการดำเนินการค้นหาใน O (log n) เมื่อเรียงลำดับ
zweiterlinde

คุณกำลังบอกว่าinโอเปอเรเตอร์ที่ใช้กับรายการที่เรียงลำดับนั้นทำงานได้ดีกว่าเมื่อใช้กับรายการที่ไม่เรียงลำดับ (สำหรับการค้นหาค่าสุ่ม) (ผมไม่คิดว่าพวกเขาจะดำเนินการภายในเป็นพาหะหรือเป็นโหนดในการเชื่อมโยงรายชื่อมีความเกี่ยวข้อง.)
มาร์ติโน

4

ถ้าข้อมูลเป็นชุดที่ไม่ซ้ำกัน () จะมีประสิทธิภาพมากที่สุด แต่ในสอง - Dict (ซึ่งยังต้องมีเอกลักษณ์โอ๊ะโอ :)


ฉันได้รับรู้เมื่อฉันเห็นคำตอบของฉันโพสต์%)
SilentGhost

2
@SilentGhost หากคำตอบไม่ถูกต้องทำไมไม่ลบมัน? เลวร้ายเกินไปสำหรับผู้โหวต แต่มันเกิดขึ้น (ก็เกิดขึ้น )
Jean-François Fabre

3

เนื่องจากชุดการทดสอบใหม่ที่จะแสดง @ EriF89 ยังคงอยู่หลังจากผ่านไปหลายปี:

$ python -m timeit -s "l={k:k for k in xrange(5000)}"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.84 msec per loop
$ python -m timeit -s "l=[k for k in xrange(5000)]"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 573 msec per loop
$ python -m timeit -s "l=tuple([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 587 msec per loop
$ python -m timeit -s "l=set([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.88 msec per loop

ที่นี่เราเปรียบเทียบ a tupleซึ่งทราบกันว่าเร็วกว่าlists(และใช้หน่วยความจำน้อยกว่า) ในบางกรณี ในกรณีของตารางการค้นหาtuplefaired ไม่ดี

ทั้งสองdictและsetทำได้ดีมาก สิ่งนี้จะนำมาซึ่งประเด็นที่น่าสนใจที่ผูกไว้กับ @SilentGhost คำตอบเกี่ยวกับความเป็นเอกลักษณ์: ถ้า OP มีค่า 10M ในชุดข้อมูลและไม่ทราบว่ามีการทำซ้ำในพวกเขาหรือไม่ ด้วยชุดข้อมูลจริงและทดสอบการมีอยู่ในชุด / dict นั้น เป็นไปได้ว่าจุดข้อมูล 10M มีค่าเฉพาะ 10 ค่าซึ่งเป็นพื้นที่ที่เล็กกว่ามากในการค้นหา!

ความผิดพลาดของ SilentGhost เกี่ยวกับ dicts กำลังส่องสว่างเพราะสามารถใช้ dict เพื่อเชื่อมโยงข้อมูลที่ซ้ำกัน (เป็นค่า) ลงในชุด (คีย์) ที่ไม่ซ้ำซ้อนดังนั้นจึงเก็บวัตถุข้อมูลหนึ่งไว้เพื่อเก็บข้อมูลทั้งหมด แต่ยังคงเร็วเหมือนตารางค้นหา ตัวอย่างเช่นคีย์ dict อาจเป็นค่าที่ถูกค้นหาและค่าอาจเป็นรายการดัชนีในรายการจินตภาพที่ค่านั้นเกิดขึ้น

ตัวอย่างเช่นหากรายการแหล่งข้อมูลที่จะค้นหาl=[1,2,3,1,2,1,4]ก็สามารถปรับให้เหมาะสมสำหรับการค้นหาและหน่วยความจำโดยแทนที่ด้วย dict นี้:

>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> l=[1,2,3,1,2,1,4]
>>> for i, e in enumerate(l):
...     d[e].append(i)
>>> d
defaultdict(<class 'list'>, {1: [0, 3, 5], 2: [1, 4], 3: [2], 4: [6]})

ด้วย dict นี้คุณสามารถรู้:

  1. ถ้าค่าอยู่ในชุดข้อมูลดั้งเดิม (เช่น2 in dส่งคืนTrue)
  2. โดยที่ค่านั้นอยู่ในชุดข้อมูลดั้งเดิม (เช่นd[2]ส่งคืนรายการดัชนีที่พบข้อมูลในรายการข้อมูลดั้งเดิม[1, 4])

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

0

คุณไม่จำเป็นต้องเก็บค่า 10 ล้านไว้ในตารางดังนั้นมันจึงไม่ใช่เรื่องใหญ่เลย

คำแนะนำ: ลองคิดดูว่าผลลัพธ์ของคุณจะมีขนาดใหญ่เพียงใดหลังจากการรวมกำลังสองเป็นครั้งแรก ผลลัพธ์ที่ใหญ่ที่สุดที่เป็นไปได้จะน้อยกว่า 10 ล้าน ...

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