Estoy tratando de escribir una búsqueda de bifurcación y enlace en el conjunto de todas las funciones f: D -> R, donde el tamaño del dominio es pequeño (| D | ~ 20) y el rango es mucho mayor (| R | ~ 2 ^ 20 ) Inicialmente, se me ocurrió la siguiente solución.
(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))))))))
Aquí, el parámetro condlist
de la función builder
es una lista de condiciones que una solución debe cumplir. La función check
devuelve nil si el elemento viola algún elemento de la lista de condiciones partial-map
. La función recur-on-first
asigna el primer elemento del dominio al primer elemento del rango e intenta construir una solución a partir de ahí. De lo contrario, se recur-on-first
invoca para intentar construir una solución que asigne el primer elemento del dominio a otro elemento que no sea el primero en el rango. Sin embargo, debe mantener una lista ignored
que almacene estos elementos descartados (como el primer elemento del rango) ya que podrían ser imágenes de algunos otros elementos en el dominio.
Hay dos problemas que puedo ver con esta solución. La primera es que las listas ignored
y range
la función recur-on-first
son bastante grandes y append
su uso es una operación costosa. El segundo problema es que la profundidad de recursión de la solución depende del tamaño del rango.
Entonces se me ocurrió la siguiente solución que utiliza listas doblemente vinculadas para almacenar los elementos en el rango. Las funciones start
, next
y end
proporcionan instalaciones para iterar sobre la lista doblemente enlazada.
(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))))))
El tiempo de ejecución de la segunda solución es mucho mejor que el tiempo de ejecución de la primera solución. La append
operación en la primera solución se reemplaza por elementos de empalme dentro y fuera de una lista doblemente vinculada (estas operaciones son de tiempo constante) y la profundidad de recursión solo depende del tamaño del dominio. Pero mi problema con esta solución es que usa C
código de estilo. Así que mi pregunta es esta.
¿Existe una solución que sea tan eficiente como la segunda solución pero que no use setf
estructuras de datos mutables? En otras palabras, ¿existe una solución de programación funcional eficiente para este problema?
fuente