Wednesday, January 15, 2014

Finding Whole Squares for a given number range in Powershell


I have never been very good at math.  My daughter, however, is a whiz and I find myself upgrading my skill sets to make sure I stay ahead of her enough to qualify as a mentor(!).  As part of her math team efforts, she needed to find the number of whole squares from 0..1000 inclusive.  This is a pretty simple problem  which can be done on a blackboard, calculator or in Powershell:

[math]::sqrt(1000)
31.6227766016838

There are 31 whole squares in the number range 0..1000.  You can find 999 of the squares from 0..1000 and filter out all the whole squares from that group like as in the truncated result below:

rv -ea 0 a
for ($i=1; $i -lt 1000; $i++) {[array]$a+=$([math]::sqrt($i))}
0..($a.count - 1) | % {$a.item($PSItem)} | ? {([float]$PSItem - [int]$PSItem) -eq 0}
1
2
3
...
29
30
31

With not too much more effort you can create a hashtable of all these squares and their square roots:

foreach ($i in $a) {$c+=[ordered]@{$i=$([math]::pow([double]$i,2));}}


Name                           Value
----                           -----
1                              1
1.4142135623731                2
1.73205080756888               3
2                              4
2.23606797749979               5
2.44948974278318               6
2.64575131106459               7
2.82842712474619               8
3                              9
3.16227766016838               10
...



It doesn't take too long to do this for a range 50K in length:

PS C:\ps1>  function sq {
>>  rv -ea 0 a
>>  rv -ea 0 b
>>  for ($i=1E6; $i -lt 1.05E6; $i++) {[array]$global:a+=$([math]::sqrt($i))}
>>  0..($a.count - 1) | % {$a.item($PSItem)} | ? {([float]$PSItem - [int]$PSItem) -eq 0}
>>  }
>>
PS C:\ps1>  measure-command{[array]$b=sq}

Days              : 0
Hours             : 0
Minutes           : 3
Seconds           : 25
Milliseconds      : 48
Ticks             : 2050483832
TotalDays         : 0.00237324517592593
TotalHours        : 0.0569578842222222
TotalMinutes      : 3.41747305333333
TotalSeconds      : 205.0483832
TotalMilliseconds : 205048.3832

PS C:\ps1> $a.count
50000
PS C:\ps1> $b.count
25
PS C:\ps1> $a[0..10]
1000
1000.00049999988
1000.0009999995
1000.00149999888
1000.001999998
1000.00249999688
1000.0029999955
1000.00349999388
1000.003999992
1000.00449998988
1000.0049999875
PS C:\ps1> $a[-1..-10]
1024.69458864581
1024.69410069542
1024.6936127448
1024.69312479396
1024.69263684287
1024.69214889156
1024.69166094001
1024.69117298823
1024.69068503622
1024.69019708398
PS C:\ps1> $b[0..10]
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
PS C:\ps1> $b[-1..-10]
1024
1023
1022
1021
1020
1019
1018
1017
1016
1015

PS C:\ps1> foreach ($i in $a) {$c+=[ordered]@{$i=$([math]::pow([double]$i,2));}}
PS C:\ps1> $c

Name                           Value
----                           -----
1000                           1000000
1000.00049999988               1000001
1000.0009999995                1000002
1000.00149999888               1000003
1000.001999998                 1000004
1000.00249999688               1000005
1000.0029999955                1000006
1000.00349999388               1000007
1000.003999992                 1000008
1000.00449998988               1000009
1000.0049999875                1000010
1000.00549998488               1000011
...

Another approach is using 'System.Collections' directly. There doesn't seem to be much optimization although I attempted to measure this carefully:

rv -ea 0 a
rv -ea 0 b
rv -ea 0 ArrayList
rv -ea 0 Hash

(measure-command {for ($i=1; $i -lt 10000; $i++) {[array]$a+=[math]::sqrt($i)}}).Milliseconds
(measure-command {foreach ($i in $a) {[array]$b+=[ordered]@{$i=[math]::pow([double]$i,2);}}}).Milliseconds

$Hash = New-Object System.Collections.Hashtable
$ArrayList=New-Object System.Collections.ArrayList
(measure-command {for ($i=1; $i -lt 10000; $i++) {$ArrayList+=$([math]::sqrt($i))}}).Milliseconds
(measure-command {foreach ($i in $ArrayList) {[array]$hash+=[ordered]@{$i=[math]::pow([double]$i,2)};}}).Milliseconds

I can use Chart-hashdata.ps1 to produce this chart:



Someone who understands the math behind deriving non-negative, non-integer (irrational) square roots will have to explain the chart below. I am using Windows 7 and  .NET 4.5.50938.


$Hash = New-Object System.Collections.Hashtable
$ArrayList=New-Object System.Collections.ArrayList
for ($i=1; $i -lt 10000; $i++) {$ArrayList+=$([math]::sqrt($i))}
foreach ($i in $ArrayList) {[array]$hash+=[ordered]@{$i=[math]::abs([math]::pow([double]$i,2) - [math]::pow([float]$i,2))};}


No comments: