Регистрация | Войти
Lisp — программируемый язык программирования
RSS
[clisp] Запустите скрипт для вычисления функции Аккермана у себя
vaginoid2 - 09.03.2010 23:17, Сообщений - 27

Скрипт вычисляет значения функции Аккермана, используя кэширование. Кэш вначале инициализируется, а потом происходит выполнение (ниже код).

Скрипт:

 
;; кэш для функции Аккермана
(defvar *akk-cache* nil)

;; счетчик рекурсивных вызовов (должен быть обнулен перед вызовом функции akk или akk-cache)
(defvar *akk-recursive-calls-counter* 0)


(defun print-akk-cache ()
(print *akk-cache*)
)


(defun print-akk-calls-counter ()
(format t "~a~%" *akk-recursive-calls-counter*)
)



;; эта функция инициализирует кэш указанным размером
(defun init-akk-cache (m n)
(setf *akk-cache* nil)
(setf *akk-cache* (make-list m))
(let ((cache-row (make-list n :initial-element 0)))
(dotimes (i m)
(setf (nth i *akk-cache*) (copy-list cache-row))
)
)

(return-from init-akk-cache t)
)



;; установка значения в кэше
(defun set-akk-cache-value (m n value)
(setf (nth n (nth m *akk-cache*)) value)
(return-from set-akk-cache-value t)
)



;; получение значения из кэша               
(defun get-akk-cache-value (m n)
(if (or (> m (length *akk-cache*)) (> n (length (nth 0 *akk-cache*))))
(format t "FUCK!~%")
)

(let ((result (nth n (nth m *akk-cache*))))
(return-from get-akk-cache-value result)
)
)



;; функция Аккермана без использования кэширования
(defun akk (m n)
(incf *akk-recursive-calls-counter*)
(cond ((= m 0) (+ n 1))
   ((= n 0) (akk (- m 1) 1))
   (t (akk (- m 1) (akk m (- n 1))))
)
)



;; функция Аккермана с использованием кэширования
(defun akk-cache (m n)
(incf *akk-recursive-calls-counter*)
(if (/= (get-akk-cache-value m n) 0)
(return-from akk-cache (get-akk-cache-value m n))
(let ((value (cond ((= m 0) (+ n 1))
          ((= n 0) (akk-cache (- m 1) 1))
          (t (akk-cache (- m 1) (akk-cache m (- n 1))))
)
)
)

   (set-akk-cache-value m n value)
   (return-from akk-cache value)
)
)
)


;;
(defun akk-with-calls-counting ()
(loop for m from 0 to 3 do
(loop for n from 0 to 14 do
    (setf *akk-recursive-calls-counter* 0)
    (format t "(~2a ~2a) ~12a [~a]~%" m n (akk m n) *akk-recursive-calls-counter*)
)
)
)


(defun akk-cache-with-calls-counting ()
(loop for m from 0 to 3 do
(loop for n from 0 to 14 do
    (setf *akk-recursive-calls-counter* 0)
    (format t "(~2a ~2a) ~12a [~a]~%" m n (akk-cache m n) *akk-recursive-calls-counter*)
)
)
)

Я работаю в Emacs/Inferior Lisp, поэтому привожу коды для запуска:

 
(init-akk-cache 10 1000000)
(akk-cache-with-calls-counting)

Я дошел до (3 14), дальше у меня шло переполнение стека (я 4 * не считал). Если возможно, посчитайте, пожалуйста, например, не от 0 до 3 и от 0 до 14, как сейчас, а от 0 до 5 и от 0 до 20, например. Если будет переполнение стека, то нужно увеличить размер кэша, например, на 10х10000000 и так далее.

Мой компьютер не позволяет мне такое сделать, буду очень благодарен, если поможете.

Также, если предложите улучшения в коде, с удовольствием выслушаю.

[#]
Хм, чем ты это так отформатировал? ;)

;; кэш для функции Аккермана
(defvar *akk-cache* nil)

;; счетчик рекурсивных вызовов (должен быть обнулен перед вызовом функции akk или akk-cache)
(defvar *akk-recursive-calls-counter* 0)


(defun print-akk-cache ()
  (print *akk-cache*)
)


(defun print-akk-calls-counter ()
  (format t "~a~%" *akk-recursive-calls-counter*)
)



;; эта функция инициализирует кэш указанным размером
(defun init-akk-cache (m n)
  (setf *akk-cache* nil)
  (setf *akk-cache* (make-list m))
  (let ((cache-row (make-list n :initial-element 0)))
    (dotimes (i m)
      (setf (nth i *akk-cache*) (copy-list cache-row))
)
)

  (return-from init-akk-cache t)
)



;; установка значения в кэше
(defun set-akk-cache-value (m n value)
  (setf (nth n (nth m *akk-cache*)) value)
  (return-from set-akk-cache-value t)
)



