按Clojure中的比率减少列表值

Mar*_* tM 1 clojure

我在Clojure中尝试解决一个编程小问题。

说,我有一个带有整数值的列表(它们也包括零)。这些值有一个总和,我想减少一定值。为了获得较低的总和,我想按比例减小列表中的值。

说,我有以下列表:[0,10,30,40,20,0]。总和为100,我想将总和减少为90。我想按比例减少值,因此新列表将为[0,9,27,36,18,0]。

但是,当数字变成小数时,这将成为问题。当您对数字进行四舍五入时(无论是四舍五入,最小或最小),最终的总和都将减少1或2。我似乎找不到完美的解决方案。我得到的一切都包括一次遍历所有值,然后返回以修复偏移量。有任何想法吗?

编辑

为了弄清楚我想看到的行为,四舍五入的方式对我来说并不重要,只要总和正确且数字的比率大致相同即可。我不在乎总错误是最小的还是最大的舍入。

附加要求是仅允许数字保持相等或降低,数字应> = 0,并且结果数字列表应为整数。

Tay*_*ood 5

我们可以使用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))))))
Run Code Online (Sandbox Code Playgroud)

然后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}
Run Code Online (Sandbox Code Playgroud)

原始答案

这是一个将集合中每个数字乘以一个比率以达到所需总和的函数:

(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)
Run Code Online (Sandbox Code Playgroud)

对于您的示例,结果自然以大整数形式出现。这是唯一给出的示例,但是此问题非常适合基于属性的生成测试。我们可以定义所有示例都应具有的属性,并使用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}
Run Code Online (Sandbox Code Playgroud)

有关处理带舍入错误的示例,请参见上面的更新;有关处理负数的示例,请参见上面的更新。