जब सबसे अधिक किया जाता है तो इफ-एलिफ-इस्तिफा-बयान का सबसे कुशल तरीका?


99

मुझे एक if-elif-elif-else स्टेटमेंट मिला है जिसमें 99% समय, बाकी स्टेटमेंट निष्पादित किया गया है:

if something == 'this':
    doThis()
elif something == 'that':
    doThat()
elif something == 'there':
    doThere()
else:
    doThisMostOfTheTime()

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

क्या किसी को पता है कि क्या और कैसे यह अधिक कुशलता से किया जा सकता है या क्या यह बस इसे करने का सबसे अच्छा तरीका है?


क्या आप sortअपने द्वारा चलाए जा रहे सामानों को अपने पास रख सकते हैं / कर सकते हैं ... श्रृंखला पर, जैसे कि सभी तत्व जो एक स्थिति के लिए मेल खाते हैं, वे एक छोर पर हैं, और बाकी सभी दूसरे पर हैं? यदि हां, तो आप देख सकते हैं कि यह तेज / अधिक सुरुचिपूर्ण है या नहीं। लेकिन याद रखें, यदि कोई प्रदर्शन समस्या नहीं है, तो अनुकूलन के बारे में चिंता करना बहुत जल्दी है।
पटाशु


4
क्या ऐसा कुछ है जो तीन विशेष मामलों में सामान्य है? उदाहरण के लिए, आप क्लॉज if not something.startswith("th"): doThisMostOfTheTime()में एक और तुलना कर सकते हैं else
टिम पीटरज़

3
@ kramer65 अगर यह if / elif की इतनी लंबी श्रृंखला है ... यह धीमा हो सकता है, लेकिन सुनिश्चित करें कि वास्तव में अपना कोड प्रोफाइल करें और जो भी समय सबसे अधिक लगता है उसे अनुकूलित करके शुरू करें।
जॉर्गेका

1
क्या इन तुलनाओं को केवल एक बार प्रति मान के आधार somethingपर प्रदर्शन किया जाता है, या समान मूल्य पर कई बार तुलना की जाती है?
क्रिस पिटमैन

जवाबों:


99

कोड...

options.get(something, doThisMostOfTheTime)()

... ऐसा लगता है कि यह तेज़ होना चाहिए, लेकिन यह वास्तव में धीमी है if... elif... elseनिर्माण, क्योंकि इसे एक फ़ंक्शन को कॉल करना है, जो एक तंग लूप में एक महत्वपूर्ण प्रदर्शन ओवरहेड हो सकता है।

इन उदाहरणों पर विचार करें ...

1.py

something = 'something'

for i in xrange(1000000):
    if something == 'this':
        the_thing = 1
    elif something == 'that':
        the_thing = 2
    elif something == 'there':
        the_thing = 3
    else:
        the_thing = 4

2.py

something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}

for i in xrange(1000000):
    the_thing = options.get(something, 4)

3.py

something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}

for i in xrange(1000000):
    if something in options:
        the_thing = options[something]
    else:
        the_thing = 4

4.py

from collections import defaultdict

something = 'something'
options = defaultdict(lambda: 4, {'this': 1, 'that': 2, 'there': 3})

for i in xrange(1000000):
    the_thing = options[something]

... और CPU समय का उपयोग करने पर उनकी मात्रा पर ध्यान दें ...

1.py: 160ms
2.py: 170ms
3.py: 110ms
4.py: 100ms

... उपयोगकर्ता समय का उपयोग करके time(1)

विकल्प # 4 में हर अलग-अलग कुंजी मिस के लिए एक नया आइटम जोड़ने का अतिरिक्त मेमोरी ओवरहेड है, इसलिए यदि आप अलग-अलग कुंजी मिस की अनबाउंड संख्या की उम्मीद कर रहे हैं, तो मैं विकल्प # 3 के साथ जाऊंगा, जो अभी भी एक महत्वपूर्ण सुधार है मूल निर्माण।


2
अजगर का एक स्विच स्टेटमेंट है?
नथन हैयफील्ड

उह ... अच्छी तरह से अभी तक केवल एक चीज है जो मैंने अजगर के बारे में सुना है कि मुझे परवाह नहीं है ... लगता है कि कुछ होने के लिए बाध्य था
नथान हाईफील्ड

2
-1 आप कहते हैं कि एक dictधीमी गति का उपयोग करना , लेकिन फिर आपकी टाइमिंग वास्तव में दिखाती है कि यह दूसरा सबसे तेज़ विकल्प है।
मार्सिन

11
@ मारकिन मैं कह रहा हूं कि dict.get()यह धीमी है, जो 2.pyसबसे धीमी है।
आया

रिकॉर्ड के लिए, तीन और चार नाटकीय रूप से एक प्रयास / निर्माण को छोड़कर मुख्य त्रुटि को पकड़ने से भी तेज हैं।
जेफ

78

मैं एक शब्दकोश बनाऊंगा:

options = {'this': doThis,'that' :doThat, 'there':doThere}

अब बस का उपयोग करें:

options.get(something, doThisMostOfTheTime)()

यदि हुकुम somethingमें नहीं पाया जाता है optionsतो dict.getडिफ़ॉल्ट मान लौटा देगाdoThisMostOfTheTime

कुछ समय की तुलना:

स्क्रिप्ट:

from random import shuffle
def doThis():pass
def doThat():pass
def doThere():pass
def doSomethingElse():pass
options = {'this':doThis, 'that':doThat, 'there':doThere}
lis = range(10**4) + options.keys()*100
shuffle(lis)

def get():
    for x in lis:
        options.get(x, doSomethingElse)()

