在Clojure中按比例减少列表值

在Clojure中按比例减少列表值,clojure,Clojure,我有一个小的编程问题,我正试图在Clojure中解决 比如说,我有一个包含整数值的列表,它们也包括零。这些值有一个和,我想减少一个特定的值。为了得到这个较低的总和,我想按比例减少列表中的值 比如说,我有以下列表:[0,10,30,40,20,0]。总数是100,我想把总数减少到90。我想按比率减少这些值,因此新列表将是[0,9,27,36,18,0] 然而,当数字变成分数时,这就成了问题。当你用round、floor或ceil对数字进行四舍五入时,你可以得到一个1或2的和。我似乎找不到一个优雅的

我有一个小的编程问题,我正试图在Clojure中解决

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

比如说,我有以下列表:[0,10,30,40,20,0]。总数是100,我想把总数减少到90。我想按比率减少这些值,因此新列表将是[0,9,27,36,18,0]

然而,当数字变成分数时,这就成了问题。当你用round、floor或ceil对数字进行四舍五入时,你可以得到一个1或2的和。我似乎找不到一个优雅的解决办法。我得到的所有东西都包括一次遍历所有值,然后返回修复偏移。有什么想法吗

编辑

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


其他要求是,数字只能保持相等或变小,数字应>=0,生成的数字列表应为整数。

我们可以使用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))))))
原始答案

下面是一个函数,用于将集合中的每个数字乘以一个比率,以达到所需的总和:

(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}

有关处理舍入错误的示例,请参见上面的更新,或有关处理负数的先前编辑。

我们可以使用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))))))
原始答案

下面是一个函数,用于将集合中的每个数字乘以一个比率,以达到所需的总和:

(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}

有关处理舍入错误的示例,请参见上面的更新,或有关处理负数的先前编辑。

我认为如果不再次查看列表以修复舍入,就没有办法解决此问题。以下是一个解决方案:

定义调整顺序 [输入比率四舍五入] 让;; ;将比率应用于数字的函数 ;; 多重比率部分*比率 ;; ;;用于对数字应用比率和舍入的函数 ;; mul-ratio-r comp四舍五入mul比 ;; ;;对原始输入进行排序,首先使用最大余数 然后对每个数字应用比率和四舍五入 ;; 四舍五入列表->>输入 按排序-多个比率-r% mul比率% 图mul-ratio-r ;; 原数之和 ;; 总和输入减少+输入 ;; ;计算期望和与所有四舍五入数字之和之间的差值 ;; delta-mul-ratio-r和输入减少+四舍五入列表] ;; ;; 按最大余数顺序将增量分配到舍入数 ;; ->>四舍五入名单 减少fn[[保留]e] ;; 如果剩余增量大于1,则将数字增加1 如果pos?保持 [dec Leime公司] ;; 否则,按原样返回舍入的数字 [0 e] ;; delta是提供给还原函数的初始值 [三角洲] ;; ;; 忽略缩减函数的第一个输出,即原始增量 ;; 休息 ;; ;; 获取调整后的数字:比率+舍入+增量调整 ;; 最后一张地图 和一个示例运行:

def输入[0 10 30 40 20 0] def比率0.83 def舍入整数 减少输入 ;; => 100 *比率*1 ;; => 83 调整顺序输入比率四舍五入 ;; => 25 17 8 33 0 0 减少+*1 ;; => 83
我不认为有办法解决这个问题,不需要第二次检查列表来修正舍入。这里有一个溶胶 使用:

定义调整顺序 [输入比率四舍五入] 让;; ;将比率应用于数字的函数 ;; 多重比率部分*比率 ;; ;;用于对数字应用比率和舍入的函数 ;; mul-ratio-r comp四舍五入mul比 ;; ;;对原始输入进行排序,首先使用最大余数 然后对每个数字应用比率和四舍五入 ;; 四舍五入列表->>输入 按排序-多个比率-r% mul比率% 图mul-ratio-r ;; 原数之和 ;; 总和输入减少+输入 ;; ;计算期望和与所有四舍五入数字之和之间的差值 ;; delta-mul-ratio-r和输入减少+四舍五入列表] ;; ;; 按最大余数顺序将增量分配到舍入数 ;; ->>四舍五入名单 减少fn[[保留]e] ;; 如果剩余增量大于1,则将数字增加1 如果pos?保持 [dec Leime公司] ;; 否则,按原样返回舍入的数字 [0 e] ;; delta是提供给还原函数的初始值 [三角洲] ;; ;; 忽略缩减函数的第一个输出,即原始增量 ;; 休息 ;; ;; 获取调整后的数字:比率+舍入+增量调整 ;; 最后一张地图 和一个示例运行:

def输入[0 10 30 40 20 0] def比率0.83 def舍入整数 减少输入 ;; => 100 *比率*1 ;; => 83 调整顺序输入比率四舍五入 ;; => 25 17 8 33 0 0 减少+*1 ;; => 83 这是你需要的吗

defn尺度向量 给定's',一个数字序列和't',一个 序列,返回类似's'的序列,但每个数字都已缩放 适当地。 [ST] let[比率/减少+过滤器数量?s t] 映射如果数?%/%比率%s 标度向量[1020:foo3045.3027/3]21 =>1.837270341207349 3.674540682414698:foo 5.511811023622047 8.32283464566929 0.0 1.6535433070866141 减少+过滤器数量?标度向量[1020:foo3045.3027/3]21 => 21.0 这里发生了什么:

