我们可以使用 clojure.spec 指定函数的要求。如果我们希望函数支持任意精度的整数、总和为零的序列、空序列等,我们可以编写以下函数规范:
(s/def ::natural-integer (s/and integer? (comp not neg?)))
(s/fdef dec-sum-int
:args (s/and (s/cat :new-sum ::natural-integer
:nums (s/coll-of ::natural-integer))
#(<= (:new-sum %) (apply +' (:nums %))))
:ret (s/coll-of ::natural-integer)
:fn (fn [{:keys [args ret]}]
(and (= (count (:nums args)) (count ret))
;; each output <= corresponding input
(every? true? (map <= ret (:nums args)))
(or (empty? ret)
(= (:new-sum args) (apply + ret))))))
然后st/check 下面的原始答案可以查看失败的示例,或者查看带有s/exercise-fn 的示例调用。
这是一个满足您更新要求的规范的版本。大多数复杂性是在调整舍入误差时确保每个输出
(defn dec-sum-int [new-sum nums]
(let [sum (apply +' nums)
ratio (if (zero? sum) 1 (/ new-sum sum))
nums' (map #(bigint (*' % ratio)) nums)
err (- new-sum (apply + nums'))]
(loop [nums nums
nums' nums'
out []
err err]
(cond
(zero? err)
(into out nums')
(seq nums')
(let [[num & more] nums
[num' & more'] nums']
(if (pos? num)
(let [num'' (min num (+ num' err))]
(recur more more'
(conj out num'')
(- err (- num'' num'))))
(recur more more' (conj out num') err)))
:else out))))
(st/summarize-results (st/check `dec-sum-int))
{:sym playground.so/dec-sum-int}
=> {:total 1, :check-passed 1}
原答案
这是一个函数,可以将集合中的每个数字乘以一个比率以达到某个所需的总和:
(defn adjust-sum [new-sum nums]
(let [sum (apply + nums)]
(map #(* % (/ new-sum sum))
nums)))
(adjust-sum 90 [0 10 30 40 20 0])
=> (0N 9N 27N 36N 18N 0N)
(map int *1)
=> (0 9 27 36 18 0)
对于您的示例,结果自然会以大整数形式出现。这是唯一给出的例子,但这个问题很适合基于属性的生成测试。我们可以定义所有示例都应该持有的属性,并使用 test.check 来针对许多我们可能无法想象的随机示例测试该函数:
(tc/quick-check 10000
(prop/for-all [new-sum gen/int
nums (->> (gen/vector gen/int)
;; current approach fails for inputs that sum to zero
(gen/such-that #(not (zero? (apply + %)))))]
(= new-sum (apply + (adjust-sum new-sum nums)))))
=> {:result true, :num-tests 10000, :seed 1552170880184}
有关处理舍入错误示例的更新,或处理负数的先前编辑,请参阅上面的更新。