For loop 在这个递归Clojure代码中,`for`是如何工作的?

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)

我是个初学者。以下是我试图理解的一些代码(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)
    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}]…)
。两者的语法相同,但行为却截然不同!