42的除数是:1、2、3、6、7、14、21、42。这些除数的平方是:1、4、9、36、49、196、441、1764。平方除数的总和是2500这是50 * 50,一个正方形!
给定两个整数m,n(1 <= m <= n),我们希望找到m和n之间的所有整数,它们的平方除数之和本身就是一个平方。42就是这样一个数字。
结果将是一个数组数组,每个子数组都有两个元素,首先是平方除数为平方的数字,然后是平方除数之和。
如何使此特定程序运行更快?我的当前代码在n> 9999之后超时。
#returns the divisors of each number in an array of arrays r = (m..n).to_a.map { |z| (1..z).select { |x| z % x == 0} } #this finds all integers between m and n whose sum of squared divisors is itself a square squarenumbers = r.map { |x| x.map { |c| c**2 }.inject(:+) }.select { |x| Math.sqrt(x) % 1 == 0 } #returns an array of booleans. booleans = r.map { |x| x.map { |c| c**2 }.inject(:+) }.map { |x| Math.sqrt(x) % 1 == 0 } #returns the index of each of the true values in booleans as an array indexer = booleans.map.with_index{|x, i| i if x == true }.compact #returns the numbers whose squared divisors is a square in an array unsqr = indexer.map { |x| (m..n).to_a[x] } #merges the two arrays together, element for element and creates an array of arrays unsqr.zip(squarenumbers) # for m = 1 and n = 1000 the result would be # [[1, 1], [42, 2500], [246, 84100], [287, 84100], [728, 722500]]
蛮力计算因素
首先计算:
m, n = 40, 42 r = (m..n).to_a.map { |z| (1..z).select { |x| z % x == 0} } #=> [[1, 2, 4, 5, 8, 10, 20, 40], [1, 41], [1, 2, 3, 6, 7, 14, 21, 42]]
可以,但是您不需要.to_a:
.to_a
r = (m..n).map { |z| (1..z).select { |x| z % x == 0} } #=> [[1, 2, 4, 5, 8, 10, 20, 40], [1, 41], [1, 2, 3, 6, 7, 14, 21, 42]]
这避免了额外的步骤,即创建临时数组1,2:
(m..n).to_a #=> [40, 41, 42]
解决方案的结构
让我们向后工作以提出我们的代码。首先,对于任何给定的数字,集中精力确定q因子的平方和q本身是否是一个完美的平方。假设我们构造了一个方法magic_number?,该方法将q其唯一的参数作为参数,并true在q满足所需属性的false情况下返回,否则返回。然后我们将计算:
q
magic_number?
true
false
(m..n).select { |q| magic_number?(q) }
返回一个数组,其中包含之间的所有数字,m并n满足该属性。magic_number?可以这样写:
m
n
def magic_number?(q) return true if q == 1 s = sum_of_squared_factors(q) s == Math.sqrt(s).round**2 end
计算平方和
因此,现在我们只剩下编写方法了sum_of_squared_factors。我们可以使用您的代码来获取因素:
sum_of_squared_factors
def factors(q) (1..q).select { |x| q % x == 0 } end factors(40) #=> [1, 2, 4, 5, 8, 10, 20, 40] factors(41) #=> [1, 41] factors(42) #=> [1, 2, 3, 6, 7, 14, 21, 42]
然后写:
def sum_of_squared_factors(q) factors(q).reduce(0) { |t,i| t + i*i } end sum_of_squared_factors(40) #=> 2210 sum_of_squared_factors(41) #=> 1682 sum_of_squared_factors(42) #=> 2500
加快因子计算
我们还可以做更多的事情来加快因子的计算。如果f是的因素n,f和n/f,都是的因素n。(例如,由于3是的因数42,所以也是如此`42/3
f
n/f
3
42
此规则有一个例外。如果n是和的理想平方f == n**0.5,则f = n/f,因此,我们仅将包括f在内n(而不是n/f)。
f == n**0.5
f = n/f
如果发现if f是一对中较小的,f <=(n**0.5).round3。因此,我们只需要检查哪些数字(1..(n**0.5).round)是因子并包括它们的补数(除非n是一个完美的平方,在这种情况下我们就不会重复计算(n**0.5).round):
f <=(n**0.5).round
(1..(n**0.5).round)
(n**0.5).round
q = 42 arr = (1..Math.sqrt(q).round).select { |x| q % x == 0 } #=> [1, 2, 3, 6] arr = arr.flat_map { |n| [n, q/n] } #=> [1, 42, 2, 21, 3, 14, 6, 7] arr.pop if a[-2] == a[-1] arr #=> [1, 42, 2, 21, 3, 14, 6, 7] q = 36 arr = (1..Math.sqrt(q).round).select { |x| q % x == 0 } #=> [1, 2, 3, 4, 6] arr = arr.flat_map { |n| [n, q/n] } #=> [1, 36, 2, 18, 3, 12, 4, 9, 6, 6] arr.pop if a[-2] == a[-1] #=> 6 arr #=> [1, 36, 2, 18, 3, 12, 4, 9, 6]
所以我们可以这样写:
def factors(q) arr = (1..Math.sqrt(q)).select { |x| q % x == 0 } arr = arr.flat_map { |n| [n, q/n] } arr.pop if arr[-2] == arr[-1] arr end
替换掉arr(“链接”表达式),我们得到一个典型的Ruby表达式:
arr
def factors(q) (1..Math.sqrt(q)).select { |x| q % x == 0 }. flat_map { |n| [n, q/n] }. tap { |a| a.pop if a[-2] == a[-1] } end factors(42) #=> [1, 42, 2, 21, 3, 14, 6, 7] factors(36) #=> [1, 36, 2, 18, 3, 12, 4, 9, 6]
请参见Enumerable#flat_map和Object#tap。(无需对此数组进行排序。在需要对其进行排序的应用程序中,只需.sort将其粘贴到flat_maps块的末尾即可。)
.sort
flat_map
包起来
总而言之,我们剩下以下内容:
def magic_number?(q) return true if q == 1 s = sum_of_squared_factors(q) s == Math.sqrt(s).round**2 end def sum_of_squared_factors(q) factors(q).reduce(0) { |t,i| t + i*i } end def factors(q) (1..Math.sqrt(q)).select { |x| q % x == 0 }. flat_map { |n| [n, q/n] }. tap { |a| a.pop if a[-2] == a[-1] } end m, n = 1, 1000 (m..n).select { |q| magic_number?(q) } #=> `[1, 42, 246, 287, 728]
瞬间就完成了此计算。
计算素数以进一步加快因子计算
最后,让我描述一种使用Prime :: prime_division方法计算数字因子的更快方法。该方法将任何数字分解为其主要成分。考虑例如n = 360。
n = 360
require 'prime' Prime.prime_division(360) #=> [[2, 3], [3, 2], [5, 1]]
这告诉我们:
360 == 2**3 * 3**2 * 5**1 #=> true
这也告诉我们,每一个因素360之间的产品0及3 2的,通过乘以之间0和2 3的,乘以0或1 5的。因此:
360
0
2
1
5
def factors(n) Prime.prime_division(n).reduce([1]) do |a,(prime,pow)| a.product((0..pow).map { |po| prime**po }).map { |x,y| x*y } end end a = factors(360).sort #=> [ 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 18, # 20, 24, 30, 36, 40, 45, 60, 72, 90, 120, 180, 360]
我们可以检查一下:
a == (1..360).select { |n| (360 % n).zero? } #=> true
另一张支票:
factors(40).sort #=> [1, 2, 4, 5, 8, 10, 20, 40]
1.您可以改写那个[*m..n] #=> [40, 41, 42]。 2.为什么没有必要将范围转换为数组?Enumerable#map是模块的实例方法Enumerable,可供includes 的每个类使用Enumerable。Array是一个,但是(m..n).class #=> Range是另一个。(请参见Range的第二段)。 3.假设f小于n/f和f > n**0.5,那么n/f < n/(n**0.5) = n**0.5 < f是一个矛盾。
[*m..n] #=> [40, 41, 42]
Enumerable
include
Array
(m..n).class #=> Range
f > n**0.5
n/f < n/(n**0.5) = n**0.5 < f