因子的暴力计算
你从计算开始:
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:
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 作为唯一的参数,如果q 满足所需的属性,则返回true,否则返回false。然后我们将计算:
(m..n).select { |q| magic_number?(q) }
返回一个由m 和n 之间满足该属性的所有数字组成的数组。 magic_number?可以这样写:
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。我们可以使用您的代码来获取因子:
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 #=> 14 也是)。因此,我们只需要获得每对中较小的那个。
这条规则有一个例外。如果n是一个完美的正方形并且f == n**0.5,那么f = n/f,所以我们在n的因子中只包括f(不包括n/f)。
如果结果证明f 是这对中较小的一个,f <=(n**0.5).round3。因此,我们只需要检查(1..(n**0.5).round) 中的哪些数字是因数并包括它们的补数(除非n 是一个完美的正方形,在这种情况下我们不会重复计算(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表达式:
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 块的末尾即可。)
总结
总之,我们剩下以下几点:
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。
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 的。因此:
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,矛盾。