(更新:完全优化的版本sum-c-opt
在底部。)
我会说这是惯用的,如果不是在保持惯用语的同时做到这一点的最快方法的话。好吧,也许在输入已知为数字时使用==
代替会更惯用(注意。这些在数字上并不完全等价;不过在这里没关系。)=
作为第一个优化过程,您可以从更高的范围开始并替换=
为特定于数字的==
:
(defn sum-three [n s]
(for [k (range n)
j (range (inc k) n)
i (range (inc j) n)
:when (== s (+ i j k))]
[i j k]))
(更改了绑定的顺序,因为您希望最后的数字最小。)
至于将整数的数量作为参数,这是一种方法:
(defn sum-c [c n s]
(letfn [(go [c n s b]
(if (zero? c)
[[]]
(for [i (range b n)
is (go (dec c) n (- s i) (inc i))
:when (== s (apply + i is))]
(conj is i))))]
(go c n s 0)))
;; from the REPL:
user=> (sum-c 3 6 10)
([5 4 1] [5 3 2])
user=> (sum-c 3 7 10)
([6 4 0] [6 3 1] [5 4 1] [5 3 2])
更新:宁可破坏使用它的练习,但math.combinatorics提供了一个combinations
为解决这个问题而量身定制的函数:
(require '[clojure.math.combinatorics :as c])
(c/combinations (range 10) 3)
;=> all combinations of 3 distinct numbers less than 10;
; will be returned as lists, but in fact will also be distinct
; as sets, so no (0 1 2) / (2 1 0) "duplicates modulo ordering";
; it also so happens that the individual lists will maintain the
; relative ordering of elements from the input, although the docs
; don't guarantee this
filter
输出适当。
进一步更新:通过sum-c
上述工作方式的思考给出了进一步的优化想法。内部go
函数的重点sum-c
是生成一个元组序列,总和为某个目标值(其初始目标减去i
当前迭代中的值for
);然而,我们仍然验证从递归调用返回的元组的总和,就go
好像我们不确定它们是否真的完成了他们的工作一样。
相反,我们可以通过构造确保生成的元组是正确的:
(defn sum-c-opt [c n s]
(let [m (max 0 (- s (* (dec c) (dec n))))]
(if (>= m n)
()
(letfn [(go [c s t]
(if (zero? c)
(list t)
(mapcat #(go (dec c) (- s %) (conj t %))
(range (max (inc (peek t))
(- s (* (dec c) (dec n))))
(min n (inc s))))))]
(mapcat #(go (dec c) (- s %) (list %)) (range m n))))))
此版本将元组作为列表返回,以便保留预期的结果顺序,同时保持代码结构,这在这种方法下是很自然的。您可以通过传递将它们转换为向量map vec
。
对于较小的参数值,这实际上会比 慢sum-c
,但对于较大的值,它要快得多:
user> (time (last (sum-c-opt 3 500 500)))
"Elapsed time: 88.110716 msecs"
(168 167 165)
user> (time (last (sum-c 3 500 500)))
"Elapsed time: 13792.312323 msecs"
[168 167 165]
并且只是为了进一步保证它做同样的事情(除了在两种情况下归纳证明正确性之外):
; NB. this illustrates Clojure's notion of equality as applied
; to vectors and lists
user> (= (sum-c 3 100 100) (sum-c-opt 3 100 100))
true
user> (= (sum-c 4 50 50) (sum-c-opt 4 50 50))
true