Code-challenge: How fast can you calculate 1000! with Powershell? - powershell

I have got a challenge to calculate 1000! with Powershell as fast as possible.
Here the given rules for this code-challenge:
no predefined arrays or strings (except for initial 0!-value)
no use of external modules or embedded C# code
routine must be correct for any input from 0 till 1000
result-string must be created as part of the measurement
Based on this conditions I could create the below code-snippet as a first draft.
Is there any idea to improve the speed? Inputs are more than welcome!
cls
Remove-Variable * -ea 0
$in = 1000
$runtime = measure-command {
# define initial arr with 0! = 1:
$arr = [System.Collections.Generic.List[uint64]]::new()
$arr.Add(1)
if ($in -gt 1) {
# define block-dimension per array-entry:
$posLen = 16
$multiplier = [uint64][math]::Pow(10,$posLen)
# calculate faculty:
$start = 0
foreach($i in 2..$in) {
$div = 0
if ($arr[$start] -eq 0){$start++}
foreach($p in $start..($arr.Count-1)) {
$mul = $i * $arr[$p] + $div
$arr[$p] = $mul % $multiplier
$div = [math]::Floor($mul/$multiplier)
}
if ($div -gt 0) {$arr.Add($div)}
}
}
# convert array into string-result:
$max = $arr.count-1
$faculty = $arr[$max].ToString()
if ($max -gt 1) {
foreach($p in ($max-1)..0) {
$faculty += ($multiplier + $arr[$p]).ToString().Substring(1)
}
}
}
# check:
if ($in -eq 1000 -and !$faculty.StartsWith('402387260077') -or $faculty.length -ne 2568) {
write-host 'result is not OK.' -f y
}
# show result:
write-host 'runtime:' $runtime.TotalSeconds 'sec.'
write-host "`nfaculty of $in :`n$faculty"

The fastest way is to rely on the existing multiplication capabilities of a data type designed specifically for large integers - like [bigint]:
$in = 1000
$runtime = Measure-Command {
# handle 0!
$n = [Math]::Max($in, 1)
$b = [bigint]::new($n)
while(--$n -ge 1){
$b *= $n
}
}
Clear-Host
Write-Host "Runtime: $($runtime.TotalSeconds)"
Write-Host "Factorial of $in is: `n$b"
This gives me a runtime of ~18ms, contrasting with ~300ms using your [uint64]-based carry approach :)
As Jeroen Mostert points out, you may be able to attain an additional improvement by side-stepping the *= operator and calling [BigInt]::Multiply directly:
# change this line
$b *= $n
# to this
$b = [bigint]::Multiply($b, $n)
I believe all the constraints are met as well:
no predefined arrays or strings (except for initial 0!-value)
Check!
no use of external modules or embedded C# code
Check! ([bigint] is part of the .NET base class library)
routine must be correct for any input from 0 till 1000
Check!
result-string must be created as part of the measurement
We're already tracking the result as an integer, thereby implicitly storing the string representation

Related

Find index of array where condition is true in PowerShell [duplicate]