我们假设s是一个数字序列;但如果某个元素不是数字,则不一定是错误。过滤数字使我们能够优雅地处理一些非数字元素;我选择保留非数值元素,但您也可以删除它们。 我没有做什么特别的事情来从输出中排除有理数,我不明白为什么需要这样做;但如果您想这样做,可以使用MapDouble[1 1/2 22/7]=>1.0 0.5 3.142857142857143。 但习惯上,在Clojure中,数字只是一个数字。任何接受数字的函数都应该接受数字。有理数——你们所说的“分数”——和其他数字一样,都是数字。别担心他们。 这是你需要的吗

defn尺度向量 给定's',一个数字序列和't',一个 序列,返回类似's'的序列,但每个数字都已缩放 适当地。 [ST] let[比率/减少+过滤器数量?s t] 映射如果数?%/%比率%s 标度向量[1020:foo3045.3027/3]21 =>1.837270341207349 3.674540682414698:foo 5.511811023622047 8.32283464566929 0.0 1.6535433070866141 减少+过滤器数量?标度向量[1020:foo3045.3027/3]21 => 21.0 这里发生了什么:

我们假设s是一个数字序列;但如果某个元素不是数字,则不一定是错误。过滤数字使我们能够优雅地处理一些非数字元素;我选择保留非数值元素,但您也可以删除它们。 我没有做什么特别的事情来从输出中排除有理数,我不明白为什么需要这样做;但如果您想这样做,可以使用MapDouble[1 1/2 22/7]=>1.0 0.5 3.142857142857143。 但习惯上,在Clojure中,数字只是一个数字。任何接受数字的函数都应该接受数字。有理数——你们所说的“分数”——和其他数字一样,都是数字。别担心他们。
我不清楚你想做什么。您似乎希望对输入列表中的数字进行某种比率,以便输出列表的总和等于给定的数字。正如你所指出的,你并不总能得到积分结果。但是你没有提到你在这种情况下的期望。另外,请包括你尝试过的。我不清楚你想做什么。您似乎希望对输入列表中的数字进行某种比率,以便输出列表的总和等于给定的数字。正如你所指出的,你并不总能得到积分结果。但是你没有提到你的花费
本例为ct。另外,请包括你已经尝试过的。太棒了,我喜欢你在其中加入一个规范。adjust-sum-int有一个小问题。如果第一个值是0,那么调整要么使其成为不允许的负数,要么使其增加不允许的值,所有数字应保持相等或向下。我想最好是更新第一个非零值,或者更好,更新最高值。我看到了,是的,但我仍然不确定我是更喜欢这个还是使用最大余数法的答案。它看起来更干净。太棒了,我喜欢你在里面加上一个规范。adjust-sum-int有一个小问题。如果第一个值是0,那么调整要么使其成为不允许的负数,要么使其增加不允许的值,所有数字应保持相等或向下。我想最好是更新第一个非零值,或者更好,更新最高值。我看到了,是的,但我仍然不确定我是更喜欢这个还是使用最大余数法的答案。看起来更干净。谢谢!我喜欢你检查非数字!但是,我希望结果列表由整数组成。我更新了原来的问题来澄清这一点。啊!然后,您需要映射在结果上转换为整数的内容。e、 g.地图int'22/7 3.14 0.001。当然,您也可以使用map int Math/ceil%result。这里没有一个方便的数学/循环,但如果这是您需要的,请使用map int+0.5%的结果。请注意还有一个问题:Clojure中的基本+-*和/函数仅适用于64位值。如果结果溢出了一个64位的值,就会出现一个异常,这很愚蠢。没有很好的文档记录的是,有替代函数+'、-'、*'和/'可以正确处理超过64位的值。这样做的性能损失非常小,我建议在任何地方都使用任意精度版本,但需要优化性能的代码除外。是的,除了在转换为整数时,舍入可能会导致总和不正确。谢谢!我喜欢你检查非数字!但是,我希望结果列表由整数组成。我更新了原来的问题来澄清这一点。啊!然后,您需要映射在结果上转换为整数的内容。e、 g.地图int'22/7 3.14 0.001。当然,您也可以使用map int Math/ceil%result。这里没有一个方便的数学/循环,但如果这是您需要的,请使用map int+0.5%的结果。请注意还有一个问题:Clojure中的基本+-*和/函数仅适用于64位值。如果结果溢出了一个64位的值,就会出现一个异常,这很愚蠢。没有很好的文档记录的是,有替代函数+'、-'、*'和/'可以正确处理超过64位的值。这样做的性能损失非常小,我建议在任何地方都使用任意精度版本,性能需要优化的代码除外。是的,除了在转换为整数时,舍入可能会导致总和不正确。谢谢,特别是指向所用方法的链接,这是我需要的信息,我找不到开始的地方。让我看看我是否能完全理解你的代码,遗憾的是它比我希望的要复杂,但我担心这就是问题的本质。谢谢你,特别是指向所用方法的链接,这是我需要的信息,我找不到开始的地方。让我看看我是否能完全理解你的代码,遗憾的是它比我希望的要复杂,但我担心这就是问题的本质。