سؤال

لقد نشرت مؤخرًا سؤالًا باستخدام وظيفة Lambda وفي الرد الذي ذكره شخص ما أن Lambda يخرج عن صالح ، لاستخدام شمولية قائمة بدلاً من ذلك. أنا جديد نسبيا على بيثون. أجرت اختبارًا بسيطًا:

import time

S=[x for x in range(1000000)]
T=[y**2 for y in range(300)]
#
#
time1 = time.time()
N=[x for x in S for y in T if x==y]
time2 = time.time()
print 'time diff [x for x in S for y in T if x==y]=', time2-time1
#print N
#
#
time1 = time.time()
N=filter(lambda x:x in S,T)
time2 = time.time()
print 'time diff filter(lambda x:x in S,T)=', time2-time1
#print N
#
#
#http://snipt.net/voyeg3r/python-intersect-lists/
time1 = time.time()
N = [val for val in S if val in T]
time2 = time.time()
print 'time diff [val for val in S if val in T]=', time2-time1
#print N
#
#
time1 = time.time()
N= list(set(S) & set(T))
time2 = time.time()
print 'time diff list(set(S) & set(T))=', time2-time1
#print N  #the results will be unordered as compared to the other ways!!!
#
#
time1 = time.time()
N=[]
for x in S:
    for y in T:
        if x==y:
            N.append(x)
time2 = time.time()
print 'time diff using traditional for loop', time2-time1
#print N

لقد قاموا جميعًا بطباعة نفس N ، لذا علقت على ذلك بطباعة STMT (باستثناء الطريقة الأخيرة غير المرتبة) ، لكن الاختلافات الزمنية الناتجة كانت مثيرة للاهتمام على الاختبارات المتكررة كما هو موضح في هذا المثال:

time diff [x for x in S for y in T if x==y]= 54.875
time diff filter(lambda x:x in S,T)= 0.391000032425
time diff [val for val in S if val in T]= 12.6089999676
time diff list(set(S) & set(T))= 0.125
time diff using traditional for loop 54.7970001698

لذا ، بينما أجد أن القائمة شمولية على كل حال أسهل في القراءة ، يبدو أن هناك بعض مشكلات الأداء على الأقل في هذا المثال.

لذلك ، سؤالان:

  1. لماذا يتم دفع Lambda وما إلى ذلك؟

  2. لطرق فهم القائمة ، هل هناك تطبيق أكثر كفاءة وكيف تعرف أنه أكثر كفاءة دون اختبار؟ أعني ، كان من المفترض أن يكون Lambda/Map/Filter أقل كفاءة بسبب مكالمات الوظائف الإضافية ، لكن يبدو أنه أكثر كفاءة.

بول

هل كانت مفيدة؟

المحلول

اختباراتك تقوم بأشياء مختلفة للغاية. مع وجود عناصر 1M و T 300:

[x for x in S for y in T if x==y]= 54.875

هذا الخيار يقوم بمقارنات 300 متر للمساواة.

 

filter(lambda x:x in S,T)= 0.391000032425

هذا الخيار يقوم 300 عملية بحث خطي من خلال S.

 

[val for val in S if val in T]= 12.6089999676

هذا الخيار يقوم بالبحث الخطية 1M من خلال T.

 

list(set(S) & set(T))= 0.125

هذا الخيار يقوم بإنشاءات مجموعة ومجموعة واحدة تقاطع.


ترتبط الاختلافات في الأداء بين هذه الخيارات أكثر بكثير من الخوارزميات التي يستخدمها كل منها ، بدلاً من أي فرق بين شمولية القائمة و lambda.

نصائح أخرى

عندما أقوم بإصلاح الرمز الخاص بك بحيث يكون فهم القائمة والدعوة إلى filter في الواقع يقومون بنفس العمل الذي تغير الأشياء كثيرًا:

import time

S=[x for x in range(1000000)]
T=[y**2 for y in range(300)]
#
#
time1 = time.time()
N=[x for x in T if x in S]
time2 = time.time()
print 'time diff [x for x in T if x in S]=', time2-time1
#print N
#
#
time1 = time.time()
N=filter(lambda x:x in S,T)
time2 = time.time()
print 'time diff filter(lambda x:x in S,T)=', time2-time1
#print N

ثم يكون الإخراج أشبه:

time diff [x for x in T if x in S]= 0.414485931396
time diff filter(lambda x:x in S,T)= 0.466315984726

لذلك فإن فهم القائمة لديه وقت قريب عمومًا وعادة ما يكون أقل من تعبير Lambda.

السبب في أن تعبيرات Lambda يتم التخلص منها في أن الكثير من الناس يعتقدون أنها أقل قابلية للقراءة بكثير من اختصارات القائمة. أنا أوافق على مضض.

س: لماذا يتم دفع Lambda وما إلى ذلك؟