I have trouble of getting index of the current element for multiple elements that are exactly the same object:
$b = "A","D","B","D","C","E","D","F"
$b | ? { $_ -contains "D" }
Alternative version:
$b = "A","D","B","D","C","E","D","F"
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" })
This will return:
D
D
D
But this code:
$b | % { $b.IndexOf("D") }
Alternative version:
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" }) | % { $b.IndexOf($_) }
Returns:
1
1
1
so it's pointing at the index of the first element. How to get indexes of the other elements?
You can do this:
$b = "A","D","B","D","C","E","D","F"
(0..($b.Count-1)) | where {$b[$_] -eq 'D'}
1
3
6
mjolinor's answer is conceptually elegant, but slow with large arrays, presumably due to having to build a parallel array of indices first (which is also memory-inefficient).
It is conceptually similar to the following LINQ-based solution (PSv3+), which is more memory-efficient and about twice as fast, but still slow:
$arr = 'A','D','B','D','C','E','D','F'
[Linq.Enumerable]::Where(
[Linq.Enumerable]::Range(0, $arr.Length),
[Func[int, bool]] { param($i) $arr[$i] -eq 'D' }
)
While any PowerShell looping solution is ultimately slow compared to a compiled language, the following alternative, while more verbose, is still much faster with large arrays:
PS C:\> & { param($arr, $val)
$i = 0
foreach ($el in $arr) { if ($el -eq $val) { $i } ++$i }
} ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Note:
Perhaps surprisingly, this solution is even faster than Matt's solution, which calls [array]::IndexOf() in a loop instead of enumerating all elements.
Use of a script block (invoked with call operator & and arguments), while not strictly necessary, is used to prevent polluting the enclosing scope with helper variable $i.
The foreach statement is faster than the Foreach-Object cmdlet (whose built-in aliases are % and, confusingly, also foreach).
Simply (implicitly) outputting $i for each match makes PowerShell collect multiple results in an array.
If only one index is found, you'll get a scalar [int] instance instead; wrap the whole command in #(...) to ensure that you always get an array.
While $i by itself outputs the value of $i, ++$i by design does NOT (though you could use (++$i) to achieve that, if needed).
Unlike Array.IndexOf(), PowerShell's -eq operator is case-insensitive by default; for case-sensitivity, use -ceq instead.
It's easy to turn the above into a (simple) function (note that the parameters are purposely untyped, for flexibility):
function get-IndicesOf($Array, $Value) {
$i = 0
foreach ($el in $Array) {
if ($el -eq $Value) { $i }
++$i
}
}
# Sample call
PS C:\> get-IndicesOf ('A','D','B','D','C','E','D','F') 'D'
1
3
6
You would still need to loop with the static methods from [array] but if you are still curious something like this would work.
$b = "A","D","B","D","C","E","D","F"
$results = #()
$singleIndex = -1
Do{
$singleIndex = [array]::IndexOf($b,"D",$singleIndex + 1)
If($singleIndex -ge 0){$results += $singleIndex}
}While($singleIndex -ge 0)
$results
1
3
6
Loop until a match is not found. Assume the match at first by assigning the $singleIndex to -1 ( Which is what a non match would return). When a match is found add the index to a results array.

Powershell command to move forward and backward in array

I would like to be able to add functionality to a PowerShell script to move one spot forwards or one spot backwards when in a foreach loop. While looping through a foreach statement, is it possible to move forwards and backwards or to identify what point in the array the current item lies?
Edit - the items in the array are files, not numbers
Yes - it's called a for loop!
For data structures that we can index into (arrays, lists, etc.), the foreach loop statement can easily be translated to a for loop statement, like so:
# This foreach loop statement
foreach($item in $array){
Do-Something $item
}
# ... is functionally identical to this for loop statement
for($i = 0; $i -gt $array.Length; $i++){
$item = $array[$i]
Do-Something $item
}
Since we have direct access to the current index (via $i) in the for loop, we can now use that as an offset to "look around" that position in the array:
$array = #(1,2,3,4)
for($i = 0; $i -lt $array.Length;$i++)
{
$item = $array[$i]
if($i -gt 0){
# If our index position $i is greater than zero,
# then there must be _at least_ 1 item "behind" us
$behind = $array[$i - 1]
}
if($i -lt ($array.Length - 1)){
# Not at end of array, we can also "look ahead"
$ahead = $array[$i + 1]
}
}
Be careful about checking that you don't use an $i value greater than $array.Length (which would result in $null) or less than 0 (in which case PowerShell will start reading the array backwards!)

Handle 4 For loops at once?

