понедельник, 17 мая 2010 г.

Common Lisp. Цикл против рекурсии

Пробуем писать простейшие циклы с помощью do. Циклы в большинстве случаев могут выполнять полезную работу только вместе с переменными, поэтому переменные, изученные в прошлых примерах, для освоения цикла будут как нельзя кстати.

1. Вычисления Факториала в цикле
;; Рекурсивное вычисление факториала
(defun fact (n)
       (if (= 1 n)
           1
           (* n (fact (- n 1)))))

;; Рекурсивное вычисление факториала
;; с использованием хвостовой рекурсии
(defun fact2 (counter &optional (val 1))
       (if (= counter 0)
           val
           (fact2 (- counter 1) (* val counter))))

;; Вычисление факториала с помощью цикла do
(defun fact3 (n)
       (let ((f 1))
            (do ((i 2 (+ i 1)))
                ((> i n) f)
                (setf f (* f i)))))

;;; Сравниваем результат вычислений и
;;; ресурсоёмкость трёх функций
(= (time (fact 20000))
   (time (fact2 20000))
   (time (fact3 20000)))
Результат выполнения:
Evaluation took:
  0.426 seconds of real time
  0.420026 seconds of total run time (0.400025 user, 0.020001 system)
  [ Run times consist of 0.064 seconds GC time, and 0.357 seconds non-GC time. ]
  98.59% CPU
  855,372,860 processor cycles
  303,248,336 bytes consed
Evaluation took:
  0.437 seconds of real time
  0.436027 seconds of total run time (0.436027 user, 0.000000 system)
  [ Run times consist of 0.044 seconds GC time, and 0.393 seconds non-GC time. ]
  99.77% CPU
  877,747,980 processor cycles
  339,314,808 bytes consed
Evaluation took:
  0.402 seconds of real time
  0.400025 seconds of total run time (0.380023 user, 0.020002 system)
  [ Run times consist of 0.076 seconds GC time, and 0.325 seconds non-GC time. ]
  99.50% CPU
  807,685,920 processor cycles
  303,254,160 bytes consed
T
Вариант вычисления при помощи цикла самый быстрый, на втором месте идёт рекурсивная функция, на третьем - рекурсивная с хвостовой рекурсией.

2. Вычисление чисел Фибоначчи
;; Вычисление чисел Фибоначчи рекурсией
(defun fib (num &key (a 1) (b 1))
       (if (= num 1)
           a
           (fib (- num 1) :a b :b (+ a b))))

;; Вычисление чисел Фибоначчи с использованием цикла do
(defun fib2 (n &key (a 1) (b 1))
       (cond ((= n 1) a)
             ((= n 2) b)
             (T (do ((i 3 (+ i 1)))
                    ((= i n) (+ a b))
                    (shiftf a b b (+ a b))))))

;;; Сравниваем результат вычислений и
;;; ресурсоёмкость функций
(= (time (fib 20000))
   (time (fib2 20000)))
Результат выполнения программы:
Evaluation took:
  0.030 seconds of real time
  0.032002 seconds of total run time (0.032002 user, 0.000000 system)
  [ Run times consist of 0.012 seconds GC time, and 0.021 seconds non-GC time. ]
  106.67% CPU
  60,564,250 processor cycles
  17,588,392 bytes consed
Evaluation took:
  0.025 seconds of real time
  0.024001 seconds of total run time (0.024001 user, 0.000000 system)
  [ Run times consist of 0.004 seconds GC time, and 0.021 seconds non-GC time. ]
  96.00% CPU
  48,908,280 processor cycles
  17,597,480 bytes consed
T
Вычисление с помощью циклов оказалось быстрее. Хотя, если производительность - не главное, то я выбрал бы рекурсивный вариант - он легче читается и красивее выглядит.

2 комментария:

  1. Здравствуйте!
    (defun fib2 (n &key (a 1) (b 1))
    (cond ((= n 1) a)
    ((= n 2) b)
    (T (do ((i 3 (+ i 1)))
    ((= i n) (+ a b))
    (shiftf a b b (+ a b))))))
    можете объяснить последнюю строчку и что значит T перед циклом do?

    ОтветитьУдалить
  2. Оператор cond принимает список условий и действий, которые нужно выполнить, если соответствующее условие истинно. Если n=1, то возвращается a. Если n=2, то возвращается b. Если ни то ни другое не сработало, то выполняется цикл. T - это истина. Вместо T можно было написать что-то вроде (= n n) и это выражение тоже вычилилось бы в T, и цикл был бы выполнен.

    ОтветитьУдалить