पायथन में देवताओं को कैसे लागू किया जाता है, और वे सूचियों से भी बदतर कब हैं?


85

मैंने हाल ही में जांच में पाया है कि मेरे कोड को अधिक कुशल बनाने के लिए पायथन में विभिन्न डेटा संरचनाएं कैसे कार्यान्वित की जाती हैं। यह जांचने के लिए कि सूचियां और देवता कैसे काम करते हैं, मैंने पाया कि जब मैं शिफ्ट करना चाहता हूं और ओ (एन) से सूचियों में हे (1) के लिए देवताओं में सूचियों को कम करना चाहता हूं तो लाभ प्राप्त कर सकते हैं (सूचियों को निर्धारित लंबाई सरणियों के रूप में लागू किया जा रहा है) पूरी तरह से नकल करने के लिए हर बार कुछ सामने की तरफ डाला जाता है, आदि ...)। मैं यह नहीं जान सकता कि एक डॉक कैसे लागू किया जाता है, और इसकी डाउनसाइड्स बनाम सूचियों की बारीकियों की बारीकियां हैं। क्या कोई मुझे इन दो सवालों पर बता सकता है?

जवाबों:


74

https://github.com/python/cpython/blob/v3.8.1/Modules/_collectionsmodule.c

ए नोड्स dequeobjectकी दोहरी-लिंक की गई सूची से बना है block

तो हां, एक dequeउत्तर के रूप में एक (दोगुना) लिंक्ड सूची है जो एक अन्य उत्तर का सुझाव देता है।

विस्तृत करना: इसका मतलब यह है कि रद्दीकरण सहित यादृच्छिक-अभिगम और निश्चित लंबाई के संचालन के लिए पायथन सूचियां बहुत बेहतर हैं, जबकि अनुक्रमणिका के साथ चीजों को धकेलने और बंद करने के लिए देवता बहुत अधिक उपयोगी हैं, लेकिन अनुक्रमण (लेकिन स्लाइसिंग, दिलचस्प रूप से नहीं) किया जा रहा है। सूचियों की तुलना में संभव लेकिन धीमा।


3
ध्यान दें कि यदि आपको केवल एक छोर (स्टैक) पर संलग्न और पॉप करने की आवश्यकता है, तो सूचियों को बेहतर प्रदर्शन करना चाहिए .append()और .pop()उन्हें O (1) (रियलकास्ट और नकल करना) होता है, लेकिन बहुत कम और केवल तब तक जब तक आप स्टैक को आकार नहीं देते। कभी भी हो)।

@delnan: लेकिन यदि आप एक कतार चाहते हैं, तो dequeनिश्चित रूप से जाने के लिए कुछ सही तरीका है।
JAB

@ डेलन: आप कैसे आंकते हैं? .append () और .pop () सूचियों पर O (1) amortized हैं, लेकिन क्या वे वास्तविक O (1) देवताओं और प्रतियों में आवश्यक नहीं हैं।
एली

1
@ एलि: सूचियाँ थ्रेड-सेफ़्टी से निपटती नहीं हैं (ठीक है, यह उनके इंटर्न में वायर्ड नहीं है) और कई स्मार्ट लोगों द्वारा लंबे समय से ट्यून किया गया है।

3
@ डायलेन: वास्तव में, सीपीथॉन में वास्तव dequeमें थ्रेड सुरक्षा को संभाल नहीं है; वे जीआईएल से अपने संचालन को परमाणु बनाने में लाभ उठाते हैं (और वास्तव में, appendऔर उसी सुरक्षा popके अंत से list)। व्यवहार में, यदि आप केवल एक स्टैक का उपयोग कर रहे हैं, तो दोनों listऔर dequeप्रभावी रूप से सीपीथॉन में समान प्रदर्शन करते हैं; ब्लॉक आवंटन अधिक लगातार होते हैं deque(लेकिन सादे लिंक की गई सूची अक्सर नहीं होती; आप केवल हर बार जब आप CPython कार्यान्वयन में 64 सदस्य सीमा को पार कर जाते हैं / आवंटित करते हैं) को समाप्त कर देंगे, लेकिन बड़ी रुक-रुक कर होने वाली प्रतियों की कमी क्षतिपूर्ति करती है।
शैडो रेंजर

51

जांच करें collections.deque। डॉक्स से:

प्रत्येक पक्ष में लगभग एक ही O (1) प्रदर्शन के साथ, Deque का समर्थन थ्रेड-सेफ, मेमोरी कुशल एपेंड और पॉप के दोनों ओर से होता है।

