一尘不染

求m和n之间的所有整数,它们的平方除数之和本身就是一个平方

algorithm

问题问题

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]]

阅读 315

收藏
2020-07-28

共1个答案

一尘不染

蛮力计算因素

首先计算:

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其唯一的参数作为参数,并trueq满足所需属性的false情况下返回,否则返回。然后我们将计算:

(m..n).select { |q| magic_number?(q) }

返回一个数组,其中包含之间的所有数字,mn满足该属性。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是的因素nfn/f,都是的因素n。(例如,由于3是的因数42,所以也是如此`42/3

=> 14`)。因此,我们只需要获取每对中较小的一个即可。

此规则有一个例外。如果n是和的理想平方f == n**0.5,则f = n/f,因此,我们仅将包括f在内n(而不是n/f)。

如果发现if 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_mapObject#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之间的产品03 2的,通过乘以之间02 3的,乘以01 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
的每个类使用EnumerableArray是一个,但是(m..n).class #=> Range是另一个。(请参见Range的第二段)。
3.假设f小于n/ff > n**0.5,那么n/f < n/(n**0.5) = n**0.5 < f是一个矛盾。

2020-07-28