Powershell command to move forward and backward in array - powershell

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

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.

Code-challenge: How fast can you calculate 1000! with 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

Powershell - output a foreach loop to one line

I am trying to take the output of my foreach loop and apply the array to a string that reads on one line. Here is my code so far:
$upper = 65..90
$lower = 97..122
foreach ($i in $upper)
{
[char]$i
}
foreach ($i in $lower)
{
[char]$i
}
I'm guessing I need to convert the output of the scriptblock to a variable and use the -join option, but everywhere I look I'm struggling to find how to structure that. Any guidance would be appreciated.
For this particular case, ForEach(type convertToType) is very useful, here is a cool way to get your lower and upper case dictionary string:
$lowerDict = [string]::new(([int][char]'a'..[int][char]'z').ForEach([char]))
$upperDict = $lowerDict.ToUpper()
If you have access to PowerShell Core, it can be reduced to:
$lowerDict = [string]::new('a'..'z')
$upperDict = $lowerDict.ToUpper()
As for what you are struggling on, how to do it with what you currently have (a foreach loop). You can capture all the output from the loop first:
$upper = foreach ($i in 65..90) { [char]$i }
Now, $upper is an array of chars, then to convert it to string, you can either use -join (guessed right) or [string]::new(...) as I did on my previous example:
$upperDict = -join $upper
# OR
$upperDict = [string]::new($upper)

How to declare and get array value

I am new to Powershell, first time using it.
I have declared an array and use array value but using below code, I am not able to retrieve the array value...Any idea what I am missing here?
Just FYI.. I am executing script in ADOBE ILLUSTRATOR and for testing I am using 3 here in condition (for loop)... will use $array later
$array = "a.jpg","b.jpg","c.jpg";
for ($i=1; $i-le=3; $i++)
{
$.writeln("This is line number " + $array[$i]);
var targetFileName = $array[$i]+'.png';
$.writeln(targetFileName);
}
I tried $array[$i].toString() as well but still not getting values... I am getting 0
Any help is appreciated and thanks in advance to all for your help
for ($i=1; $i-le=3; $i++)
The condition in the above line doesn't have a valid comparison operator. Change that to
for ($i=1; $i -le 3; $i++)
if you want the loop to terminate after 3 cycles.
$.writeln("This is line number " + $array[$i]);
var targetFileName = $array[$i]+'.png';
$.writeln(targetFileName);
This is not valid PowerShell. Looks more like JavaScript to me. In PowerShell it should probably look like this:
Write-Output "This is line number $i"
$targetFileName = $array[$i] + '.png'
Write-Output $targetFileName
or shorter
"This is line number $i"
$array[$i] + '.png'
Note that PowerShell arrays are zero-based, so the last iteration ($array[3]) will return $null instead of an element from the array. If you want to iterate over the elements of the array you should change your loop to this:
for ($i=0; $i -lt $array.Length; $i++) {
"This is line number $($i+1)"
$array[$i] + '.png'
}
or (better) pipe your array into a foreach loop:
$i = 0
$array | % {
"This is line number " + ++$i
$_ + '.png'
}

Referencing Powershell array index produces unexpected results when referenced with string

I am trying to find out why the following occurs if you have
$arr = #("Filename1", "Filename2")
for($i =0; $i -le $arr.Length -1; $i++) {
write-host ".\"$arr[$i]
write-host ".\$arr[$i]"
write-host $arr[$i]
}
So taking just one loop through it produces:
".\ Filename1"
".\ Filename1 Filename2[0]"
"Filename1"
Just referencing the array[index] will produce the correct value, but if I concatenated with a string it places a space between the string and value. When placed within the string I assume it is dumping the entire contents because it is evaluating $array then evaluating $i ending up with
".\ filename1 filename2[index number]"
But if I assign the individual value to a separate variable and concatenate it with a string there is no space? Why is that:
Example:
$name = $arr[$i]
write-host ".\$name"
output = ".\filename1"
which is correct.
You have to do:
write-host ".\$($arr[$i])"
so that it is evaluated as array indexing.
It would be the case with something like accessing properties of an object or key of hash etc within the string:
PS > $a = #{test="A";test2="B"}
PS > write-host "$a.test"
System.Collections.Hashtable.test
PS > write-host "$($a.test)"
A
Another alternative is to use string formatting, especially useful when you have lots of variables / objects in the string:
write-host (".\{0}" -f $arr[$i])
Your code should look like this:
$arr = #("Filename1", "Filename2")
#for($i =0; $i -le $arr.Length-1; $i++) {
for($i =0; $i -le $arr.Length; $i++) {
write-host ".\"$arr[$i]
#write-host ".\$arr[$i]"
write-host ".\$($arr[$i])"
write-host $arr[$i]
}