I want to fill up a dynamic array with the same integer value as fast as possible using Powershell.
The Measure-Command shows that it takes 7 seconds on my system to fill it up.
My current code (snipped) looks like:
$myArray = #()
$length = 16385
for ($i=1;$i -le $length; $i++) {$myArray += 2}
(Full code can be seen on gist.github.com or on superuser)
Consider that $length can change. But for better understanding I chose a fixed length.
Q: How do I speed up this Powershell code?
You can repeat arrays, just as you can do with strings:
$myArray = ,2 * $length
This means »Take the array with the single element 2 and repeat it $length times, yielding a new array.«.
Note that you cannot really use this to create multidimensional arrays because the following:
$some2darray = ,(,2 * 1000) * 1000
will just create 1000 references to the inner array, making them useless for manipulation. In that case you can use a hybrid strategy. I have used
$some2darray = 1..1000 | ForEach-Object { ,(,2 * 1000) }
in the past, but below performance measurements suggest that
$some2darray = foreach ($i in 1..1000) { ,(,2 * 1000) }
would be a much faster way.
Some performance measurements:
Command Average Time (ms)
------- -----------------
$a = ,2 * $length 0,135902 # my own
[int[]]$a = [System.Linq.Enumerable]::Repeat(2, $length) 7,15362 # JPBlanc
$a = foreach ($i in 1..$length) { 2 } 14,54417
[int[]]$a = -split "2 " * $length 24,867394
$a = for ($i = 0; $i -lt $length; $i++) { 2 } 45,771122 # Ansgar
$a = 1..$length | %{ 2 } 431,70304 # JPBlanc
$a = #(); for ($i = 0; $i -lt $length; $i++) { $a += 2 } 10425,79214 # original code
Taken by running each variant 50 times through Measure-Command, each with the same value for $length, and averaging the results.
Position 3 and 4 are a bit of a surprise, actually. Apparently it's much better to foreach over a range instead of using a normal for loop.
Code to generate above chart:
$length = 16384
$tests = '$a = ,2 * $length',
'[int[]]$a = [System.Linq.Enumerable]::Repeat(2, $length)',
'$a = for ($i = 0; $i -lt $length; $i++) { 2 }',
'$a = foreach ($i in 1..$length) { 2 }',
'$a = 1..$length | %{ 2 }',
'$a = #(); for ($i = 0; $i -lt $length; $i++) { $a += 2 }',
'[int[]]$a = -split "2 " * $length'
$tests | ForEach-Object {
$cmd = $_
$timings = 1..50 | ForEach-Object {
Remove-Variable i,a -ErrorAction Ignore
[GC]::Collect()
Measure-Command { Invoke-Expression $cmd }
}
[pscustomobject]#{
Command = $cmd
'Average Time (ms)' = ($timings | Measure-Object -Average TotalMilliseconds).Average
}
} | Sort-Object Ave* | Format-Table -AutoSize -Wrap
Avoid appending to an array in a loop. It's copying the existing array to a new array with each iteration. Do this instead:
$MyArray = for ($i=1; $i -le $length; $i++) { 2 }
Using PowerShell 3.0 you can use (need .NET Framework 3.5 or upper):
[int[]]$MyArray = ([System.Linq.Enumerable]::Repeat(2, 65000))
Using PowerShell 2.0
$AnArray = 1..65000 | % {2}
It is not clear what you are trying. I tried looking at your code. But, $myArray +=2 means you are just adding 2 as the element. For example, here is the output from my test code:
$myArray = #()
$length = 4
for ($i=1;$i -le $length; $i++) {
Write-Host $myArray
$myArray += 2
}
2
2 2
2 2 2
Why do you need to add 2 as the array element so many times?
If all you want is just fill the same value, try this:
$myArray = 1..$length | % { 2 }
If you need it really fast, then go with ArrayLists and Tuples:
$myArray = New-Object 'Collections.ArrayList'
$myArray = foreach($i in 1..$length) {
[tuple]::create(2)
}
and if you need to sort it later then use this (normally a bit slower):
$myArray = New-Object 'Collections.ArrayList'
foreach($i in 1..$length) {
$myArray.add(
[tuple]::create(2)
)
}
both versions are in the 20ms range for me ;-)
Related
I'd like to join two arrays by picking the elements from each array one by one. and not have them combined or simply merged
I know how to add a second array to the first one as in:
$array1 = (0,4,8)
$array2 = (1,5,2)
$array1 += $array2
$array1
Which results in the following:
0
4
8
1
5
2
But how can I copy them into one another giving me an output like this:
0
1
4
5
8
2
Note: I don't want to merge them and then sort the list.
The elements need to stay in the same order. How would that be achieved?
Although Esperento57 gives you a perfect working solution, here's my idea that will also allow for arrays that are not of the same length. It uses a System.Collections.ArrayList to add the values from the arrays for better performance if you have large arrays to combine.
$array1 = (0,2,4)
$array2 = (1,3,5,6,7,8)
$len1 = $array1.Length
$len2 = $array2.Length
$maxLength = [Math]::Max($len1, $len2)
$listResult = New-Object System.Collections.ArrayList
for ($i = 0; $i -lt $maxLength; $i++) {
if ($i -lt $len1) { [void] $listResult.Add($array1[$i]) }
if ($i -lt $len2) { [void] $listResult.Add($array2[$i]) }
}
$listResult.ToArray()
try something like this
$array1 = (0,2,4)
$array2 = (1,3,5)
$MaxLen=[Math]::Max($array1.Length, $array2.Length)
$Result=#()
for ($i = 0; $i -lt $MaxLen; $i++)
{
$Result+=$array1[$i]
$Result+=$array2[$i]
}
$Result
here's another way to do it. [grin]
this one takes into account dissimilar sizes in the arrays and interleaves them until one array runs out of items. the remaining items in the larger array are then added without "ghost" items from the the smaller array.
$array1 = #(0,2,4)
$array2 = #(5,7,9,11)
$InterleavedArray = [System.Collections.Generic.List[int]]::new()
$UpperBound = [math]::Max($array1.GetUpperBound(0), $array2.GetUpperBound(0))
foreach ($Index in 0..$UpperBound)
{
if ($Index -le $array1.GetUpperBound(0))
{
$InterleavedArray.Add($array1[$Index])
}
if ($Index -le $array2.GetUpperBound(0))
{
$InterleavedArray.Add($array2[$Index])
}
}
$InterleavedArray
output ...
0
5
2
7
4
9
11
hope that helps,
lee
If you want the elements to stay in the same order, just do $array3 = $array1 + $array2. If you want to sort it though, do $array3 = ($array1 + $array2) | sort.
Here is a slightly modified version of Theos answer.
Looks cleaner and its faster:
$array1 = (0,2,4)
$array2 = (1,3,5,6,7,8)
$len1 = $array1.Length
$len2 = $array2.Length
$maxIndex = [Math]::Max($len1, $len2)-1
$arrayResult = #()
$arrayResult = foreach ($i in 0..$maxIndex) {
if ($i -lt $len1) { $array1[$i] }
if ($i -lt $len2) { $array2[$i] }
}
$arrayResult
I'm trying to build a "connect four" in powershell, usable via console commands, without any GUI.
I've written the code to initialize the gamefield via the output of an array. However, after the newline element in the array, after the very first line, the output gets moved a little to the left:
The code i'm using that produces the error:
$initializegamefield = #()
$savedgamefield = #()
for ($i = 0; $i -lt 48; $i++) {
if (($i -eq 7 ) -or ($i -eq 15) -or ($i -eq 23) -or ($i -eq 31) -or ($i -eq 39) -or ($i -eq 47)) {
$initializegamefield += "`n"
Write-Host "$($initializegamefield)"
$savedgamefield += $initializegamefield
$initializegamefield = #()
} else {
$initializegamefield += "A"
}
}
#Write-Host "$($initializegamefield)"
Write-Host "$($savedgamefield)"
Here I've basically initialized the gamefield two times for testing purposes.
The first time it is initialized, it is done via outputting the array $initializegamefield after it has been filled with ONE ROW including the newline element.
Afterwards $initializegamefield is emptied (see if structure).
In addition, before it is emptied, it is saved to $savedgamefield.
Whilst the formatting of the gamefield is okay with the way I do it with $initializegamefield it isn't okay anymore when doing it it with $savedgamefield.
How can I avoid having this distortion of $savedgamefield?
Since your game field is a 6x8 array I'd recommend actually initializing it as an 6x8 array:
$height = 6
$width = 8
$gamefield = New-Object 'Object[,]' $height, $width
for ($i=0; $i -lt $height; $i++) {
for ($j=0; $j -lt $width; $j++) {
$gamefield[$i, $j] = 'A'
}
}
or at least as a "jagged" array (an array of arrays):
$height = 6
$width = 8
$gamefield = #()
for ($i=0; $i -lt $height; $i++) {
$gamefield += ,#(1..$width | ForEach-Object { 'A' })
}
I've written the following code to build an array with sequential strings.
It works like I expect, but I wish it ran quicker. Is there a more efficient way to produce my desired results in PowerShell?
I'm very new to PowerShell and appreciate your coaching.
$MIN = 1
$MAX = 20000
$ARRAY = #()
$PREFIX = "AA"
$startDTM = (Get-Date) # Start time
FOR ($i=$MIN; $i -le $MAX; $i++)
{
If ($i -gt 0 -and $i -lt 10) {
$ARRAY = $ARRAY + ($PREFIX+"00000"+$i)
}
If ($i -gt 9 -and $i -lt 100) {
$ARRAY = $ARRAY + ($PREFIX+"0000"+$i)
}
If ($i -gt 99 -and $i -lt 1000) {
$ARRAY = $ARRAY + ($PREFIX+"000"+$i)
}
If ($i -gt 999 -and $i -lt 10000) {
$ARRAY = $ARRAY + ($PREFIX+"00"+$i)
}
If ($i -gt 9999 -and $i -lt 100000) {
$ARRAY = $ARRAY + ($PREFIX+"0"+$i)
}
If ($i -gt 99999 -and $i -lt 1000000) {
$ARRAY = $ARRAY + ($PREFIX+$i)
}
}
$endDTM = (Get-Date) #End Time
"Elapsed Time: $(($endDTM-$startDTM).totalseconds) seconds"
$ARRAY.count #How many loaded.
Example of $ARRAY:
AA000001
AA000002
...
AA019999
AA020000
The following one-liner tooked 0.4911672 seconds (your example 12.9638944 seonds). Results are the same. It uses the format string with decimal specifier to ensure there are 6 digits and just store the result in the array:
$MIN = 1
$MAX = 20000
$PREFIX = "AA"
$ARRAY = $MIN .. $MAX | % { "$PREFIX{0:D6}" -f $_ }
You can further improove the performance by using a while loop (thanks 4c74356b41 for mentioning that):
# ....
$array = while ($MIN -le $MAX)
{
"$PREFIX{0:D6}" -f $MIN++
}
This tooked 0.0970327 seconds on my computer.
Q: I'm looking for a more elegant way to get the closest match of a numerical from an array.
I may have over-complicated things here
Input
## A given array A where to search in
$a = (16/10),(16/9),(4/3),(5/4),(21/10),(21/9)
## A given value B which should be searched for in array A (closest match)
$b = 16/11
Desired output
"Closest value to 16/11 is 4/3"
My current code to solve the problem
## New array C for the differences of array A - B
$c = $a | %{ [math]::abs($_ - $b) }
## Measure array C to get lowest value D
$d = $c | measure -Minimum
## Get position E of value D in array C
$e = [array]::IndexOf($c, $d.minimum)
## Position E correlates to array A
echo "Closest value to $b is $($a[$e])
Remarks
It don't has to be an array if a hash table or something else suits better
My current code outputs decimals like 1.33333 instead of fractions 4/3. It would be nice to output the fraction
Short code is always better
$a = (16/10),(16/9),(4/3),(5/4),(21/10),(21/9)
$b = 16/11
$oldval = $b - $a[0]
$Final = $a[0]
if($oldval -lt 0){$oldval = $oldval * -1}
$a | %{$val = $b - $_
if($val -lt 0 ){$val = $val * -1}
if ($val -lt $oldval){
$oldval = $val
$Final = $_} }
Write-host "$Final is the closest to $b"
$diff = $b - $a[0]
$min_index = 0
for ($i = 1; $i -lt $a.count; $i++)
{
$new_diff = $b - $a[$i]
if ($new_diff -lt $diff)
{
$diff = $new_diff
$min_index = $i
}
}
Write-Output "Closest value to $b is $($a[$min_index])"
Powershell dose not support fraction values...
## A given array A where to search in
$a = '16/10','16/9','4/3','5/4','21/10','21/9'
## A given value B which should be searched for in array A (closest match)
$b = '16/11'
$numericArray = ($a | % { Invoke-Expression $_ })
$test = Invoke-Expression $b
$best = 0
$diff = [double]::MaxValue
for ($i = 1; $i -lt $numericArray.count; $i++) {
$newdiff = [math]::abs($numericArray[$i] - $test)
if ($newdiff -lt $diff) {
$diff = $newdiff
$best = $i
}
}
"Closest value to $b is $($a[$best])"
The big difference here is that the inputs are strings and not numbers, so we can preserve the fractions.
Security note: Don't use this if the inputs are from a user, as passing user-generated strings to Invoke-Expression is obviously a recipe for trouble.
## A given array A where to search in
$a = (16/10),(16/9),(4/3),(5/4),(21/10),(21/9)
## A given value B which should be searched for in array A (closest match)
$b = 16/11
<#Create a new-Object , we'll use this to store the results in the Foreach Loop#>
$values = [Pscustomobject] #{
'result' = #()
'a-values' = #()
}
foreach($aa in $a)
{ #round to 2 decimal places and then subtract
#store the result at the 'a-value' used to create that result
$values.result += [MATH]::Abs( [Math]::Round($B,2)`
-[Math]::Round($aa,2)`
)
$values."a-values" += $aa
}
# sort ascending and then this gets me the number closest to Zero
$lookFor = $($values.result | Sort-Object )[0]
#the result of the Number closest to $b = 16/11
$endresult = $values.'a-values'[ $values.result.indexof($lookfor) ]
Write-host "the number closest to '$b' is '$endresult'"
Remove-Variable values
Here's a oneliner sorting with distance function:
($a | Sort-Object { [Math]::abs($_ - $b) })[0]
Returning a fraction is not possible in this case because e.g. 16/10 is immediately converted to a decimal.
What's the best way to initialize an array in PowerShell?
For example, the code
$array = #()
for($i=0; $i -lt 5;$i++)
{
$array[$i] = $FALSE
}
generates the error
Array assignment failed because index '0' was out of range.
At H:\Software\PowerShell\TestArray.ps1:4 char:10
+ $array[$ <<<< i] = $FALSE
Here's two more ways, both very concise.
$arr1 = #(0) * 20
$arr2 = ,0 * 20
You can also rely on the default value of the constructor if you wish to create a typed array:
> $a = new-object bool[] 5
> $a
False
False
False
False
False
The default value of a bool is apparently false so this works in your case. Likewise if you create a typed int[] array, you'll get the default value of 0.
Another cool way that I use to initialze arrays is with the following shorthand:
> $a = ($false, $false, $false, $false, $false)
> $a
False
False
False
False
False
Or if you can you want to initialize a range, I've sometimes found this useful:
> $a = (1..5)
> $a
1
2
3
4
5
Hope this was somewhat helpful!
Yet another alternative:
for ($i = 0; $i -lt 5; $i++)
{
$arr += #($false)
}
This one works if $arr isn't defined yet.
NOTE - there are better (and more performant) ways to do this... see https://stackoverflow.com/a/234060/4570 below as an example.
The original example returns an error because the array is created empty, then you try to access the nth element to assign it a value.
The are a number of creative answers here, many I didn't know before reading this post. All are fine for a small array, but as n0rd points out, there are significant differences in performance.
Here I use Measure-Command to find out how long each initialization takes. As you might guess, any approach that uses an explicit PowerShell loop is slower than those that use .Net constructors or PowerShell operators (which would be compiled in IL or native code).
Summary
New-Object and #(somevalue)*n are fast (around 20k ticks for 100k elements).
Creating an array with the range operator n..m is 10x slower (200k ticks).
Using an ArrayList with the Add() method is 1000x slower than the baseline (20M ticks), as is looping through an already-sized array using for() or ForEach-Object (a.k.a. foreach,%).
Appending with += is the worst (2M ticks for just 1000 elements).
Overall, I'd say array*n is "best" because:
It's fast.
You can use any value, not just the default for the type.
You can create repeating values (to illustrate, type this at the powershell prompt: (1..10)*10 -join " " or ('one',2,3)*3)
Terse syntax.
The only drawback:
Non-obvious. If you haven't seen this construct before, it's not apparent what it does.
But keep in mind that for many cases where you would want to initialize the array elements to some value, then a strongly-typed array is exactly what you need. If you're initializing everything to $false, then is the array ever going to hold anything other than $false or $true? If not, then New-Object type[] n is the "best" approach.
Testing
Create and size a default array, then assign values:
PS> Measure-Command -Expression {$a = new-object object[] 100000} | Format-List -Property "Ticks"
Ticks : 20039
PS> Measure-Command -Expression {for($i=0; $i -lt $a.Length;$i++) {$a[$i] = $false}} | Format-List -Property "Ticks"
Ticks : 28866028
Creating an array of Boolean is bit little slower than and array of Object:
PS> Measure-Command -Expression {$a = New-Object bool[] 100000} | Format-List -Property "Ticks"
Ticks : 130968
It's not obvious what this does, the documentation for New-Object just says that the second parameter is an argument list which is passed to the .Net object constructor. In the case of arrays, the parameter evidently is the desired size.
Appending with +=
PS> $a=#()
PS> Measure-Command -Expression { for ($i=0; $i -lt 100000; $i++) {$a+=$false} } | Format-List -Property "Ticks"
I got tired of waiting for that to complete, so ctrl+c then:
PS> $a=#()
PS> Measure-Command -Expression { for ($i=0; $i -lt 100; $i++) {$a+=$false} } | Format-List -Property "Ticks"
Ticks : 147663
PS> $a=#()
PS> Measure-Command -Expression { for ($i=0; $i -lt 1000; $i++) {$a+=$false} } | Format-List -Property "Ticks"
Ticks : 2194398
Just as (6 * 3) is conceptually similar to (6 + 6 + 6), so ($somearray * 3) ought to give the same result as ($somearray + $somearray + $somearray). But with arrays, + is concatenation rather than addition.
If $array+=$element is slow, you might expect $array*$n to also be slow, but it's not:
PS> Measure-Command -Expression { $a = #($false) * 100000 } | Format-List -Property "Ticks"
Ticks : 20131
Just like Java has a StringBuilder class to avoid creating multiple objects when appending, so it seems PowerShell has an ArrayList.
PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 1000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 447133
PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 10000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 2097498
PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 100000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 19866894
Range operator, and Where-Object loop:
PS> Measure-Command -Expression { $a = 1..100000 } | Format-List -Property "Ticks"
Ticks : 239863
Measure-Command -Expression { $a | % {$false} } | Format-List -Property "Ticks"
Ticks : 102298091
Notes:
I nulled the variable between each run ($a=$null).
Testing was on a tablet with Atom processor; you would probably see faster speeds on other machines. [edit: About twice as fast on a desktop machine.]
There was a fair bit of variation when I tried multiple runs. Look for the orders of magnitude rather than exact numbers.
Testing was with PowerShell 3.0 in Windows 8.
Acknowledgements
Thanks to #halr9000 for array*n, #Scott Saad and Lee Desmond for New-Object, and #EBGreen for ArrayList.
Thanks to #n0rd for getting me to think about performance.
$array = 1..5 | foreach { $false }
Here's another idea. You have to remember, that it's .NET underneath:
$arr = [System.Array]::CreateInstance([System.Object], 5)
$arr.GetType()
$arr.Length
$arr = [Object[]]::new(5)
$arr.GetType()
$arr.Length
Result:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
5
True True Object[] System.Array
5
Using new() has one distinct advantage: when you're programming in ISE and want to create an object, ISE will give you hint with all paramer combinations and their types. You don't have that with New-Object, where you have to remember the types and order of arguments.
$array = #()
for($i=0; $i -lt 5; $i++)
{
$array += $i
}
The solution I found was to use the New-Object cmdlet to initialize an array of the proper size.
$array = new-object object[] 5
for($i=0; $i -lt $array.Length;$i++)
{
$array[$i] = $FALSE
}
If I don't know the size up front, I use an arraylist instead of an array.
$al = New-Object System.Collections.ArrayList
for($i=0; $i -lt 5; $i++)
{
$al.Add($i)
}
Here's another typical way:
$array = for($i = 0; $i -le 4; $i++) { $false }
Or try this an idea. Works with powershell 5.0+.
[bool[]]$tf=((,$False)*5)
$array = foreach($i in 1..5) { $false }