Sto cercando di scrivere un ramo e la ricerca associata sull'insieme di tutte le funzioni f: D -> R, dove la dimensione del dominio è piccola (| D | ~ 20) e l'intervallo è molto più grande (| R | ~ 2 ^ 20 ). Inizialmente, ho trovato la seguente soluzione.
(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))))))))
Qui il parametro condlist
per la funzione builder
è un elenco di condizioni che dovrebbero essere soddisfatte da una soluzione. La funzione check
restituisce zero se qualsiasi elemento nell'elenco delle condizioni viene violato da partial-map
. La funzione recur-on-first
assegna il primo elemento nel dominio al primo elemento nell'intervallo e tenta di creare una soluzione da lì. In caso recur-on-first
contrario, si invoca a tentare di costruire una soluzione che assegni il primo elemento nel dominio a un elemento diverso dal primo nell'intervallo. Tuttavia, deve mantenere un elenco ignored
che memorizza questi elementi scartati (come il primo elemento nell'intervallo) in quanto potrebbero essere immagini di alcuni altri elementi nel dominio.
Ci sono due problemi che posso vedere con questa soluzione. Il primo è che gli elenchi ignored
e range
nella funzione recur-on-first
sono piuttosto grandi e append
il loro utilizzo è un'operazione costosa. Il secondo problema è che la profondità di ricorsione della soluzione dipende dalle dimensioni dell'intervallo.
Quindi ho ideato la seguente soluzione che utilizza elenchi doppiamente collegati per memorizzare gli elementi nell'intervallo. Le funzioni start
, next
e end
offrono servizi per scorrere la lista doppiamente collegata.
(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))))))
Il runtime della seconda soluzione è molto migliore del runtime della prima soluzione. L' append
operazione nella prima soluzione è sostituita da elementi di giunzione all'interno e all'esterno di un elenco doppiamente collegato (queste operazioni sono a tempo costante) e la profondità di ricorsione dipende solo dalla dimensione del dominio. Ma il mio problema con questa soluzione è che utilizza il C
codice di stile. Quindi la mia domanda è questa.
Esiste una soluzione efficiente quanto la seconda soluzione ma che non utilizza setf
strutture di dati mutabili? In altre parole, esiste un'efficace soluzione di programmazione funzionale a questo problema?