Introduction to question
I'm trying to search for the following values: '02-08-1997' and '01-08-1997' in string $b1. To find that value, I used searching for the starting and ending index of the values around it.
How did I do it?
To do this I used 4 For loops, only to execute the exact same thing over and over again. I know this can be done much simpler; so far I haven't found a way to do so yet. I have to write multiple scripts like this, so I really need to find a simpler - more easy to read - way to do so.
These are 2 of the For loops I'm using:
$b1 = 'set-aduser -identity 3423-234-23-42-432 dorstm -replace #{ geboortedatum = 01-08-1997 } waarde_org = 02-08-1997'
For ($i = 0; $i -lt $b1InArray.Length; $i++) {
if ($b1InArray[$i].contains("=")) {
$e = $i
break
}
}
For ($j = ($e + 1); $j -lt $b1InArray.Length; $j++) {
if ($b1InArray[$j].contains("}")) {
$f = $j
break
}
}
Looks like you're trying to use more of a Java solution. In Powershell you can use 'ForEach'
$b1 = 'set-aduser -identity 3423-234-23-42-432 dorstm -replace #{ geboortedatum = 01-08-1997 } waarde_org = 02-08-1997'
foreach ($b in $b1) {
if ( $b -contains '=') {
$f = $j
}
}
Note: $b in the foreach loop condition can be anything you want it to be just like $i in Java.
If you want to get the index of the strings you are searching for: there's a method for that:
$b1.IndexOf('02-08-1997')
$b1.IndexOf('01-08-1997')
No need to write loops. For simple things like this .Net has a working method for you.
Many ways to do this. This could work for you:
(Select-String -InputObject $b1 -allmatches -Pattern '\d{2}-\d{2}-\d{4}').matches.value
it will output all dates from your $b1 string as an array of strings. Assuming all dates have the same format like your example.
Fiddle with regex at https://regex101.com/

PowerShell: The difference in parentheses expressions

I've been experimenting with the different forms of operators/expressions involving parentheses, but I can't find an explanation for an interaction I'm running into. Namely, ( ) and $( ) (subexpression operator) are not equivalents. Nor is it equivalent to #( ) (array operator). For most cases this doesn't matter, but when trying to evaluate the contents of the parentheses as an expression (for example, variable assignment), they're different. I'm looking for an answer on what parentheses are doing when they aren't explicitly one operator or another and the about_ documents don't call this out.
($var = Test-Something) # -> this passes through
$($var = Test-Something) # -> $null
#($var = Test-Something) # -> $null
about_Operators
For the array and subexpression operators, the parentheses are simply needed syntactically. Their only purpose is to wrap the expression the operator should be applied on.
Some examples:
# always return array, even if no child items found
#(Get-ChildItem -Filter "*.log").Count
# if's don't work inside regular parentheses
$(if ($true) { 1 } else { 0 })
When you put (only) parentheses around a variable assignment, this is called variable squeezing.
$v = 1 # sets v to 1 and returns nothing
($v = 1) # sets v to 1 and returns assigned value
You can get the pass-thru version for all your examples by combining variable squeezing with the subexpression operator syntax (that is, adding a second pair of parentheses):
($var = Test-Something)
$(($var = Test-Something))
#(($var = Test-Something))
$( ) is unique. You can put multiple statements inside it:
$(echo hi; echo there) | measure | % count
2
You can also put things you can't normally pipe from, like foreach () and if, although the values won't come out until the whole thing is finished. This allows you to put multiple statements anywhere that just expects a value.
$(foreach ($i in 1..5) { $i } ) | measure | % count
5
$x = 10
if ( $( if ($x -lt 5) { $false } else { $x } ) -gt 20)
{$false} else {$true}
for ($i=0; $($y = $i*2; $i -lt 5); $i++) { $y }
$err = $( $output = ls foo ) 2>&1

PowerShell Compare-Object, results in context

I'm using Compare-Object in PowerShell to compare two XML files. It adequately displays the differences between the two using <= and =>. My problem is that I want to see the difference in context. Since it's XML, one line, one node, is different, but I don't know where that lives in the overall document. If I could grab say 5 lines before and 3 lines after it, it would give me enough information to understand what it is in context. Any ideas?
You can start from something like this:
$a = gc a.xml
$b = gc b.xml
if ($a.Length -ne $b.Length)
{ "File lenght is different" }
else
{
for ( $i= 0; $i -le $a.Length; $i++)
{
If ( $a[$i] -notmatch $b[$i] )
{
#for more context change the range i.e.: -2..2
-1..1 | % { "Line number {0}: value in file a is {1} - value in file b {2}" -f ($i+$_),$a[$i+$_], $b[$i+$_] }
" "
}
}
}
Compare-Object comes with an IncludeEqual parameter that might give what you are looking for:
[xml]$aa = "<this>
<they>1</they>
<they>2></they>
</this>"
[xml]$bb = "<this>
<they>1</they>
<they>2</they>
</this>"
Compare-Object $aa.this.they $bb.this.they -IncludeEqual
Result
InputObject SideIndicator
----------- -------------
1 ==
2 =>
2> <=