すべての関数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
ドメイン内の他の要素の画像である可能性があるため、これらの破棄された要素(範囲内の最初の要素など)を格納するリストを維持する必要があります。
このソリューションには2つの問題があります。1つ目は、リストignored
とrange
関数のrecur-on-first
サイズが非常に大きく、リストを作成append
するのは高価な操作であることです。2番目の問題は、解の再帰の深さが範囲のサイズに依存することです。
そこで、二重リンクリストを使用して範囲内の要素を保存する次のソリューションを思い付きました。関数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))))))
2番目のソリューションのランタイムは、1番目のソリューションのランタイムよりもはるかに優れています。append
最初のソリューションの操作は、二重にリンクされたリストの内外のスプライシング要素に置き換えられ(これらの操作は一定時間です)、再帰の深さはドメインのサイズにのみ依存します。しかし、このソリューションの私の問題は、C
スタイルコードを使用することです。私の質問はこれです。
2番目のソリューションと同じくらい効率的ですが、setf
sおよび可変データ構造を使用しないソリューションはありますか?言い換えれば、この問題に対する効率的な関数型プログラミングソリューションはありますか?