How to put constraints for variables of the loop in PowerShell? - loops

I have the following code:
$SE = 215
function F
{
Param ($OE)
For ($iteration = 1; $iteration -le 100; $iteration++)
{
Write-host "Iteration" $iteration
$result = $OE - 1
$OE = $result
$result
ForEach ($input in $result)
{
$OPT = 5 * [double]$Steel.Force * 1000 * ([math]::pow([double]$Steel.L, 4))/(384 * $input * ([math]::pow(10, 9)) * [double]$Steel.I * ([math]::pow(10, -8)))
$OPT
}
}
}
F -OE $SE
I want to put constraints for two variables: $result and $OPT. For example, when $result -le 205 and/or $OPT -ge 5 the iteration will stop. Currently, the iteration will keep running until $iteration = 100. I have tried to add a do{} While() loop, but it did not work. Sometimes the script is stuck. Any idea?

You can use break to exit a loop.
For example:
if ($result -le 205 -and $OPT -ge 5) { break }
You can read more about it here:
https://technet.microsoft.com/en-us/library/hh847873.aspx

Related

how to create bunch of arrays inside a loop in powershell

I'm writing a script to fetch values for a set of components and storing the fetched values in an array. As there are many components I created a loop and tried to create an array name along with its iteration value (fi1, fi2, fi3, etc.) like this.
Here is the code:
function fiswitchinfo {
Param ($sheet, [string]$text, $iter)
$($fi+($iter)) = #()
$max = $sheet.UsedRange.Rows.Count
for ($i=1; $i -lt 900 ; $i++) {
$rows = $sheet.Cells.Item($i, 2).EntireRow
$cell_info = $sheet.Cells.Item($i, 2)
$cell = $cell_info.Address($false, $false)
if ($rows.hidden -eq $false) {
$cell_info = $sheet.Cells.Item($i, 2).Text
if ($cell_info -ne "" -and $cell_info.Contains($text) -eq "True") {
$cell = $cell -split "(?<=[A-Z])(?=\d)"
[int]$curline = $cell[1]
$component = $sheet.Cells.Item($curline, 2).Text
$compip = $sheet.Cells.Item($curline, 3).Text
$row = $sheet.Cells.Item($curline, 2).EntireRow
$cellinfo = $sheet.Cells.Item($curline, 2).text
if ($row.Hidden -ne "True" -and $cellinfo -ne $null) {
Write-Host $component $compip
$script:fi+$iter += $compip
}
}
}
}
}
fiswitchinfo $worksheet_3 "Fabric Interconnect 01 Cluster IP" 1
fiswitchinfo $worksheet_3 "Fabric Interconnect 01 A" 1
fiswitchinfo $worksheet_3 "Fabric Interconnect 01 B" 1
I'm not quite sure what you expect $($fi+($iter)) or $script:fi+$iter to do, but I'm pretty certain they won't do whatever it is you expect.
To have a function create an array of arrays in a loop and then return it you'd do something like this:
function fiswitchinfo {
...
$arr = #()
for ($i=1; $i -lt 900 ; $i++) {
...
$arr += ,$compip
}
return ,$arr
}
$fi1 = fiswitchinfo ...
$fi2 = fiswitchinfo ...
...
The leading comma in the statements $arr += ,$compip and return ,$arr is the unary array construction operator, which prevents PowerShell from unrolling the arrays. $arr += ,$compip appends $compip to $arr as a nested array (thus making $arr a jagged array) instead of appending the elements of $compip to $arr. return ,$arr ensures that $arr is returned to the caller as-is (thus preserving the array even if it's empty).

How to deal with "distorted" array-output

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' })
}

Building a sequential string into an array (quickly)

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.

Removing lines if word exits and printing all lines between lines having two words in powershell

Question 1 :
I have a MAIN.txt file with some 2,000,000 lines. File will be in the below format
unit=123
xxx
yyy
unit=245
xx
yy
unit=PO 789
x
y
unit=258
xy
yx
unit=777
xz
zx
unit=999
yz
zy
unit=456
zz
yy
I want to remove the line having word 'PO' and three lines following them.
Sample Output :
unit=123
xxx
yyy
unit=245
xx
yy
unit=258
xy
yx
unit=777
xz
zx
unit=999
yz
zy
unit=456
zz
yy
I am new to Powershell. I tried this but I'm able to remove only the line with PO. How do I delete n lines following it.
Second question :
I have a file ,say extractthis.txt -
123|258
777|456
I want to print the lines in MAIN file between the lines having the number 123 and two lines after 258 (i.e bbb) and save it in a new file, say file1.
Then read the second line from extractthis.txt (777|n456) and print the lines between the line having the number 777 and two lines after the line having 456 (jjj) and save it to file2.txt and so on.
I have done similar thing in Unix. But I'm struggling to do the same in Powershell.
For question #1, something like the following function should work (at least it did when I tried it against your data file):
function Skip-Match {
[cmdletbinding()]
Param(
[parameter(Mandatory)][string]$Pattern,
[parameter(Mandatory)][string]$Path,
[int]$Count=3
)
$lines = Get-Content -Path $Path
$state = -1
$lines | ForEach-Object {
$line = $_
if( $line.ToString() -like "*$Pattern*" ) {
$state=3
} elseif ( $state -lt 0 ) {
$line
Write-Verbose $line
}
$state--
}
}
You can then save it to a file (I called it skip-match.ps1), source the file, and then just execute the function... something like:
. .\skip-match.ps1
Skip-Match -Pattern "PO" -Path .\datafile.dat
Given you have such a large amount of lines in MAIN.txt, I'd avoid using Get-Content as it will open the entire file into memory. Use streams instead.
function sanitise($file) {
$reader = [System.IO.File]::OpenText($file)
$i = 0
try {
while(($line = $reader.ReadLine()) -ne $null) {
if($i -gt 0) { $i++ }
if($i -gt 4) { $i = 0 }
if($line -like "*PO*") { $i++ }
if ($i -eq 0) { echo $line }
}
}
finally {
$reader.Close()
}
}
function readBetweenLines($file, $a, $b) {
$reader = [System.IO.File]::OpenText($file)
$i = 0
$read = $false
try {
while(($line = $reader.ReadLine()) -ne $null) {
if($i -gt 0) { $i++ }
if($line -match ".*$a`$") { $read = $true }
if($line -match ".*$b`$") { $i++ }
if(($read) -and ($i -lt 4)) { echo $line }
if($i -gt 4) { break }
}
}
finally {
$reader.Close()
}
}
sanitise(".\MAIN.txt")
$extract = get-content ".\extractthis.txt"
foreach($line in $extract) {
$lineNum = $line.split("|")
readBetweenLines ".\MAIN.txt" $lineNum[0] $lineNum[1]
}
Substitute the echo statements with whatever you need to output the content somewhere else. As it stands, this will also require you sanitise MAIN.txt into a new file before running the line checking function on it.

How to fill an array efficiently in Powershell

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 ;-)

Resources