हालांकि सूची ऑब्जेक्ट समान संचालन का समर्थन करते हैं, वे तेजी से निर्धारित लंबाई के संचालन के लिए अनुकूलित होते हैं और पॉप (0) और इंसर्ट (0, v) संचालन के लिए मेमोरी मूवमेंट की लागत (एन) ओ के लिए होती है जो अंतर्निहित डेटा प्रतिनिधित्व के आकार और स्थिति दोनों को बदलते हैं। ।

जैसा कि यह कहता है, पॉप (0) या इंसर्ट (0, v) का उपयोग करते हुए सूची वस्तुओं के साथ बड़े दंड का उपयोग करते हैं। आप एक पर स्लाइस / इंडेक्स ऑपरेशंस का उपयोग नहीं dequeकर सकते हैं, लेकिन आप उपयोग कर सकते हैं popleft/ appendleft, जो ऑपरेशन के dequeलिए अनुकूलित है। यहाँ यह प्रदर्शित करने के लिए एक सरल बेंचमार्क है:

import time
from collections import deque

num = 100000

def append(c):
    for i in range(num):
        c.append(i)

def appendleft(c):
    if isinstance(c, deque):
        for i in range(num):
            c.appendleft(i)
    else:
        for i in range(num):
            c.insert(0, i)
def pop(c):
    for i in range(num):
        c.pop()

def popleft(c):
    if isinstance(c, deque):
        for i in range(num):
            c.popleft()
    else:
        for i in range(num):
            c.pop(0)

for container in [deque, list]:
    for operation in [append, appendleft, pop, popleft]:
        c = container(range(num))
        start = time.time()
        operation(c)
        elapsed = time.time() - start
        print "Completed %s/%s in %.2f seconds: %.1f ops/sec" % (container.__name__, operation.__name__, elapsed, num / elapsed)

मेरी मशीन पर परिणाम:

Completed deque/append in 0.02 seconds: 5582877.2 ops/sec
Completed deque/appendleft in 0.02 seconds: 6406549.7 ops/sec
Completed deque/pop in 0.01 seconds: 7146417.7 ops/sec
Completed deque/popleft in 0.01 seconds: 7271174.0 ops/sec
Completed list/append in 0.01 seconds: 6761407.6 ops/sec
Completed list/appendleft in 16.55 seconds: 6042.7 ops/sec
Completed list/pop in 0.02 seconds: 4394057.9 ops/sec
Completed list/popleft in 3.23 seconds: 30983.3 ops/sec

3
हुह, बस आपने देखा कि आप अनुक्रमण करते हुए भी देवताओं के साथ स्लाइस नहीं कर सकते। दिलचस्प।
JAB

1
+1 टाइमिंग के लिए - दिलचस्प यह है कि listएपेंडेस की तुलना में dequeऐप्पल थोड़ा तेज हैं ।
प्रेषित

1
@ ज़ीक़े: यह काफी अजीब है, यह देखते हुए कि किसी विशिष्ट वस्तु के सूचकांक की खोज करने के लिए आम तौर पर वैसे भी संग्रह की वस्तुओं पर पुनरावृत्ति की आवश्यकता होगी, और यह कि आप एक dequeजैसे ही कर सकते हैं list
JAB

1
@ प्रमाण: निश्चित रूप से, list popएस धीमी की तुलना में कम थे deque(संभावना है listकि यह सिकुड़ते हुए रुक-रुक कर की उच्च लागत के कारण , जहां dequeबस खाली सूची या छोटी वस्तु पूल में वापस जा रही है), इसलिए जब कोई डेटा संरचना का चयन करें एक स्टैक (उर्फ LIFO कतार), खाली-से-पूर्ण-से-खाली प्रदर्शन थोड़ा बेहतर दिखता है deque(औसत 6365K ऑप्स / सेकंड के लिए append/ pop, बनाम list5578K ऑप्स / सेकंड)। मुझे संदेह dequeहै कि वास्तविक दुनिया में यह थोड़ा बेहतर होगा, क्योंकि dequeपहली बार फ्रीलास्ट बढ़ने का मतलब सिकुड़ने के बाद बढ़ने की तुलना में अधिक महंगा है।
शैडो रेंजर