ج: تعتبر القائمة الشاملة وتعبيرات المولدات عمومًا مزيجًا رائعًا من القوة وقابلية القراءة. نمط برمجة الوظيفية الخالصة حيث تستخدم map(), reduce(), ، و filter() مع الوظائف (في كثير من الأحيان lambda وظائف) تعتبر غير واضحة. أيضًا ، أضافت Python وظائف مدمجة تتعامل بشكل جيد مع جميع الاستخدامات الرئيسية reduce().

لنفترض أنك تريد تلخيص قائمة. فيما يلي طريقتان للقيام بذلك.

lst = range(10)
print reduce(lambda x, y: x + y, lst)

print sum(lst)

اشترك لي كمشجع sum() وليس من محبي reduce() لحل هذه المشكلة. هذه مشكلة أخرى ، مماثلة:

lst = range(10)
print reduce(lambda x, y: bool(x or y), lst)

print any(lst)

ليس فقط any() الحل أسهل في الفهم ، لكنه أيضًا أسرع بكثير ؛ يحتوي على تقييم لدائرة القصر ، بحيث يتوقف عن التقييم بمجرد العثور على أي قيمة حقيقية. ال reduce() يجب أن يربح من خلال القائمة بأكملها. سيكون فرق الأداء هذا صارخًا إذا كانت القائمة طويلة مليون عنصر ، وتم تقييم العنصر الأول. على فكرة، any() تمت إضافة في بيثون 2.5 ؛ إذا لم يكن لديك ذلك ، فإليك نسخة للإصدارات القديمة من Python:

def any(iterable):
    for x in iterable:
        if x:
            return True
    return False

لنفترض أنك تريد إنشاء قائمة من المربعات من الأرقام الزوجية من بعض القائمة.

lst = range(10)
print map(lambda x: x**2, filter(lambda x: x % 2 == 0, lst))

print [x**2 for x in lst if x % 2 == 0]

لنفترض الآن أنك تريد أن تلخص قائمة المربعات هذه.

lst = range(10)
print sum(map(lambda x: x**2, filter(lambda x: x % 2 == 0, lst)))

# list comprehension version of the above
print sum([x**2 for x in lst if x % 2 == 0])

# generator expression version; note the lack of '[' and ']'
print sum(x**2 for x in lst if x % 2 == 0)

