2012-06-24 7 views
7

Podczas próby napisania odpowiedzi na inne pytanie SO, wydarzyło się coś naprawdę szczególnego.są lambda pytona zaimplementowana inaczej niż standardowe funkcje?

I w zasadzie wymyślił GCD jeden liniowej i powiedział it maybe slower because of recursion
gcd = lambda a,b : a if not b else gcd(b, a % b)

herezje prosty test:

assert gcd(10, 3) == 1 and gcd(21, 7) == 7 and gcd(100, 1000) == 100 

oto kilka punktów odniesienia:

timeit.Timer('gcd(2**2048, 2**2048+123)', setup = 'from fractions import gcd').repeat(3, 100) 
# [0.0022919178009033203, 0.0016410350799560547, 0.0016489028930664062] 
timeit.Timer('gcd(2**2048, 2**2048+123)', setup = 'gcd = lambda a,b : a if not b else gcd(b, a % b)').repeat(3, 100) 
# [0.0020480155944824219, 0.0016460418701171875, 0.0014090538024902344] 

Dobrze Ów ciekawy I oczekuje się, że będzie znacznie wolniej, ale czasy są dość zbliżone,? być może importowanie modułu jest problemem ...

>>> setup = ''' 
... def gcd(a, b): 
...  """Calculate the Greatest Common Divisor of a and b. 
... 
...  Unless b==0, the result will have the same sign as b (so that when 
...  b is divided by it, the result comes out positive). 
...  """ 
...  while b: 
...   a, b = b, a%b 
...  return a 
... ''' 
>>> timeit.Timer('gcd(2**2048, 2**2048+123)', setup = setup).repeat(3, 100) 
[0.0015637874603271484, 0.0014810562133789062, 0.0014750957489013672] 

nie ma jeszcze dość dokładnych taktów ok pozwala wypróbować większe wartości.

timeit.Timer('gcd(2**9048, 2**248212)', setup = 'gcd = lambda a,b : a if not b else gcd(b, a % b)').repeat(3, 100) [2.866894006729126, 2.8396279811859131, 2.8353509902954102] 
[2.866894006729126, 2.8396279811859131, 2.8353509902954102] 
timeit.Timer('gcd(2**9048, 2**248212)', setup = setup).repeat(3, 100) 
[2.8533108234405518, 2.8411397933959961, 2.8430981636047363] 

ciekawe Zastanawiam się, co się dzieje?
Zawsze zakładałem, że rekurencja była wolniejsza ze względu na narzut wywoływania funkcji, czy lambda jest wyjątkiem? i dlaczego nie osiągnąłem limitu rekurencji?
Jeśli realizowane z wykorzystaniem def Uderzyłem go od razu, gdybym zwiększyć głębokość rekurencji do czegoś podobnego 10**9 I rzeczywiście dostać segmentation fault prawdopodobnie przepełnienie stosu ...

Aktualizacja

>>> setup = ''' 
... import sys 
... sys.setrecursionlimit(10**6) 
... 
... def gcd(a, b): 
...  return a if not b else gcd(b, a % b) 
... ''' 
>>> 
>>> timeit.Timer('gcd(2**9048, 2**248212)', setup = 'gcd = lambda a,b:a if not b else gcd(b, a%b)').repeat(3, 100) 
[3.0647969245910645, 3.0081429481506348, 2.9654929637908936] 
>>> timeit.Timer('gcd(2**9048, 2**248212)', setup = 'from fractions import gcd').repeat(3, 100) 
[3.0753359794616699, 2.97499680519104, 3.0096950531005859] 
>>> timeit.Timer('gcd(2**9048, 2**248212)', setup = setup).repeat(3, 100) 
[3.0334799289703369, 2.9955930709838867, 2.9726388454437256] 
>>> 

nawet więcej zagadkowy ...

+0

Myślę, że najprawdopodobniej interpreter Python optymalizuje wyrażenia lambda w pętlę * dla ciebie * (podobnie jak typowy Lisp implementacja zoptymalizuje rekurencyjne wywołania końcowe). Jednak byłby to szczegół implementacji CPython, niekoniecznie prawdziwy dla wszystkich interpreterów Pythona. – Felix

+0

'recursive tail calls' yeah thats co również myślę, wciąż możemy zawsze to zastosować, nie znaczy, że rekursja jest nieco lepsza, używając lambdas, a następnie standardowego' def', który jest naprawdę zagadkowy, szczególnie gdy bierzesz pod uwagę "to nadaje priorytet czytelności ponad szybkość i ekspresyjność 'pobrane z http://en.wikipedia.org/wiki/Portal:Python_programming –

+2

@FelixBonkoski, Python nie optymalizuje rekurencji ogona.Ten kod ma tylko małe zużycie stosu :) – astynax

Odpowiedz

-1

Rodzaj lambda jest dokładnie taki sam jak typ jakiejkolwiek innej funkcji, aw przypadku obu, jeśli zdefiniowano w innym zasięgu lokalnym, następuje przechwytywanie środowiska.

Jedyna różnica polega na tym, że funkcje zdefiniowane za pomocą składni lambda nie stają się automatycznie wartościami zmiennej w zakresie, w którym się pojawiają, i że składnia lambda wymaga, aby treść była jednym (prawdopodobnie złożonym) wyrażeniem, wartość z których jest zwracana z funkcji.

Co do szybkości rekursji - tak, jest niewielki wzrost, ale najwyraźniej nie za wiele. Wydaje się, że obciążenie nazywane jest głównie kosztem przydzielenia ramki stosu.

+0

Czy spojrzałeś na czasy, które pokazuje? Nie ** nie widzisz "efektu", że biblioteka Funkcja fractions.gcd jest szybsza, próbuje pokazać, że jest to rzut monetą, a on jest tym zaskoczony -1 za to, że nie czyta tego pytania: – Felix

+1

@Marcin Zdefiniowałem również funkcję nierekurencyjną przy użyciu zwykłego pythona i czasy w dalszym ciągu nie różnią się, dzieje się coś dziwnego i dlaczego nie osiągnąłem granicy rekurencji? –

+0

@ samy.vilar Tworzysz tylko jeden nowy int na ramkę stosu, więc zużycie pamięci nie jest problemem. nie ma tu tajemnicy, co do rekurencji li mit, dlaczego spodziewałbyś się, że trafisz w ten przykład? – Marcin

6
counter = 0 

def gcd(a, b): 
    global counter 
    counter += 1 
    return a if not b else gcd(b, a % b) 

gcd(2**9048, 2**248212) 
print counter 

Drukuje 3. Oczywiście nie ma zbyt wiele narzekania na rekursję głębokości 3.

+0

Tak, jestem tego całkiem świadomy, powinienem używać numerów Fibonacciego do przeprowadzania testów, uczenia się czegoś nowego codziennie ... –

Powiązane problemy