1
मेरे स्वतंत्र संदर्भ को स्पष्ट करने के लिए: सीपीथॉन dequeवास्तव freeमें 16 ब्लॉक (मॉड्यूल-वाइड, प्रति नहीं deque) तक होगा, बजाय उन्हें पुन: उपयोग के लिए उपलब्ध ब्लॉकों के सस्ते सरणी में डाल देगा। इसलिए जब dequeपहली बार बढ़ रहा है , तो इसे हमेशा नए ब्लॉकों को malloc( appendअधिक महंगा बनाने ) से खींचना पड़ता है , लेकिन अगर यह लगातार थोड़ा विस्तार कर रहा है, तो थोड़ा और आगे और पीछे सिकुड़ रहा है, इसमें आमतौर पर शामिल नहीं होगा malloc/ freeपर जब तक लंबाई 1024 तत्वों (नि: शुल्क सूची पर 16 ब्लॉक, प्रति ब्लॉक 64 स्लॉट) की सीमा के भीतर लगभग लंबे समय तक रहती है।
शैडो रेंजर

17

dequeवस्तुओं के लिए प्रलेखन प्रविष्टि में से अधिकांश को आप जो जानना चाहते हैं, मुझे संदेह है। उल्लेखनीय उद्धरण:

प्रत्येक पक्ष में लगभग एक ही O (1) प्रदर्शन के साथ, Deque का समर्थन थ्रेड-सेफ, मेमोरी कुशल एपेंड और पॉप के दोनों ओर से होता है।

परंतु...

अनुक्रमित पहुंच O (1) दोनों सिरों पर है लेकिन बीच में O (n) तक धीमी हो जाती है। तीव्र यादृच्छिक अभिगम के लिए, इसके बजाय सूचियों का उपयोग करें।

मुझे यह बताने के लिए स्रोत पर एक नज़र डालनी होगी कि क्या कार्यान्वयन एक लिंक की गई सूची है या कुछ और, लेकिन यह मुझे लगता है जैसे कि dequeएक दोहरी-लिंक की गई सूची के रूप में लगभग समान विशेषताएं हैं।


11

अन्य सभी उपयोगी उत्तरों के अलावा, यहां पायथन सूचियों, देवताओं, सेटों और शब्दकोशों पर विभिन्न कार्यों के समय की जटिलता (बिग-ओह) की तुलना में कुछ और जानकारी दी गई है। इससे किसी विशेष समस्या के लिए सही डेटा संरचना का चयन करने में मदद मिलेगी।


-3

हालांकि, मुझे बिल्कुल यकीन नहीं है कि पायथन ने इसे कैसे लागू किया है, यहां मैंने केवल सरणियों का उपयोग करके क्युओं का कार्यान्वयन लिखा है। इसमें पाइथन की क्यू के समान जटिलता है।

class ArrayQueue:
""" Implements a queue data structure """

def __init__(self, capacity):
    """ Initialize the queue """

    self.data = [None] * capacity
    self.size = 0
    self.front = 0

def __len__(self):
    """ return the length of the queue """

    return self.size

def isEmpty(self):
    """ return True if the queue is Empty """

    return self.data == 0

def printQueue(self):
    """ Prints the queue """

    print self.data 

def first(self):
    """ Return the first element of the queue """

    if self.isEmpty():
        raise Empty("Queue is empty")
    else:
        return self.data[0]

def enqueue(self, e):
    """ Enqueues the element e in the queue """

    if self.size == len(self.data):
        self.resize(2 * len(self.data))
    avail = (self.front + self.size) % len(self.data) 
    self.data[avail] = e
    self.size += 1

def resize(self, num):
    """ Resize the queue """

    old = self.data
    self.data = [None] * num
    walk = self.front
    for k in range(self.size):
        self.data[k] = old[walk]
        walk = (1+walk)%len(old)
    self.front = 0

def dequeue(self):
    """ Removes and returns an element from the queue """

    if self.isEmpty():
        raise Empty("Queue is empty")
    answer = self.data[self.front]
    self.data[self.front] = None 
    self.front = (self.front + 1) % len(self.data)
    self.size -= 1
    return answer

class Empty(Exception):
""" Implements a new exception to be used when stacks are empty """

pass

और यहाँ आप कुछ कोड के साथ इसका परीक्षण कर सकते हैं:

def main():
""" Tests the queue """ 

Q = ArrayQueue(5)
for i in range(10):
    Q.enqueue(i)
Q.printQueue()    
for i in range(10):
    Q.dequeue()
Q.printQueue()    


if __name__ == '__main__':
    main()

यह सी कार्यान्वयन के रूप में तेजी से काम नहीं करेगा, लेकिन यह एक ही तर्क का उपयोग करता है।


1
Don't.reinvent.the.wheel!
अभिजीत सरकार

सवाल यह था कि पायथन की चाल को कैसे लागू किया जाए। यह वैकल्पिक कार्यान्वयन के लिए नहीं कह रहा था।
गीनो मेम्पिन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.