skuro は、何がうまくいかないのか、その理由について適切な回答を提供してくれると思います。また、元の目標を解決するために使用できる非常に便利なライブラリも提案してくれます。「では、なぜ私はこれを読んでいるのですか?」私はあなたが尋ねると聞きます。数行のコードだけで、デカルト積に関数を適用する方法を一般化する方法を共有できたら面白いと思いました。
一般化するとは、次のようなことができるようになることを目標としていることを意味します。
user> (permute str ["abc" "ab"])
=> ("aa" "ab" "ba" "bb" "ca" "cb")
これまで存在しなかったこの置換関数を開発した場合の良い点は、同じ関数を使用して次のようなことができることです。
user> (permute + [[1 2 3] [10 20 30]])
=> (11 21 31 12 22 32 13 23 33)
これはおもちゃの例ですが、この方法で一般化することで得られる柔軟性が伝わることを願っています。
さて、ここに私が思いついた非常に簡潔な方法があります:
(defn permute [f [coll & remaining]]
(if (nil? remaining)
(map f coll)
(mapcat #(permute (partial f %) remaining) coll)))
私が始めた中心的なアイデアは、結合するさまざまな文字列と同じ数の反復を反復することでしたmap
。mapcat
私はあなたの例から始めて、「冗長な」非一般的な解決策を書きました:
user> (mapcat (fn [i] (map (partial str i) "ab")) "abc")
=> ("aa" "ab" "ba" "bb" "ca" "cb")
これはmapcat
関数 down"abc"
です。具体的には、この時点で使用している単一の要素 ( ) と、 の各要素を一緒に歌うmapcat
関数ダウン"abc"
です。map
str
"abc"
i
"ab"
これを関数に一般化する方法を理解するために、1 つ「深いレベル」に進み、3 番目の文字列で試してみる必要がありました。
user> (mapcat (fn [i] (mapcat (fn [j] (map (partial str i j) "def")) "ab")) "abc")
=> ("aad" "aae" "aaf" "abd" "abe" "abf" "bad" "bae" "baf" "bbd" "bbe" "bbf" "cad" "cae" "caf" "cbd" "cbe" "cbf")
これは、要素を組み合わせた方法で一緒に歌う関数ですmapcat
。ふぅ。今、私はどのように一般化できるかを見始めました。最も内側の式は、再結合する文字列のリストの最後の文字列の下に、常にある種の部分関数を ing することになります。外側の式は、最も外側の文字列が文字列のリストの最初の文字列を使用して再結合するところまで、連続してより前方の文字列を持つ s です。mapcat
map
str
map
str
mapcat
str
ここから、部分関数全体を一度に定義する必要はなく、再帰的に呼び出して「構築」できることに気付いたと思いますpermute
。
関数がどのように機能するかを説明するのに十分なコンテキストを提供できたことを願っています。の最後の反復permute
は、残りの colls がない場合に発生します (つまり、 を(nil? remaining)
返しますtrue
)。それmap
は、最後の coll で与えられた関数です。
残りの colls がある場合は、現在の coll のバリアントにmapcat
なります。この置換バリアントは、無名引数でpermute
の部分関数を使用しており、残りの colls をダウンさせています。これを行うことで、colls のリストの最後に到達すると最終的に呼び出される部分関数が段階的に作成されます。次に、頭の中で、逆方向にトレースし、ネストされたs を呼び出して、結果として再結合された colls で最終的に解き明かされることを想像します。f
permute
mapcat
この関数は簡潔ですが、おそらく最適ではないと思います。率直に言って、私は CS のバックグラウンドをあまり持っていませんが、Clojure について読んだことから、自己再帰呼び出しの代わりにloop
/を使用recur
すると、はるかに「効率的」になる傾向があります。最適化が重要な場合はloop
、使用する関数を作り直すのはかなり簡単だと思います。recur