我认为 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")
这个mapcats 是一个功能下降"abc"。具体来说,它 mapcats 一个函数向下 "abc" 那个 maps string-ing 一起使用的单个元素来自 "abc" 此时 (i),每个元素来自 "ab" .
为了让我理解如何将其概括为一个函数,我必须“更深一层”,并尝试使用第三个字符串。
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")
mapcats 是一个函数 mapcats 是 maps string-ing 以组合方式将元素组合在一起的函数。呸。现在我开始了解如何进行概括。最里面的表达式总是maping 某种部分的str 函数,在要重新组合的字符串列表中的最后一个字符串。外部表达式只是mapcats 与连续更靠前的字符串,直到最外面的地方使用字符串列表中的第一个字符串重新组合。
我想从这里我注意到我不需要一次定义整个部分 str 函数,而是可以“构建”它,因为我递归调用 permute。
希望我现在已经给出了足够的上下文来解释该函数的工作原理。 permute 的最后一次迭代发生在没有剩余 colls 时(即 (nil? remaining) 返回 true)。它只是 maps 在最后一个 coll 中给出的任何功能。
当还有剩余的 coll 时,它 mapcats 一个 permute 变体向下当前 coll。这个置换变体使用带有匿名参数的f 的部分函数,并且是permuteing 剩余的colls。通过这样做,它将逐步构建一个部分函数,一旦它到达 colls 列表的末尾,最终将被调用。然后,在我的脑海中,我想象它向后追溯,调用嵌套的mapcats,直到它最终解开并产生重新组合的 colls。
我想这个函数虽然简洁,但可能不是最佳的。坦率地说,我没有太多的 CS 背景,但从我读到的有关 Clojure 的内容来看,使用 loop/recur 而不是自递归调用往往更“有效”。如果优化对您很重要,我想重新设计函数以使用 loop/recur 会相当简单。