找到m和n之间的所有整数,其平方除数之和本身就是一个平方

时间:2016-01-20 03:41:02

标签: arrays ruby 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]]

2 个答案:

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

解决方案的结构

让我们向后工作以提出我们的代码。首先,集中精力确定任何给定数字qq因子的平方和是否为完美平方。假设我们构造一个方法magic_number?,它将q作为唯一参数,如果true满足所需属性,则返回q,否则返回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 

加快计算因素

我们可以采取更多措施来加快因素的计算。如果fnfn/f的因子,则n的因子都是3。 (例如,由于4242/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_mapObject#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的每个因素都是32 0之间的乘积,乘以2和{ {1}} 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的实例方法的Enumerable#map可供include Enumerable的每个类使用。 Array是一个,(m..n).class #=> Range是另一个。 (见Range的第二段)。 3。假设f小于n/ff > 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来说是一个很大的区别。