;; получение значения из кэша                
(defun get-akk-cache-value (m n)
  (if (or (> m (length *akk-cache*)) (> n (length (nth 0 *akk-cache*))))
      (format t "FUCK!~%")
)

  (let ((result (nth n (nth m *akk-cache*))))
    (return-from get-akk-cache-value result)
)
)



;; функция Аккермана без использования кэширования
(defun akk (m n)
  (incf *akk-recursive-calls-counter*)
  (cond ((= m 0) (+ n 1))
        ((= n 0) (akk (- m 1) 1))
        (t (akk (- m 1) (akk m (- n 1))))
)
)



;; функция Аккермана с использованием кэширования
(defun akk-cache (m n)
  (incf *akk-recursive-calls-counter*)
  (if (/= (get-akk-cache-value m n) 0)
      (return-from akk-cache (get-akk-cache-value m n))
      (let ((value (cond ((= m 0) (+ n 1))
                         ((= n 0) (akk-cache (- m 1) 1))
                         (t (akk-cache (- m 1) (akk-cache m (- n 1))))
)
)
)

        (set-akk-cache-value m n value)
        (return-from akk-cache value)
)
)
)


;;
(defun akk-with-calls-counting ()
  (loop for m from 0 to 3 do
       (loop for n from 0 to 14 do
            (setf *akk-recursive-calls-counter* 0)
            (format t "(~2a ~2a) ~12a [~a]~%" m n (akk m n) *akk-recursive-calls-counter*)
)
)
)


