2009-07-22 10 views
6

Napisałem 3 funkcje, które zliczają ile razy pojawia się element-a na liście. Próbowałem różnych wejść i profilowałem to, ale nadal nie wiem, która funkcja jest najlepsza pod względem wydajności użycia stosu i wydajności czasu. Proszę pomóż mi.Która funkcja jest najlepsza pod względem wydajności i czasu użytkowania stosu

;; Using an accumulator 
    (defn count-instances1 [a-list an-element] 
     (letfn [(count-aux [list-aux acc] 
         (cond 
          (empty? list-aux) acc 
          :else (if (= (first list-aux) an-element) 
            (count-aux (rest list-aux) (inc acc)) 
            (count-aux (rest list-aux) acc))))] 
     (count-aux a-list 0))) 

;; Normal counting 
    (defn count-instances2 [a-list an-element] 
    (cond 
     (empty? a-list) 0 
     :else 
      (if (= (first a-list) an-element) 
       (+ 1 (count-instances2 (rest a-list) an-element)) 
       (count-instances2 (rest a-list) an-element)))) 

;; using loop. does this help at all? 
    (defn count-instances3 [a-list an-element] 
     (loop [mylist a-list acount 0] 
      (if (empty? mylist) 
       acount 
       (if (= (first mylist) an-element) 
       (recur (rest mylist)(inc acount)) 
       (recur (rest mylist) acount))))) 
+3

Jakie były wyniki Twoich działań profilujących? –

+3

Zagnieżdżone 'defn' prawdopodobnie nie robi tego, co myślisz. 'defn' zawsze definiuje funkcję najwyższego poziomu. Możesz użyć 'letfn' (lub nawet' (let [f (fn ...)]) ') jeśli chcesz zdefiniować wewnętrzną funkcję. –

+0

Dzięki Brian. Ale nie mogę pozwolić, żeby letfn zadziałał. Czy mógłbyś edytować moje pytanie z letfn? Wielkie dzięki. – unj2

Odpowiedz

2

Wersja pętla/powtarzać to właściwa droga. Clojure nie może zoptymalizować wywołań końcowych z powodu ograniczeń JVM.

+3

To nie jest dokładne. Powinieneś powiedzieć, że Clojure * wybiera * nie optymalizować wywołań ogona, ponieważ jest to trudne do osiągnięcia w języku (Java), który ich jeszcze nie ma. W rzeczywistości istnieją implementacje języków (np. SISC - http://sisc-scheme.org/) na szczycie JVM, które optymalizują wywołania ogona. –

+0

To naprawdę dziwne. Dlaczego nie chciałby optymalizować połączeń typu tail? To zaoszczędziłoby nam wiele kłopotów? Czy są kompromisy? – unj2

+3

'recur' jest ładne, ponieważ jest wyraźne i można go używać tylko z pozycji ogonowych (kompilator narzeka inaczej), które przechwytuje przypadki, w których myślisz, że jesteś rekwizytem, ​​ale ty nie. Wszystko to może zostać automatycznie zdemontowane, ale zastąpienie nazwy funkcji w wywołaniu ogona za pomocą symbolu 'recur' nie stanowi większego problemu. –

0

Pisanie kodu więc kompilator/interperter może optymalizowania go za ogon rekursji, powinny spowodować zwiększenie wydajności i zmniejszenie wykorzystania stosu. Myślę, że twoja normalna funkcja liczenia mogłaby kwalifikować się do rekurencji ogona, tak że powinna być dość szybko. Nie jestem pewien, bo ja tylko pstry w Lisp jako hobby.

http://en.wikipedia.org/wiki/Tail_recursion

+0

Clojure nie może optymalizować wywołań końcowych ze względu na ograniczenia JVM. – Svante

+1

Komentarz Rich Hickey na ten temat był taki, że lepiej było mieć wyraźną abstrację, która działa przez cały czas (recur) niż niejawna, która działa prawie przez cały czas (ze względu na złożoność robienia tego w JVM) –

+0

@Svante - Clojure może i zoptymalizuje połączenia typu "ogon". Musisz tylko jasno powiedzieć, że chcesz go użyć, używając 'recur' (który został wybrany przez Rich Hickeya). Całkiem możliwe jest wykonywanie całkowitego kosztu posiadania na JVM. Ograniczenia JVM wpływają tylko na wzajemną rekurencję (co w rzeczywistości jest dość rzadkim przypadkiem). – mikera

Powiązane problemy