ドメインサイズが小さく(| D |〜20)、範囲がはるかに大きい(| R |〜2 ^ 20)すべての関数のセットf:D-> Rでブランチとバウンド検索を記述しようとしています)。最初に、私は次の解決策を思いつきました。
(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
は、条件のリスト内の要素がpartial-map
に違反している場合にのみnilを返します。関数recur-on-first
は、ドメインの最初の要素を範囲の最初の要素に割り当て、そこからソリューションを構築しようとします。このrecur-on-first
に失敗すると、ドメイン内の最初の要素を範囲内の最初の要素以外の要素に割り当てるソリューションを構築しようとします。ただし、これらは破棄された要素(範囲内の最初の要素など)を格納するリストignored
を維持する必要があります。これは、ドメイン内の他のいくつかの要素のイメージになる可能性があるためです。
この解決策には2つの問題があります。 1つ目は、関数recur-on-first
のignored
とrange
のリストが非常に大きく、それらをappend
ingすることは負荷の高い操作であることです。 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番目のソリューションの実行時間は、最初のソリューションの実行時間よりもはるかに優れています。最初のソリューションのappend
操作は、二重にリンクされたリストの内外のスプライシング要素に置き換えられ(これらの操作は一定の時間です)、再帰の深さはドメインのサイズにのみ依存します。しかし、このソリューションの私の問題は、C
スタイルのコードを使用することです。だから私の質問はこれです。
2番目のソリューションと同じくらい効率的ですが、setf
sと可変データ構造を使用しないソリューションはありますか?言い換えれば、この問題に対する効率的な関数型プログラミングソリューションはありますか?
頭に浮かぶ最初のアイデア:同じ一般的なアプローチを使用しますが、ループを末尾再帰呼び出しで置き換えます。スプライスされたリストを変更する必要はなく、各ステージで新しいリストを生成するだけです。これは確かに一定時間ではありませんが、とにかくどこにスプライスするかを見つけるためにリストを歩く必要があります。特に単一リンクリストを使用できる場合は、ほとんどのノードを再利用できる場合もあります。