(defun akk-cache-with-calls-counting ()
  (loop for m from 0 to 3 do
       (loop for n from 0 to 14 do
            (setf *akk-recursive-calls-counter* 0)
            (format t "(~2a ~2a) ~12a [~a]~%" m n (akk-cache m n) *akk-recursive-calls-counter*)
)
)
)
archimag - 10.03.2010 00:50
[#]
Я заменил списки на массив и стало выполняться мгновенно.
andy128k - 10.03.2010 01:05
[#]
Отформатировал в Emacs'е нормально, просто при копировании не обратил внимание, что отступы исказились :).

А правда, что clisp без компиляции рекурсию не раскрывает, что нужно скомпилить в байт-код, тогда переполнения или не будет, или будет позже?
vaginoid2 - 10.03.2010 01:07
[#]
Вычисление A(4, 2) переполнило двухгигабайтный стек. :)

return-from не нужны.
andy128k - 10.03.2010 01:17
[#]
Исправил на массивы - и правда работает быстрее, спасибо.
У меня 2ГБ ram, однако не могу вычислить (3 15) (уже с массивами) - Control stack guard page temporarily disabled: proceed with caution - переполняется стек.
Не могу понять, почему (кэш 10х100000). Пробовал увеличить кэш - не помогает, что вполне логично.
vaginoid2 - 10.03.2010 01:23
[#]
Покажите пожалуйста Ваш измененный код.
vaginoid2 - 10.03.2010 01:30
[#]
(defvar *akk-cache* nil)

(defun akk (m n)
  (let ((*akk-cache* (or *akk-cache*
                         (make-hash-table :test 'equal)
)
)
)

    (labels ((akk/impl (m n)
               (or (gethash (list m n) *akk-cache*)
                   (setf (gethash (list m n) *akk-cache*)
                         (cond ((= m 0) (1+ n))
                               ((= n 0) (akk/impl (1- m) 1))
                               (t (akk/impl (1- m) 
                                            (akk/impl m (1- n))
)
)
)
)
)
)
)

      (akk/impl m n)
)
)
)
archimag - 10.03.2010 01:42
[#]
Так кажется короче (блин, что-то не могу победить нормально форматирование)
archimag - 10.03.2010 01:43
[#]
2andy128k
У Вас вычисление (4 2) переполнило _2ГБ_ стек? Я сделал цикл 0-4 внешний и 0-14 внутренний, оборвалось как у Вас на (4 2), однако в System Monitor написано, что память sbcl занимает ~ 400 МБ, что никак не 2ГБ.
Вероятно, у Вас получилось то же.
Тогда вопрос: можно ли избежать переполнения стека? (компиляция в байткод не помогает)
vaginoid2 - 10.03.2010 02:01
[#]
> Тогда вопрос: можно ли избежать переполнения стека? (компиляция в байткод не помогает)

Компиляция тут не при чём, я запускаю под SBCL - получаю тоже самое. Надо избавляться от рекурсии.
archimag - 10.03.2010 02:08
[#]
Если я правильно понимаю, уйти от рекурсии в данной случае невозможно, так как функция аккремана на этом и построена.

Всем спасибо за помощь.
vaginoid2 - 10.03.2010 02:14
[#]
> Если я правильно понимаю, уйти от рекурсии в данной случае невозможно, так как функция аккремана на этом и построена.

От рекурсии всегда можно уйти ;)
archimag - 10.03.2010 02:18
[#]
А как в данном случае?
vaginoid2 - 10.03.2010 02:20
[#]
> А как в данном случае?

Ну, не так просто, как обычно :) Надо подумать... А что, очень надо?
archimag - 10.03.2010 02:26
[#]
Раз уже я взялся за тему, так довести до логического конца с Вашей помощью :)
Интересно узнать, как реализовать аккермана через итерацию, ибо у меня не выходит.
vaginoid2 - 10.03.2010 02:29
[#]
Я последовательно увеличивал стек дошёл до 2ГБ. Дальше не стал.
Запускал так:

sbcl --control-stack-size 2048
andy128k - 10.03.2010 03:04
[#]
> Раз уже я взялся за тему, так довести до логического конца с Вашей помощью :)
> Интересно узнать, как реализовать аккермана через итерацию, ибо у меня не выходит.

Ну, если сможешь понять, то вот:

(defvar *akk-cache* nil)

(defun akk (x y)
  (let ((*akk-cache* (or *akk-cache*
                         (make-hash-table :test 'equal)
)
)

        (stack `((,x ,y)))
)

    (labels ((akk-cache (m n)
               (gethash `(,m ,n) *akk-cache*)
)

             (new-value (value)
               (setf (gethash (car stack) *akk-cache*)
                     value
)

               (cond
                 ((not stack) t)
                 ((not (second stack))
                  (pop stack)
)

                 ((second (second stack))
                  (pop stack)
)

                 (t (let (((first (car stack)))
                          ((second (car stack)))
                          ((first (second stack)))
)

                      (pop stack)
                      (pop stack)
                      (push (list i (gethash `(,m ,n) *akk-cache*))
                            stack
)
)
)
)
)
)

      (loop
         while stack
         do (let (((first (car stack)))
                  ((second (car stack)))
)

              (if (akk-cache m n)
                  (pop stack)
                  (cond ((= m 0)
                         (new-value (1+ n))
)

                        ((and (= n 0)
                              (akk-cache (1- m) 1)
)

                         (new-value (akk-cache (1- m) 1))
)

                        ((= n 0)
                         (push (list (1- m) 1)
                               stack
)
)

                        ((and (akk-cache m (1- n))
                              (akk-cache (1- m)
                                         (akk-cache m (1- n))
)
)

                         (new-value (akk-cache (1- m)
                                                  (akk-cache m (1- n))
)
)
)

                        ((akk-cache m (1- n))
                         (push (list (1- m)
                                     (akk-cache m (1- n))
)

                               stack
)
)

                        (t (push (list (1- m))
                                 stack
)

                           (push (list m (1- n))
                                 stack
)
)
)
)
)
)

      (akk-cache x y)
)
)
)

archimag - 10.03.2010 04:12
[#]
А уВас этот код нормально выполняется/компилируется?
vaginoid2 - 10.03.2010 09:28
[#]
> А у Вас этот код нормально выполняется/компилируется?

Конечно, что не так?
archimag - 10.03.2010 09:33
[#]
Так это та же самая рекурсия. Только и того, что один стек заменён на другой.
andy128k - 10.03.2010 10:27
[#]
На вычислении (akk 4 2) съело 3.5 гига памяти и было убито ядром.
andy128k - 10.03.2010 10:34
[#]
> Так это та же самая рекурсия. Только и того, что один стек заменён на другой.

Это не рекурсия. Никакой рекурсии. Ну а куда же здесь без стэка? Можно, конечно, время от времени оставлять кэш, но сбрасывать стэк и начинать всё с начала, там можно будет больше посчитать, но будет дольше.
archimag - 10.03.2010 11:21
[#]

Еще можно так (на просторах интернета есть объяснение этому; см. ту же английскую википедию про эту функцию, а не русскую википедию про нее):

http://paste.lisp.org/display/96207   (не знаю как тут код вставлять).

Взято из вот тут http://www.ymeme.com/ackermann-function-lisp.html

p.s. как я понял, функция Аккермана интересна как раз в виде рекурсии, так как на ней проверяют насколько "смекалистый" компилятор по отошению к оптимизации рекурсий.


artem - 10.03.2010 16:57
[#]
> как я понял, функция Аккермана интересна как раз в виде рекурсии, так как на ней проверяют насколько "смекалистый"
> компилятор по отошению к оптимизации рекурсий.

Чего-то я плохо понял, о какой "смекалистости" идёт речь. Скорей уж, о накладных расходах на рекурсию...
archimag - 10.03.2010 17:02
[#]
О "смекалистости" читать тут http://en.wikipedia.org/wiki/Ackermann_function#Use_as_benchmark
artem - 10.03.2010 17:08
[#]
Я здесь попробывал реализовать запись/чтение кэша в файл, у меня записывает около 21 МБ и выдает ошибку.

И, кстати, почему массивы работают быстрее, чем списки?
vaginoid2 - 10.03.2010 21:38
[#]
> И, кстати, почему массивы работают быстрее, чем списки?

rtfm! Вопрос исчерпан, нашел ответ в интернете.
vaginoid2 - 10.03.2010 21:48
@2009-2010 lisper.ru