Як реалізувати розгалужену галузь у функціональній мові програмування?


26

Я намагаюся записати гілку і зв'язаний пошук на множині всіх функцій f: D -> R, де розмір домену невеликий (| D | ~ 20) і діапазон набагато більший (| R | ~ 2 ^ 20 ). Спочатку я придумав таке рішення.

(builder (domain range condlist partial-map)
            (let ((passed? (check condlist partial-map)))
              (cond
               ((not passed?) nil)
               (domain (recur-on-first domain range condlist partial-map '()))
               (t partial-map))))
(recur-on-first (domain range condlist partial-map ignored)
                   (cond
                    ((null range) nil)
                    (t (let ((first-to-first
                              (builder (cdr domain)
                                       (append ignored (cdr range))
                                       condlist
                                       (cons (cons (car domain) (car range)) partial-map))))
                         (or first-to-first
                             (recur-on-first domain
                                             (cdr range)
                                             condlist
                                             partial-map
                                             (cons (car range) ignored))))))))

Тут параметр condlistфункції builder- це перелік умов, які повинні бути задоволені рішенням. Функція checkповертає nil якщо будь-який елемент у списку умов порушується partial-map. Функція recur-on-firstпризначає перший елемент домену першому елементу в діапазоні і намагається побудувати звідти рішення. Якщо цього recur-on-firstне вдалося, спробувати створити рішення, яке присвоює перший елемент домену деякому елементу, відмінному від першого в діапазоні. Однак він повинен підтримувати список, ignoredякий зберігає ці відкинуті елементи (як перший елемент у асортименті), оскільки вони можуть бути зображеннями деяких інших елементів у домені.

Є дві проблеми, які я бачу з цим рішенням. Перший з них полягає в тому, що списки ignoredта rangeфункції recur-on-firstдосить великі, і appendїх використання - це дорога операція. Друга проблема полягає в тому, що глибина рекурсії розчину залежить від величини діапазону.

Тому я придумав таке рішення, яке використовує подвійні зв’язані списки для зберігання елементів у діапазоні. Функції start, nextі endзабезпечують засоби перебрати двусвязний список.

(builder (domain range condlist &optional (partial-map nil))
            (block builder
                   (let ((passed? (check condlist partial-map)))
                     (cond
                       ((not passed?) nil)
                       (domain (let* ((cur (start range))
                                      (prev (dbl-node-prev cur)))
                                 (loop
                                   (if (not (end cur))
                                     (progn
                                       (splice-out range cur)
                                       (let ((sol (builder (cdr domain)
                                                           range
                                                           condlist
                                                           (cons (cons (car domain) (data cur)) partial-map))))
                                         (splice-in range prev cur)
                                         (if sol (return-from builder sol)))
                                       (setq prev cur)
                                       (setq cur (next cur)))
                                     (return-from builder nil)))))
                       (t partial-map))))))

Час виконання другого рішення набагато краще, ніж час виконання першого рішення. appendОперації в першому розчині замінюються сплайсинг елементи в і з двічі пов'язаного списку (ці операції є постійним часом) , а глибина рекурсії залежить тільки від розміру домену. Але моя проблема з цим рішенням полягає в тому, що він використовує Cкод стилю. Тож моє запитання таке.

Чи існує рішення, яке є таким же ефективним, як друге рішення, але не використовує setfs та змінні структури даних? Іншими словами, чи існує ефективне функціональне програмування рішення цієї проблеми?

Відповіді:


1

Перша ідея, яка спадає на думку: використовувати той же загальний підхід, але замінити цикл на хвостово-рекурсивний виклик, параметром якого є спланований список для наступного етапу обчислення? Вам ніколи не доведеться змінювати спланований список, просто генеруйте новий список на кожному етапі. Це, правда, не є постійним часом, але вам потрібно пройти список, щоб знайти, де все-таки спланувати. Можливо, навіть вдасться повторно використовувати більшість вузлів, особливо якщо ви можете використовувати окремо пов'язаний список.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.