我正在学习Ruby,目前这是我的练习:
使用任何一对数字,证明整数n是一个完美的力量。如果没有对,则返回nil。
在数学中,完美的幂是一个正整数,可以表示为另一个正整数的整数幂。更正式地说,如果存在自然数m,则n是完美的幂。 1,和k> 1使得mk = n
目前这是我的代码:
def pp(n)
# [m,k] m^k
idx1=2
while idx1 <n
idx2=2
while idx2<n
if idx1**idx2 == n
ans = [idx1,idx2]
break
end
idx2 +=1
end
idx1 +=1
end
return ans
end
我想写这样的话,对于给定的随机数字,我的repl.it不会超时。
提前谢谢!
###Edit###
在Python的上下文中提到了这个问题(How to make perfect power algorithm more efficient?)。尽管我试图理解它并翻译语法,但事实并非如此。我希望提出这个问题也会对那些研究Ruby的人有所帮助。
答案 0 :(得分:5)
您可以使用素数分解:
require 'prime'
def pp(n)
pd = n.prime_division
k = pd.map(&:last).reduce(&:gcd)
return if k < 2
m = pd.map { |p, e| p**(e / k) }.reduce(:*)
[m, k]
end
例如,对于n = 216
,您得到[[2, 3], [3, 3]]
,意思是216 = 2 3 ⋅3 3 。然后找到指数的最大公约数,这是最大可能的指数k
。如果它小于2,你输了。否则,从碎片中计算基础m
。
你的电脑需要4.1秒才能检查2到200的数字。我的电脑大约需要0.0005秒,检查数字2到400000大约需要2.9秒。
答案 1 :(得分:1)
想要瞬间解决这么大的数字吗?有一个更短的解决方案?
pp(908485319620418693071190220095272672648773093786320077783229)
=> [29, 41]
然后阅读:-)。这将是一个小小的旅程......
让我们首先让你的代码更好,而不用改变逻辑。只需将变量重命名为m
和k
,然后使用each
循环:
def pp1(n)
ans = nil
(2...n).each do |m|
(2...n).each do |k|
if m**k == n
ans = [m, k]
break
end
end
end
ans
end
现在检查n最多200我需要大约4.1秒(与原始相同):
> require 'benchmark'
> puts Benchmark.measure { (1..200).each { |n| pp1(n) } }
4.219000 0.000000 4.219000 ( 4.210381)
首先优化:如果我们找到解决方案,请立即返回!
def pp2(n)
(2...n).each do |m|
(2...n).each do |k|
if m**k == n
return [m, k]
end
end
end
nil
end
可悲的是,测试仍需要3.9秒。下一个优化:如果m k 已经太大了,那就不要尝试更大的k!
def pp3(n)
(2...n).each do |m|
(2...n).each do |k|
break if m**k > n
if m**k == n
return [m, k]
end
end
end
nil
end
现在它的速度非常快,我可以在大约相同的时间内运行1000次测试:
> puts Benchmark.measure { 1000.times { (1..200).each { |n| pp3(n) } } }
4.125000 0.000000 4.125000 ( 4.119359)
让我们改为n = 5000:
> puts Benchmark.measure { (1..5000).each { |n| pp3(n) } }
2.547000 0.000000 2.547000 ( 2.568752)
现在不要再计算m**k
,而是使用一个我们可以更便宜地更新的新变量:
def pp4(n)
(2...n).each do |m|
mpowk = m
(2...n).each do |k|
mpowk *= m
break if mpowk > n
if mpowk == n
return [m, k]
end
end
end
nil
end
可悲的是,这几乎没有让它变得更快。但还有另一个优化:当m k 太大时,我们不仅可以忘记用更大的k来尝试这个m。如果它对于k = 2而言太大,即m 2 已经太大,那么我们也不需要尝试更大的m。我们可以停止整个搜索。我们试试吧:
def pp5(n)
(2...n).each do |m|
mpowk = m
(2...n).each do |k|
mpowk *= m
if mpowk > n
return if k == 2
break
end
if mpowk == n
return [m, k]
end
end
end
nil
end
现在这可以在大约0.07秒内完成5000次测试!我们甚至可以在6秒内检查所有数字达到100000:
> puts Benchmark.measure { (1..100000).each { |n| pp5(n) } }
5.891000 0.000000 5.891000 ( 5.927859)
无论如何,让我们看一下大局。我们正在尝试m = 2..
,对于每个m
,我们尝试找到k
以便m**k == n
。嘿,数学有一个解决方案!我们可以计算 k
为k = log m (n)。我们这样做:
def pp6(n)
(2...n).each do |m|
k = Math.log(n, m).round
return if k < 2
return [m, k] if m**k == n
end
nil
end
再次测量:
> puts Benchmark.measure { (1..100000).each { |n| pp6(n) } }
28.797000 0.000000 28.797000 ( 28.797254)
嗯,慢一点。好的,另一个想法是:让外部循环用于k
而不是m
。现在对于给定的n和k,我们如何找到m使得m k = n?取第k个根!
def pp7(n)
(2...n).each do |k|
m = (n**(1.0 / k)).round
return if m < 2
return [m, k] if m**k == n
end
nil
end
再次测量:
> puts Benchmark.measure { (1..100000).each { |n| pp7(n) } }
0.828000 0.000000 0.828000 ( 0.836402)
尼斯。 400000怎么样,我的其他答案中的分解解决方案在2.9秒内解决了?
> puts Benchmark.measure { (1..400000).each { |n| pp(n) } }
3.891000 0.000000 3.891000 ( 3.884441)
好的,这有点慢。但是......通过这个解决方案,我们可以解决真正大数字:
pp7(1000000035000000490000003430000012005000016807)
=> [1000000007, 5]
pp7(908485319620418693071190220095272672648773093786320077783229)
=> [29, 41]
> pp7(57248915047290578345908234051234692340579013460954153490523457)
=> nil
所有这些结果立即出现。因子分解解决方案也快速解决了29 41 的情况,但对于1000000007 5 情况和最终的随机类型情况它很慢,因为因子分解很慢。
P.S。注意,对数和平方根得到浮点数。这可能导致数量非常大的问题。例如:
irb(main):122:0> pp7(10**308 + 1)
=> nil
irb(main):123:0> pp7(10**309 + 1)
FloatDomainError: Infinity
from (irb):82:in `round'
from (irb):82:in `block in pp7'
from (irb):81:in `each'
from (irb):81:in `pp7'
from (irb):123
from C:/Ruby24/bin/irb.cmd:19:in `<main>'
在这种情况下,那是因为10 309 对于花车来说太大了:
> (10**309).to_f
=> Infinity
此外,数量足够大可能存在准确性问题。无论如何,您可以通过为对数和根写入整数版本来解决这些问题。但那是另一个问题。