تعبير المولد في الواقع يرجع فقط كائنًا ثابتًا. sum() يأخذ الأمر غير قابلة للسحب ويسحب القيم منه ، واحدًا تلو الآخر ، يلخص كما يذهب ، حتى يتم استهلاك جميع القيم. هذه هي الطريقة الأكثر فعالية التي يمكنك حل هذه المشكلة في بيثون. في المقابل ، map() الحل ، والحل المكافئ مع فهم قائمة داخل المكالمة إلى sum(), ، يجب أولاً بناء قائمة ؛ ثم يتم تمرير هذه القائمة إلى sum(), ، تستخدم مرة واحدة ، وتجاهل. الوقت لإنشاء القائمة ثم حذفها مرة أخرى يضيع للتو. (تحرير: ولاحظ أن الإصدار مع كليهما map و filter يجب بناء اثنين قوائم ، واحدة بنيت بواسطة filter وواحد بنيت بواسطة map; على حد سواء

عن طريق التأليف map(), filter(), ، و reduce() بالاشتراك مع lambda وظائف ، يمكنك القيام بالعديد من الأشياء القوية. لكن Python لديه طرق اصطلاح لحل نفس المشكلات التي هي في وقت واحد أفضل أداء وأسهل للقراءة والفهم.

لقد أشار الكثير من الناس بالفعل إلى أنك تقارن التفاح بالبرتقال ، وما إلى ذلك ، إلخ قد يكون:

$ python -mtimeit -s'L=range(1000)' 'map(lambda x: x+1, L)'
1000 loops, best of 3: 328 usec per loop
$ python -mtimeit -s'L=range(1000)' '[x+1 for x in L]'
10000 loops, best of 3: 129 usec per loop

هنا ، يمكنك رؤية تكلفة Lambda بشكل حاد للغاية - حوالي 200 ميكروثانية ، والتي في حالة عملية بسيطة بما فيه الكفاية مثل هذا واحد يغمر العملية نفسها.

الأرقام متشابهة جدًا مع المرشح بالطبع ، لأن المشكلة هي ليس تصفية أو خريطة ، ولكن lambda نفسها:

$ python -mtimeit -s'L=range(1000)' '[x for x in L if not x%7]'
10000 loops, best of 3: 162 usec per loop
$ python -mtimeit -s'L=range(1000)' 'filter(lambda x: not x%7, L)'
1000 loops, best of 3: 334 usec per loop

لا شك أن حقيقة أن Lambda يمكن أن يكون أقل وضوحًا ، أو علاقتها الغريبة مع Sparta (كان لدى Spartans Lambda ، لـ "Lakedaimon" ، مرسومة على دروعهم-وهذا يشير إلى أن Lambda هو دكتاتوري ودموي على الأقل ؛-) على الأقل كما لا علاقة لها بالخروج ببطء عن الموضة ، حيث تكاليف أدائها. لكن الأخير حقيقي جدا.

بادئ ذي بدء ، اختبار مثل هذا:

import timeit

S=[x for x in range(10000)]
T=[y**2 for y in range(30)]

print "v1", timeit.Timer('[x for x in S for y in T if x==y]',
             'from __main__ import S,T').timeit(100)
print "v2", timeit.Timer('filter(lambda x:x in S,T)',
             'from __main__ import S,T').timeit(100)
print "v3", timeit.Timer('[val for val in T if val in S]',
             'from __main__ import S,T').timeit(100)
print "v4", timeit.Timer('list(set(S) & set(T))',
             'from __main__ import S,T').timeit(100)

وبشكل أساسي تقوم بأشياء مختلفة في كل مرة تختبر فيها. عندما تقوم بإعادة كتابة القائمة على سبيل المثال على سبيل المثال

[val for val in T if val in S]

سيكون الأداء على قدم المساواة مع بنية "Lambda/Filter".

مجموعات هي الحل الصحيح لهذا. ومع ذلك ، حاول تبديل S و T وانظر إلى المدة التي يستغرقها!

filter(lambda x:x in T,S)

$ python -m timeit -s'S=[x for x in range(1000000)];T=[y**2 for y in range(300)]' 'filter(lambda x:x in S,T)'
10 loops, best of 3: 485 msec per loop
$ python -m timeit -r1 -n1 -s'S=[x for x in range(1000000)];T=[y**2 for y in range(300)]' 'filter(lambda x:x in T,S)'
1 loops, best of 1: 19.6 sec per loop

لذلك ترى أن ترتيب S و T مهمان للغاية

تغيير ترتيب فهم القائمة لمطابقة الفلتر

$ python -m timeit  -s'S=[x for x in range(1000000)];T=[y**2 for y in range(300)]' '[x for x in T if x in S]'
10 loops, best of 3: 441 msec per loop

لذا ، إذا كان فهم القائمة أسرع قليلاً من Lambda على جهاز الكمبيوتر الخاص بي

يقوم فهم القائمة و Lambda بأشياء مختلفة ، سيكون فهم القائمة المطابق لـ Lambda [val for val in T if val in S].

الكفاءة ليست هي السبب في تفضيل فهم القائمة (في حين أنها في الواقع أسرع قليلاً في جميع الحالات تقريبًا). السبب وراء تفضيلهم هو قابلية القراءة.

جربه بجسم حلقة أصغر وحلقات أكبر ، مثل جعل T مجموعة ، وتكرار S. في هذه الحالة على جهاز بلدي ، فإن فهم القائمة هو ما يقرب من ضعف سرعة.

تم التنميط الخاص بك خطأ. ألقِ نظرة وحدة Timeit وحاول مرة أخرى.

lambda يحدد الوظائف المجهولة. مشكلتهم الرئيسية هي أن الكثير من الناس لا يعرفون مكتبة بيثون بأكملها واستخدامها لإعادة تنفيذ الوظائف الموجودة بالفعل في operator, functools الوحدة النمطية الخ (وأسرع بكثير).

لا علاقة لها بالقائمة lambda. فهي مكافئة للمعيار filter و map وظائف من اللغات الوظيفية. تفضل LCs لأنه يمكن استخدامها كمولدات أيضًا ، ناهيك عن قابلية القراءة.

هذا سريع جدا:

def binary_search(a, x, lo=0, hi=None):
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        midval = a[mid]
        if midval < x:
            lo = mid+1
        elif midval > x: 
            hi = mid
        else:
            return mid
    return -1

time1 = time.time()
N = [x for x in T if binary_search(S, x) >= 0]
time2 = time.time()
print 'time diff binary search=', time2-time1

ببساطة: مقارنات أقل ، وقت أقل.

يمكن أن تحدث اختلافات القائمة فرقًا أكبر إذا كان عليك معالجة النتائج التي تمت تصفيتها. في حالتك ، تقوم فقط ببناء قائمة ، ولكن إذا كان عليك فعل شيء كهذا:

n = [f(i) for i in S if some_condition(i)]

سوف تكسب من تحسين LC على هذا:

n = map(f, filter(some_condition(i), S))

ببساطة لأن الأخير يجب أن يبني قائمة وسيطة (أو tuple ، أو سلسلة ، اعتمادًا على طبيعة S). نتيجة لذلك ، ستلاحظ أيضًا تأثيرًا مختلفًا على الذاكرة المستخدمة في كل طريقة ، سوف تبقي LC أقل.

Lambda في حد ذاته لا يهم.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top