关于递归:Clojure从递归调用中返回值

Clojure returning values from recursive call

我正在研究Clojure算法,以解决此处提出的问题:http://spin.atomicobject.com/2011/05/31/use-clojure-to-move-drugs-a-programming-challenge/而且我已经打h了。

我正在使用一种递归算法(也许不是正确的选择),以遍历"玩偶"结构的矢量,这些矢量按权重从高到低的顺序排序。相关代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(defn get-carryable-dolls
  [dolls carryable-dolls]
  (def doll (first dolls)) ;initializing for use in multiple places
  (def rest-dolls (rest dolls)) ;initializing for use in multiple places
  (
    if (will-fit? doll (get-weight-sum carryable-dolls))
    ( ;will fit
      (
        if
        (= carryable-dolls {})
        (def new-doll-set [doll]) ;First trip, get rid of empty set by initializing new
        (def new-doll-set (flatten [doll carryable-dolls])) ;otherwise, flatten set into vector of structs
      )
      ;tests to see if we have any more dolls to test, and if so, recurses. Otherwise, should pass the resultant vector
      ;up the stack. it appears to be the"else" potion of this if statement that is giving me problems.
      (if (not= () rest-dolls) (get-carryable-dolls rest-dolls new-doll-set) (vec new-dolls))
    )
    ( ;will not fit
      ;gets the rest of the dolls, and sends them on without modifying the vector of structs
      ;it appears to be the"else" potion of this if statement that is giving me problems.
      (if (not= () rest-dolls) (get-carryable-dolls rest-dolls carryable-dolls) (vec carryable-dolls))
    )
  )
)

该代码正常运行; returnable-dolls包含所需的洋娃娃结构向量,以作为解决方案返回。不幸的是,当我尝试将returnable-dolls向量返回到调用位置时,我收到以下错误:

1
CompilerException clojure.lang.ArityException: Wrong number of args (0) passed to: PersistentVector,compiling:(drugmover\\tests.clj:83)

第82-83行读为:

1
2
(def empty-dolls {})
(def designated-dolls (get-carryable-dolls sorted-values empty-dolls))

对于可能导致编译器错误的原因,我感到很困惑,并且由于Clojure似乎更喜欢简洁的错误消息,而不是堆栈跟踪(或至少Clooj中的REPL功能确实如此),因此我处于失去了如何解决它。如果有人有任何建议,我将不胜感激!

预先感谢。

编辑:

我已经用答案和注释中的建议修改方法修改了代码,并提供了一些注释以帮助说明正在进行的流控制。希望通过说明我的想法,有人可以让我知道我要去哪里错了。


以下代码合并了您在其他答案和其他答案中已经收到的大多数建议,即:

  • 摆脱多余的(和错误的)括号
  • 以更惯用的方式格式化代码
  • 使用loop / let代替def进行本地名称绑定
  • 使用seq检查空列表
  • 在添加元素之前,删除不必要的空carryable-dolls seq检查

如果没有定义您的辅助功能(例如will-fit?),我将无法对其进行测试,但是至少应该解决一些问题(并使代码更具可读性):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defn get-carryable-dolls
  [dolls carryable-dolls]
  (loop [doll (first dolls)
         rest-dolls (rest dolls)]
    (if (will-fit? doll (get-weight-sum carryable-dolls))
      (let [new-doll-set (if (seq carryable-dolls)
                           (cons doll carryable-dolls)
                           [doll])]
        (if (seq rest-dolls)
          (recur rest-dolls new-doll-set)
          (vec new-dolls)))
      (if (seq rest-dolls)
        (recur rest-dolls carryable-dolls)
        (vec carryable-dolls)))))

以下是对代码的完整重构,该代码利用标准的reduce函数并定义了一个函数,该函数提供了核心决策逻辑(是否必须在结果中包含一个玩偶):

1
2
3
4
5
6
7
(defn add-if-fits [dolls doll]
  (if (will-fit? doll (get-weighted-sum dolls))
    (cons doll carryable-dolls)
    carryable-dolls))

(defn get-carryable-dolls [dolls carryable-dolls]
  (reduce add-if-fits carryable-dolls dolls))


您在if代码周围有多余的括号,这就是该错误的原因:

下面的代码将产生相同的错误,因为它具有额外的括号(如果在调用else部分以创建矢量时,情况与此相同):

1
((vec {:a 10 :b 100}))

尝试在REPL中执行此操作,您将看到相同的异常:

1
java.lang.IllegalArgumentException: Wrong number of args (0) passed to: PersistentVector (NO_SOURCE_FILE:0)

此代码中有太多的原因,它们会引起问题。我强烈建议您以其他所有人的方式设置代码格式,这将使这样的错误很容易被发现。我什至猜不到您要做什么,所以我无法重写整个代码段,但需要注意的相关事项是if的语法是:

1
(if test then else)

在所有这些情况下都不允许使用多余的括号:例如,(if true 1 2)很好,但是(if (true) 1 2)会尝试将true作为函数调用,但由于它是布尔值而失败。如果要将表达式"分组"在一起并评估它们的副作用,则需要(do expr1 expr2),而不是(expr1 expr2)