Optimization 循环函数花费的时间太长
我正在尝试执行一个函数,该函数实现了n个多维数据集的总和: 1^3+2^3+3^3+…+n^3=总和 我的函数应该接收一个Optimization 循环函数花费的时间太长,optimization,clojure,Optimization,Clojure,我正在尝试执行一个函数,该函数实现了n个多维数据集的总和: 1^3+2^3+3^3+…+n^3=总和 我的函数应该接收一个sum,如果n不存在,则返回一个n或-1 一些例子: (find-n 9) ; should return 2 because 1^3 + 2^3 = 9 (find-n 100) ; should return 4 because 1^3 + 2^3 + 3^3 + 4^3 = 100 (find-n 10) ; should return -1 经过一些工作,我完
sum
,如果n
不存在,则返回一个n
或-1
一些例子:
(find-n 9) ; should return 2 because 1^3 + 2^3 = 9
(find-n 100) ; should return 4 because 1^3 + 2^3 + 3^3 + 4^3 = 100
(find-n 10) ; should return -1
经过一些工作,我完成了以下两个功能:
; aux function
(defn exp-3 [base] (apply *' (take 3 (repeat base))))
; main function
(defn find-n [m]
(loop [sum 0
actual-base 0]
(if (= sum m)
actual-base
(if (> sum m)
-1
(recur (+' sum (exp-3 (inc actual-base))) (inc actual-base))))))
这些函数工作正常,但使用BigNumbers
评估操作的时间太长,例如:
(def sum 1025247423603083074023000250000N)
(time (find-n sum))
; => "Elapsed time: 42655.138544 msecs"
; => 45001000
我问这个问题是为了提出一些建议,如何使这个函数更快。您可能需要使用一些数学技巧
(a-k)^3 + (a+k)^3 = 2a^3+(6k^2)a
那么,一个类似于:
(a-4)^3+(a-3)^3+(a-2)^3+(a-1)^3+a^3+(a+1)^3+(a+2)^3+(a+3)^3+(a+4)^3
= 9a^3+180a
(请确认计算的正确性)
使用这个等式,而不是每次递增1,您可以跳跃9(或任意2k+1)。当你碰到大于n的数字时,你可以检查准确的数字
另一种改进方法是,通过一次批量计算并在函数find-n中稍后使用此表,创建一个包含ns和sum的表。只需避免
应用
(在CLJ中并非真的那么快)即可使速度提高4倍:
(defn exp-3 [base]
(*' base base base))
还有10%:
(defn find-n [m]
(loop [sum 0
actual-base 0]
(if (>= sum m)
(if (= sum m) actual-base -1)
(let [nb (inc actual-base)]
(recur (+' sum (*' nb nb nb)) nb)))))
这都是关于代数的,与Clojure或编程无关。因为这个网站不支持数学排版,让我们用Clojure来表达它 定义
(defn sigma [coll] (reduce + coll))
及
(或
)
然后问题是,给定n
,找到i
,这样(=(sigma-1-to-n#(*%%)i)n)
快速实现这一点的关键在于立方体。它告诉我们,对于任何自然数i
,以下各项是相等的:
(#(*' % %) (sigma-1-to-n identity i))
(sigma-1-to-n #(* % % %) i)
(#(*' % %) (/ (*' i (inc i)) 2))
所以,作为立方体的和,这个数
- 一定是个完美的正方形
- 它的平方根是前几个数的和
(defn perfect-square-root [n]
(let [candidate (-> n double Math/sqrt Math/round)]
(when (= (*' candidate candidate) n)
candidate)))
如果参数不是完全平方,则返回nil
现在我们有了平方根,我们必须确定它是否是一系列自然数的和:在普通代数中,对于某些自然数,它是(j(j+1))/2
我们可以使用类似的技巧直接回答这个问题
j (j + 1) = (j + 1/2)^2 + 1/4
因此,如果存在参数,则以下函数将返回添加到参数中的连续数:
(defn perfect-sum-of [n]
(let [j (-> n (*' 2)
(- 1/4)
double
Math/sqrt
(- 0.5)
Math/round)]
(when (= (/ (*' j (inc j)) 2) n)
j)))
我们可以将这些功能结合起来,以满足您的需求:
(defn find-n [big-i]
{:pre [(integer? big-i) ((complement neg?) big-i)]}
(let [sqrt (perfect-square-root big-i)]
(and sqrt (perfect-sum-of sqrt))))
(def sum 1025247423603083074023000250000N)
(time (find-n sum))
"Elapsed time: 0.043095 msecs"
=> 45001000
(请注意,时间大约比以前快了20倍,这可能是因为HotSpot必须进行find-n
,这已经通过附加的测试得到了彻底的验证)
这显然比原来的要快得多
警告 我担心由于浮点精度有限,上述过程可能会产生假阴性(它永远不会产生假阳性)。然而,测试表明,对于问题使用的数字类型,该程序是不可破解的
Java
double
有52位精度,大约15.6位小数。令人担忧的是,对于比这个大得多的数字,该过程可能会错过精确的整数解,因为舍入只能与它开始时的浮点数一样精确
但是,该过程正确地解决了31位整数的示例。使用许多(一千万!)类似的数字进行测试不会产生一次失败
为了测试该解决方案,我们生成了一个(惰性)的
[limit cube sum]
对序列:
(defn generator [limit cube-sum]
(iterate
(fn [[l cs]]
(let [l (inc l)
cs (+' cs (*' l l l))]
[limit cs]))
[limit cube-sum]))
比如说,
(take 10 (generator 0 0))
=> ([0 0] [1 1] [2 9] [3 36] [4 100] [5 225] [6 441] [7 784] [8 1296] [9 2025])
现在我们
- 从给定的示例开始
- 尝试下一千万个案例,然后
- 去掉那些有用的
(remove (fn [[l cs]] (= (find-n cs) l)) (take 10 (generator 45001001 1025247423603083074023000250000N)))
=>
([45001001 1025247423603083074023000250000N]
[45001002 1025247514734170359564546262008N]
[45001003 1025247605865263720376770289035N]
[45001004 1025247696996363156459942337099N]
[45001005 1025247788127468667814332412224N]
[45001006 1025247879258580254440210520440N]
[45001007 1025247970389697916337846667783N]
[45001008 1025248061520821653507510860295N]
[45001009 1025248152651951465949473104024N]
[45001010 1025248243783087353664003405024N])
一切都应该失败,他们确实失败了 以下基于算法的方法依赖于一个简单的公式,即前N个自然数的立方体之和为:
(N*(N+1)/2)^2
(定义多维数据集的和
“(n*(n+1)/2)^2”
[n]
(让[n'(/(*'n(inc n))2]
(*‘n’n’))
(定义查找第n个立方体)
[n]
((fn[开始-结束前]
(let[avg(bigint(/(+'开始-结束)2))
立方体(立方体平均值之和)]
(条件(=立方n)平均值
(==多维数据集上一页)-1
(>多维数据集n)(重复开始平均多维数据集)
(45001000N
我们想找到一个数N,这样1..N个立方体的和就是某个数X。要找到这样一个数是否存在,我们可以通过应用上面的公式在某个范围内对它进行二元搜索,看看公式的结果是否等于X。这种方法是有效的,因为顶部的函数是递增的,因此任何值f(N)
太大意味着我们必须寻找一个较小的数字n
,而任何f(n)
太小的值意味着我们必须寻找一个较大的数字n
我们选择0到X的范围(比必要的范围大,但容易且安全)。如果我们应用于给定候选数字的公式产生X,我们将知道该数字存在。如果不存在,我们将继续二进制搜索,直到找到该数字,或者直到我们尝试相同的数字两次,这表明该数字不存在
由于上限为
logN
,计算1E100(1 googol)只需1毫秒,因此对于算法方法来说非常有效。在Clojure中,如果不存在某个值,则返回nil
。我对该值的有效性表示怀疑。你可能会更安全。看起来很健壮。我不明白为什么,但我不能打破它。我会在答案的最后扔掉证据。我没有意识到apply
这么慢。。。我将使用apply仔细考虑
(take 10 (generator 0 0))
=> ([0 0] [1 1] [2 9] [3 36] [4 100] [5 225] [6 441] [7 784] [8 1296] [9 2025])
(remove (fn [[l cs]] (= (find-n cs) l)) (take 10000000 (generator 45001000 1025247423603083074023000250000N)))
=> ()
(remove (fn [[l cs]] (= (find-n cs) l)) (take 10 (generator 45001001 1025247423603083074023000250000N)))
=>
([45001001 1025247423603083074023000250000N]
[45001002 1025247514734170359564546262008N]
[45001003 1025247605865263720376770289035N]
[45001004 1025247696996363156459942337099N]
[45001005 1025247788127468667814332412224N]
[45001006 1025247879258580254440210520440N]
[45001007 1025247970389697916337846667783N]
[45001008 1025248061520821653507510860295N]
[45001009 1025248152651951465949473104024N]
[45001010 1025248243783087353664003405024N])
(defn sum-of-cube
"(n*(n+1)/2)^2"
[n]
(let [n' (/ (*' n (inc n)) 2)]
(*' n' n')))
(defn find-nth-cube
[n]
((fn [start end prev]
(let [avg (bigint (/ (+' start end) 2))
cube (sum-of-cube avg)]
(cond (== cube n) avg
(== cube prev) -1
(> cube n) (recur start avg cube)
(< cube n) (recur avg end cube))))
1 n -1))
(time (find-nth-cube 1025247423603083074023000250000N))
"Elapsed time: 0.355177 msecs"
=> 45001000N