Я намагаюся записати гілку і зв'язаний пошук на множині всіх функцій 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
код стилю. Тож моє запитання таке.
Чи існує рішення, яке є таким же ефективним, як друге рішення, але не використовує setf
s та змінні структури даних? Іншими словами, чи існує ефективне функціональне програмування рішення цієї проблеми?