def key_in_dic():
    for x in lis:
        if x in options:
            options[x]()
        else:
            doSomethingElse()

def if_else():
    for x in lis:
        if x == 'this':
            doThis()
        elif x == 'that':
            doThat()
        elif x == 'there':
            doThere()
        else:
            doSomethingElse()

परिणाम:

>>> from so import *
>>> %timeit get()
100 loops, best of 3: 5.06 ms per loop
>>> %timeit key_in_dic()
100 loops, best of 3: 3.55 ms per loop
>>> %timeit if_else()
100 loops, best of 3: 6.42 ms per loop

के लिए 10**5गैर-मौजूद कुंजियों और 100 वैध कुंजी ::

>>> %timeit get()
10 loops, best of 3: 84.4 ms per loop
>>> %timeit key_in_dic()
10 loops, best of 3: 50.4 ms per loop
>>> %timeit if_else()
10 loops, best of 3: 104 ms per loop

तो, कुंजी का उपयोग करने के लिए एक सामान्य शब्दकोश की जाँच के लिए key in optionsयहाँ सबसे कुशल तरीका है:

if key in options:
   options[key]()
else:
   doSomethingElse()

options = collections.defaultdict(lambda: doThisMostOfTheTime, {'this': doThis,'that' :doThat, 'there':doThere}); options[something]()थोड़ा और अधिक कुशल है।
आया १

शांत विचार, लेकिन पठनीय के रूप में नहीं। इसके अलावा, आप शायद optionsइसे फिर से बनाने से बचने के लिए तानाशाही को अलग करना चाहते हैं , जिससे तर्क का हिस्सा (लेकिन सभी नहीं) उपयोग के बिंदु से बहुत दूर है। फिर भी, अच्छी चाल!
एंडर्स जोहानसन

7
क्या आप जानते हैं कि क्या यह अधिक कुशल है? मेरा अनुमान है कि यह धीमा है क्योंकि यह साधारण सशर्त जांच या तीन के बजाय हैश लुकअप कर रहा है। प्रश्न कोड की कॉम्पैक्टनेस के बजाय दक्षता के बारे में है।
ब्रायन ओकले

2
@BryanOakley मैंने कुछ समय तुलना की है।
अश्विनी चौधरी

1
वास्तव में यह करने के लिए और अधिक कुशल होना चाहिए try: options[key]() except KeyError: doSomeThingElse()(जब से if key in options: options[key]()आप दो बार के लिए शब्दकोश खोज रहे हैंkey
हार्ड '' 15:16

8

आप pypy का उपयोग करने में सक्षम हैं?

अपना मूल कोड रखते हुए लेकिन इसे pypy पर चलाने से मेरे लिए 50x स्पीड-अप हो जाता है।

CPython:

matt$ python
Python 2.6.8 (unknown, Nov 26 2012, 10:25:03)
[GCC 4.2.1 Compatible Apple Clang 3.0 (tags/Apple/clang-211.12)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from timeit import timeit
>>> timeit("""
... if something == 'this': pass
... elif something == 'that': pass
... elif something == 'there': pass
... else: pass
... """, "something='foo'", number=10000000)
1.728302001953125

PyPy:

matt$ pypy
Python 2.7.3 (daf4a1b651e0, Dec 07 2012, 23:00:16)
[PyPy 2.0.0-beta1 with GCC 4.2.1] on darwin
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``a 10th of forever is 1h45''
>>>>
>>>> from timeit import timeit
>>>> timeit("""
.... if something == 'this': pass
.... elif something == 'that': pass
.... elif something == 'there': pass
.... else: pass
.... """, "something='foo'", number=10000000)
0.03306388854980469

हाय फोज। पारितोषिक के लिए धन्यवाद। वास्तव में मैं पहले से ही pypy (इसे प्यार करता हूँ) का उपयोग कर रहा हूं, लेकिन मुझे अभी भी गति में सुधार की आवश्यकता है .. :)
kramer65

ओह अच्छा! इससे पहले मैंने 'यह', 'वह' और 'वहाँ' के लिए एक हैश की प्री-कंप्यूटिंग करने की कोशिश की - और फिर तार के बजाय हैश कोड की तुलना करना। यह मूल की तुलना में दो गुना धीमा है, इसलिए ऐसा लगता है कि स्ट्रिंग तुलना पहले से ही आंतरिक रूप से बहुत अच्छी तरह से अनुकूलित है।
फोज

3

यहाँ एक उदाहरण है अगर एक शब्दकोश में अनुवादित गतिशील स्थितियों के साथ।

selector = {lambda d: datetime(2014, 12, 31) >= d : 'before2015',
            lambda d: datetime(2015, 1, 1) <= d < datetime(2016, 1, 1): 'year2015',
            lambda d: datetime(2016, 1, 1) <= d < datetime(2016, 12, 31): 'year2016'}

def select_by_date(date, selector=selector):
    selected = [selector[x] for x in selector if x(date)] or ['after2016']
    return selected[0]

यह एक तरीका है, लेकिन ऐसा करने के लिए सबसे अधिक पाइथोनिक तरीका नहीं हो सकता है क्योंकि कम पठनीय है जिनके लिए पाइथन में धाराप्रवाह नहीं है।


0

लोग execसुरक्षा कारणों के बारे में चेतावनी देते हैं , लेकिन यह इसके लिए एक आदर्श मामला है।
यह एक आसान राज्य मशीन है।

Codes = {}
Codes [0] = compile('blah blah 0; nextcode = 1')
Codes [1] = compile('blah blah 1; nextcode = 2')
Codes [2] = compile('blah blah 2; nextcode = 0')

nextcode = 0
While True:
    exec(Codes[nextcode])
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.