1

Simple code to find a perfect square

def perfect_square(num)
  (1..num).each {|n| return true if n*n == num }
  false    
end``

Is there a way to get the first line to return false instead of having to use a new line with just false below it?

duckets615
  • 45
  • 3

6 Answers6

2

Since the product of perfect squares is a perfect square, using Prime#prime_division:

require 'prime'

def perfect_square?(n)
  n.prime_division.all? { |_, exp| exp % 2 == 0 }
end
iGian
  • 11,023
  • 3
  • 21
  • 36
  • Clever! It seems to that the argument is simply that a number is prime if and only there are an even number of each of its primes factors (which I think makes it obvious), and not that the product of PS's is a PS. No? Maybe I misunderstand. – Cary Swoveland Mar 19 '20 at 23:27
  • Hi, thanks @CarySwoveland! Consider the square root as a power with fractional exponent: `n**(1 / 2)` and consider the property of powers where `m**2 * m**2 * m**2 = m**6`. Now if `n = m**6`, so `n**(1 / 2) = (m**6)**(1 / 2)` which is equal to `m**3`. Consider also that `(h**(1 / 2) * k**(1 / 2) = (h * k)**(1 / 2)` (for non complex). So I must check that all exponents of the prime factors of a number can be divided by `2`, which means square rooted. If this sounds clear, I'll add to the post. – iGian Mar 20 '20 at 06:30
  • Here is a formal explanation. Every positive integer `z` can be expressed `z = s*s*t`, where `s` equals `x1*x2*...*xn` and `t` equals `y1*y2...ym` or `1`, all `xi` are prime factors (with possible duplicates) and all `yi` are distinct prime factors (though each `yi` may equal some `xj`). For example, as `864 = (2**5)*(3**3)`, `s = 2*2*3 (12)` and `t = 2*3 (6)` (`12*12*6 = 864`)... – Cary Swoveland Mar 20 '20 at 16:23
  • ...The square root of `z` therefore equals `s*sqrt(t)`, meaning that `z` is a perfect square if and only if `sqrt(t)` is an integer. However, the square root of the product of distinct primes is known to be irrational ([proof](https://math.stackexchange.com/questions/2270520/proof-the-square-root-of-the-product-of-two-distinct-primes-is-irrational)). It follows that `z` is a perfect square if and only if the number of each of its prime factors is even. – Cary Swoveland Mar 20 '20 at 17:05
  • Perhaps it would be clearer to say, "If `z = (a1**m1)*(a2**m2)*...*(an**mn)`, where `ai` are `z`'s prime factors, then it follows that `z` is a perfect square if and only if all exponents `mi` are even numbers." – Cary Swoveland Mar 20 '20 at 20:13
  • 1
    @CarySwoveland, this sounds good. Maybe the statement should be "It follows that `z` is a perfect square if and only if the **exponent** of each of its prime factors is even."? – iGian Mar 21 '20 at 06:45
  • ah, I forgot about Primes. cool idea. I always found it odd though that we have to require primes even though its built into Ruby. – duckets615 Mar 21 '20 at 19:51
1

Newlines are optional in Ruby, so you can always trivially implement anything, no matter how complex, as a one-liner by simply removing all newlines, potentially replacing them with the appropriate alternative separator:

def perfect_square(num) (1..num).each {|n| return true if n*n == num }; false end
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Cool, thanks. I didn't know the lines were irrelevant when the code runs. Something like this was what I was looking for when I posted but looking at the other solutions it looks like my original code wasn't very efficient. – duckets615 Mar 21 '20 at 19:48
1

Ruby has Integer::sqrt since version 2.5

def perfect_square(num)
  Integer.sqrt(num)**2 == num
end
steenslag
  • 79,051
  • 16
  • 138
  • 171
1

For fun, here's another way:

def perfect_square(num)
  (1..1+num/2).find { |n| n*n >= num }**2 == num
end

(1..200).select { |num| perfect_square(num) }
  #=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196]

For greater efficiency the operative line could be replaced with

  (1..1+num/2).bsearch { |n| n*n >= num }**2 == num

See Array#bsearch.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
1

I thought it might be interesting to benchmark the various methods. Any surprises?

require 'benchmark'
require 'prime'

def duckets(num)
  (1..1+num/2).each {|n| return true if n*n == num }
  false    
end

def steenslag(num)
  Integer.sqrt(num)**2 == num
end

def iGian(num)
  num.prime_division.all? { |_, exp| exp % 2 == 0 }
end

def cary(num)
  (1..1+num/2).bsearch { |n| n*n >= num }**2 == num
end

def test(arr, meth)
  arr.map { |num| method(meth).call(num) }
end

This method maps each element of arr to true if it is a perfect square (else false), using method meth (a symbol).

def benchem(arr)
  Benchmark.bm do |x|
    x.report("enumerate")      { test(arr, :duckets)   }
    x.report("integer_sqrt")   { test(arr, :steenslag) }
    x.report("prime_divisors") { test(arr, :iGian)     }
    x.report("bsearch")        { test(arr, :cary)      }
  end
end

benchem([*2..100])
    user           system     total       real
  enumerate       0.000244   0.000000   0.000244 (  0.000229)  #3
  integer_sqrt    0.000049   0.000000   0.000049 (  0.000048)  #1
  prime_divisors  0.000963   0.000000   0.000963 (  0.000967)  #4
  bsearch         0.000086   0.000000   0.000086 (  0.000086)  #2

benchem([*101..1000])
    user           system     total       real
  enumerate       0.018636   0.000000   0.018636 (  0.018662)  #4
  integer_sqrt    0.000252   0.000000   0.000252 (  0.000255)  #1
  prime_divisors  0.003402   0.000000   0.003402 (  0.003407)  #3
  bsearch         0.000870   0.000000   0.000870 (  0.000872)  #2

benchem([*1001..10000])
    user           system     total       real
  enumerate       1.345501   0.000000   1.345501 (  1.345650)  #4 
  integer_sqrt    0.004647   0.000000   0.004647 (  0.004655)  #1
  prime_divisors  0.048204   0.000000   0.048204 (  0.048226)  #3
  bsearch         0.010349   0.000000   0.010349 (  0.010362)  #2

benchem([*10001..100000])
    user           system     total      real
  enumerate     147.814253   0.000000 147.814253 (147.833672)  #4
  integer_sqrt    0.036118   0.000000   0.036118 (  0.036115)  #1
  prime_divisors  0.889245   0.000000   0.889245 (  0.889367)  #3
  bsearch         0.127438   0.000000   0.127438 (  0.127459)  #2
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • wow, this is awesome. I mean i came in last,which sucks, since I was essentially counting on my fingers (i'm still learning) but still awesome. Seeing the time and resources in a visual like this really helps emphasize the differences between various approaches. – duckets615 Mar 22 '20 at 01:02
0

You can do this instead

def perfect_square(num)
  !(1..num).find { |n| n * n == num }.nil?    
end

Find any number from 1 to num that its square is num, check if it's not nil.

Kien Le
  • 191
  • 5