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