Clojure 查找小于或等于给定整数n且与给定整数s之和的不同正整数i、j和k的所有有序三元组
这是SICP中的练习2.41 我自己写过这个天真的版本:Clojure 查找小于或等于给定整数n且与给定整数s之和的不同正整数i、j和k的所有有序三元组,clojure,sicp,Clojure,Sicp,这是SICP中的练习2.41 我自己写过这个天真的版本: (defn sum-three [n s] (for [i (range n) j (range n) k (range n) :when (and (= s (+ i j k)) (< 1 k j i n))] [i j k])) (定义和三[ns] (适用于[i(范围n) j(范围n) k(范围n) :何时(及(=s(+i j k
(defn sum-three [n s]
(for [i (range n)
j (range n)
k (range n)
:when (and (= s (+ i j k))
(< 1 k j i n))]
[i j k]))
(定义和三[ns]
(适用于[i(范围n)
j(范围n)
k(范围n)
:何时(及(=s(+i j k))
(<1 k j i n))]
[i j k]))
问题是:这在clojure中被认为是惯用的吗?我如何优化这段代码?因为计算(三个500之和)需要永远的时间
另外,我如何让这个函数接受一个额外的参数来指定整数的个数来计算和?因此,它应该处理更一般的情况,如二之和、四之和或五之和等,而不是三之和
我想这不能通过使用for
循环来实现?不确定如何动态添加i j k绑定 (更新:完全优化的版本是底部的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])
更新:反而破坏了使用它的练习,但提供了一个专门为解决此问题而定制的组合
功能:
(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
适当地过滤输出
进一步的更新:通过以上工作的方式思考sum-c
,可以提供进一步的优化思路。sum-c
中的内部go
函数的要点是生成一系列的元组,将它们相加到某个目标值(其初始目标减去中当前迭代时的i
值,以便理解);然而,我们仍然验证从对go
的递归调用返回的元组的总和,就好像我们不确定它们是否真的完成了它们的工作一样
相反,我们可以通过构造确保生成的元组是正确的:
这个版本以列表的形式返回元组,以便在保持代码结构的同时保持预期的结果顺序,而这种方法是自然的。您可以使用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]
为了进一步保证它做同样的事情(除了归纳证明两种情况下的正确性之外):
(更新:完全优化的版本在底部为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])
更新:反而破坏了使用它的练习,但提供了一个专门为解决此问题而定制的组合
功能:
(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
适当地过滤输出
进一步的更新:通过以上工作的方式思考sum-c
,可以提供进一步的优化思路。sum-c
中的内部go
函数的要点是生成一系列的元组,将它们相加到某个目标值(其初始目标减去中当前迭代时的i
值,以便理解);然而,我们仍然验证从对go
的递归调用返回的元组的总和,就好像我们不确定它们是否真的完成了它们的工作一样
相反,我们可以通过构造确保生成的元组是正确的:
这个版本以列表的形式返回元组,以便在保持代码结构的同时保持预期的结果顺序,而这种方法是自然的。您可以使用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]
为了进一步保证它做同样的事情(除了归纳证明两种情况下的正确性之外):
for是一个宏,因此很难扩展您的惯用回答来涵盖一般情况。幸运的是,clojure.math.combinationics提供了笛卡尔积函数,该函数将生成数字集的所有组合。这减少了过滤组合的问题:
(ns hello.core
(:require [clojure.math.combinatorics :as combo]))
(defn sum-three [n s i]
(filter #(= s (reduce + %))
(apply combo/cartesian-product (repeat i (range 1 (inc n))))))
hello.core> (sum-three 7 10 3)
((1 2 7) (1 3 6) (1 4 5) (1 5 4) (1 6 3) (1 7 2) (2 1 7)
(2 2 6) (2 3 5) (2 4 4) (2 5 3) (2 6 2) (2 7 1) (3 1 6)
(3 2 5) (3 3 4) (3 4 3) (3 5 2) (3 6 1) (4 1 5) (4 2 4)
(4 3 3) (4 4 2) (4 5 1) (5 1 4) (5 2 3) (5 3 2) (5 4 1)
(6 1 3) (6 2 2) (6 3 1) (7 1 2) (7 2 1))
假设答案中的顺序很重要,因为这是一个宏,所以很难扩展您的惯用答案来涵盖一般情况。幸运的是,clojure.math.combinationics提供了笛卡尔积函数,该函数将生成数字集的所有组合。这减少了过滤组合的问题:
(ns hello.core
(:require [clojure.math.combinatorics :as combo]))
(defn sum-three [n s i]
(filter #(= s (reduce + %))
(apply combo/cartesian-product (repeat i (range 1 (inc n))))))
hello.core> (sum-three 7 10 3)
((1 2 7) (1 3 6) (1 4 5) (1 5 4) (1 6 3) (1 7 2) (2 1 7)
(2 2 6) (2 3 5) (2 4 4) (2 5 3) (2 6 2) (2 7 1) (3 1 6)
(3 2 5) (3 3 4) (3 4 3) (3 5 2) (3 6 1) (4 1 5) (4 2 4)
(4 3 3) (4 4 2) (4 5 1) (5 1 4) (5 2 3) (5 3 2) (5 4 1)
(6 1 3) (6 2 2) (6 3 1) (7 1 2) (7 2 1))
假设答案中的顺序很重要,要使现有代码参数化,您可以使用reduce
。此代码显示了一种模式,您可以使用该模式来参数化宏用法的案例数
不使用
宏的
代码(仅使用函数)将是:
(defn sum-three [n s]
(mapcat (fn [i]
(mapcat (fn [j]
(filter (fn [[i j k]]
(and (= s (+ i j k))
(< 1 k j i n)))
(map (fn [k] [i j k]) (range n))))
(range n)))
(range n)))
(定义和三[ns]
(mapcat(fn[i]