For loop 在这个递归Clojure代码中,`for`是如何工作的?
我是个初学者。以下是我试图理解的一些代码(Clojure课程的一个不错的开端的一页):For loop 在这个递归Clojure代码中,`for`是如何工作的?,for-loop,recursion,clojure,list-comprehension,For Loop,Recursion,Clojure,List Comprehension,我是个初学者。以下是我试图理解的一些代码(Clojure课程的一个不错的开端的一页): 果然,(子集和{1 2 3 4}4)返回({1 3}{1 3}{4}) 但是为什么子集总和帮助器的第3行必须返回[当前集]?这难道不会返回一个([{13}][{13}][{4}])的最终答案吗 我尝试删除第3行中的括号,使函数开始如下: (defn subset-sum-helper [a-set current-set target] (if (= (sum current-set) target)
果然,
(子集和{1 2 3 4}4)
返回({1 3}{1 3}{4})
但是为什么子集总和帮助器的第3行必须返回[当前集]
?这难道不会返回一个([{13}][{13}][{4}])
的最终答案吗
我尝试删除第3行中的括号,使函数开始如下:
(defn subset-sum-helper [a-set current-set target]
(if (= (sum current-set) target)
current-set
(let ...
现在(子集和{1 2 3 4}4)
返回(1 3 3 4)
,这使得它看起来像让
不累加三个集合{1 3}、{1 3}和{4},而是只累加“裸”数,给出(1 3 1 3 4)
所以subset sum helper
在递归计算中使用列表理解for
,我不知道发生了什么。当我尝试可视化这个递归计算时,我发现自己在问,“那么当
(subset-sum-helper a-set
(conj current-set elem)
target)
不返回答案,因为从起点看不可能有答案?”(我最好的猜测是它返回[]
或类似的东西。)我不理解教程作者在写这篇文章时的意思,“这就是我们在基本情况下返回向量的原因,这样我们就可以以这种方式使用for
。”
如果你能给我任何帮助,我将不胜感激。谢谢 subset sum helper
函数始终返回一系列解决方案。如果未满足目标
,则for
表达式末尾的解决方案
正文将枚举此类序列。当满足target
时,只需返回一个解决方案:current set
参数。它必须作为一个元素的序列返回。有很多方法可以做到这一点:
[current-set] ; as given - simplest
(list current-set)
(cons current-set ())
(conj () current-set)
...
如果导致立即从子集和帮助器返回(无递归),您将看到向量:
否则,您将看到由
为生成的序列,其打印方式类似于列表:
=> (subset-sum (set (range 1 10)) 7)
(#{1 2 4}
#{1 2 4}
#{1 6}
#{1 2 4}
#{1 2 4}
#{2 5}
#{3 4}
#{1 2 4}
#{1 2 4}
#{3 4}
#{2 5}
#{1 6}
#{7})
当无法回答时,subset sum helper
返回一个空序列:
=> (subset-sum-helper #{2 4 6} #{} 19)
()
再一次,这是打印,好像它是一个列表
该算法存在以下问题:
- 它会多次找到每个解决方案-一个解决方案
的s
次的阶乘(计数s)
- 如果采用的元素
超出目标,则 无用地尝试添加剩余elem
集合的每个排列
如果我们稍微重铸一下,代码就更容易理解了
subset sum helper
的递归调用完整地传递第一个和第三个参数。如果我们使用letfn
使此函数成为子集和
的局部函数,我们可以不使用这些参数:它们是从上下文中提取的。现在看起来是这样的:
(defn subset-sum [a-set target]
(letfn [(subset-sum-helper [current-set]
(if (= (reduce + current-set) target)
[current-set]
(let [remaining (clojure.set/difference a-set current-set)]
(for [elem remaining
solution (subset-sum-helper (conj current-set elem))]
solution))))]
(subset-sum-helper #{})))
。。。其中对sum
函数的单个调用已内联展开
现在相当清楚的是,
subset sum helper
正在返回包含其单个当前集
参数的解决方案。for
表达式正在枚举a-set
的每个元素elem
(不在当前集
中)包含当前集和元素的解决方案。它正在为所有这些元素连续地这样做。因此,从所有解决方案都包含的空集开始,它会生成所有的空集 也许这个解释能帮助你:
首先,我们可以在最小的代码中试验函数的预期行为(带括号和不带括号),但删除递归相关的代码
带括号:
(for [x #{1 2 3}
y [#{x}]]
y)
=> (#{1} #{2} #{3})
(for [x #{1 2 3}
y #{x}]
y)
=> (1 2 3)
不带括号:
(for [x #{1 2 3}
y [#{x}]]
y)
=> (#{1} #{2} #{3})
(for [x #{1 2 3}
y #{x}]
y)
=> (1 2 3)
带括号,括号内有更多元素*:**
(for [x #{1 2 3}
y [#{x} :a :b :c]]
y)
=> (#{1} :a :b :c #{2} :a :b :c #{3} :a :b :c)
因此(在本例中)需要括号以避免在集合上迭代
如果我们不使用括号,我们将把“x”作为y的绑定值,如果我们使用括号,我们将把#{x}作为y的绑定值
换句话说,代码作者需要一个集合,而不是在其for中作为绑定值在集合上迭代。所以她把一个集合放进一个序列“[#{x}”
和总结“for”函数采用一个或多个绑定形式/集合表达式对的向量 因此,如果“集合表达式”是{:a},迭代结果将是(:a),但是如果“集合表达式”是[{:a}],迭代结果将是({:a})
很抱歉,我的解释太多,但很难清楚这些细微差别只是为了好玩,这里有一个更干净的解决方案,仍然使用
for
:
(defn subset-sum [s target]
(cond
(neg? target) ()
(zero? target) (list #{})
(empty? s) ()
:else (let [f (first s), ns (next s)]
(lazy-cat
(for [xs (subset-sum ns (- target f))] (conj xs f))
(subset-sum ns target)))))
我现在已经更新了我的答案。这个解释帮助我看到了我思维中的一个错误。我混淆了
中的绑定行为(让[foo{xyz}]…)
和中的迭代行为(对于[foo{xyz}]…)
。两者的语法相同,但行为却截然不同!