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]]
答案 0 :(得分:3)
因素的强力计算
首先计算:
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
作为唯一参数,如果true
满足所需属性,则返回q
,否则返回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
,那么f
,那么我们只在n
(不是n/f
因子中包含f
好)。
如果结果是f <=(n**0.5).round
是对中的较小者,(1..(n**0.5).round)
3 。因此,我们只需要检查哪些数字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
代替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]
(“链接”表达式),我们获得了一个典型的Ruby表达式:
.sort
请参阅Enumerable#flat_map和Object#tap。 (不需要对这个数组进行排序。在需要对其进行排序的应用程序中,只需将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]
块的末尾。)
结束
总之,我们留下以下内容:
n = 360
这一计算在眨眼间完成。
计算素数以进一步加快因子的计算
最后,让我描述一种使用方法Prime::prime_division计算数字因子的更快方法。该方法将任何数字分解为其主要组件。例如,考虑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
和{ {1}} 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
的实例方法的Enumerable#map可供include
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
是一个矛盾。
答案 1 :(得分:1)
我不了解Ruby,但问题在于找到数字除数的算法(这不是特定于所使用的语言,即本例中的Ruby)。
r = (m..n).to_a.map { |z| (1..z).select { |x| z % x == 0} }
要查找整数n
的除数,您要将n
除以所有正整数到n - 1
,这意味着循环运行n - 1
次。但是,将sort(n)
除以计算除数就足够了。在伪代码中,这看起来如下:
for i = 1 to i <= sqrt(n)
r = n % i
if r == 0 then
i is a divisor
if n / i != i then
n / i is another divisor
例如:
sqrt_42 = 6.48074069840786
i = 1 => 1 and 42 are two divisors
i = 2 => 2 and 21
i = 3 => 3 and 14
i = 4 => no divisor
i = 5 => no divisor
i = 6 => 6 and 7
就是这样。
这将大大提高性能,因为现在循环仅运行sort(n)
次而不是n - 1
次,这对于大n
来说是一个很大的区别。