Assume there is an array in variable $a that is created like this,
$a = ,#(1,2,3)
$a += ,#(4,5,6)
$a += ,#(7,8,9)
$a += ,#(10,11,12)
I want to extract part of the array, say $a[1] and $a[2], into another variable, say, $b such that,
$b[0] = #(4,5,6)
$b[1] = #(7,8,9)
I can use a simple for loop to do the task, but I am thinking if there is a more 'elegant' way to do this... may be a one-liner?
Thanks in advance.
You can use the Range operator to slice the array:
$b = $a[1..2]
It is worth noting the Range operator supports dynamic values - very useful when you want to work out your range dynamically, for example:
$a = #(0,1,2,3,7)
$b = #(4,5,6)
$twotoseven = $a[($a.Length-($a.Length-2))..($a.Length-2)] + $b + $a[-1]
Output:
2 3 4 5 6 7
Related
Updated: I am trying to take a user input values(string) into a 2d array. For testing purpose, I assigned a string to variable.
Goal: Later I want my program to look at each row for column 1 of my array and do something like install printer software if it says Printer and don't install software if noPrinter. I can do this for 1d array but I want anywhere from 1 to 10 rows. That is why 2d array thought would be complexity.
I want to learn to do this and not ask you to write all my code.
Thank you.
Here is my code.
#set user input to a string variable
$UserInputVar = "computer1,noPrinter,Computer2,Printer,Computer3,Printer"
#this is my 2d array; Declare. i want to be able to use anywhere from 1 to 10 rows and always 2 columns
$my2d=[object[]]::(,2)
$my2d.clear #for testing purpose
#assign values of sting to 2 dimensional array
#split the string at each comma(,)
$my2d = $UserInputVar.Split(",")
#show me the values in this array
"`n #my2d[0][0] "
$my2d[0][0] #expect value 'computer1'
$my2d[0][1] #expect value 'noPrinter'
$my2d[0][2]
$my2d[0][3]
"`n #my2d[1][0] "
$my2d[1][0] #expect value 'computer2'
$my2d[1][1] #expect value 'Printer'
$my2d[1][2]
$my2d[1][3]
"`n #my2d[2][0] "
$my2d[2][0] #expect value 'computer3'
$my2d[2][1] #expect value 'Printer'
$my2d[2][2]
$my2d[2][3]
"`n #array with no 2nd index"
$my2d[0]
$my2d[1]
$my2d[2]
I'm not sure if you want an object as output, but if so, this is a simple way to do it:
$z = 0
$UserInputVar = "This,is,my,strg,to,test".Split(',')
for($z=0; $z -lt $UserInputVar.Count; $z=$z+2)
{
[pscustomobject]#{
1 = $UserInputVar[$z]
2 = $UserInputVar[$z+1]
}
}
Output
1 2
- -
This is
my strg
to test
Edit
Looking again at the question, this may be what you're looking for:
$z = 0
$UserInputVar = "This,is,my,strg,to,test".Split(',')
$result = [System.Collections.Generic.List[object]]::new()
for($z=0; $z -lt $UserInputVar.Count; $z=$z+2)
{
$result.Add(#($UserInputVar[$z],$UserInputVar[$z+1]))
}
Some Testing
PS /> $result[0]
This
is
PS /> $result[0][0]
This
PS /> $result[0][1]
is
PS /> $result[0][2]
PS /> $result[1][0]
my
You may also use regex matching to return your groups of 2 elements. Then you can split each of those returned pairs into a single array (because of the unary operator ,). All the returned single arrays are then wrapped automatically into Object[] array.
$UserInputVar = "computer1,noPrinter,Computer2,Printer,Computer3,Printer"
$my2d = [regex]::Matches($UserInputVar,'[^,]+(,[^,]+|$)') |
Foreach-Object {,($_.Value -split ',')}
So lets say my variable $a is an array containing "1" and "2" as string.
$a = "1", "2"
Now I want to use foreach through a pipeline to subtract 1 from each value, so I'd do something like
$a | foreach{$_ = [int]$_ - 1}
but this seems do nothing, yet produces no error. So $a still contains "1" and "2". I struggle to understand where I went wrong... It's possible if i don't have an array, so this works:
$b = "3"; $b - 2
And it will return 1. So I also tried without "[int]" but it still fails, so I'm guessing it either has to do with the pipeline or my foreach but I wouldn't know why it does that.
Any suggestions?
Your foreach isn't mutating the items in your original array like you think it is - you're assigning the calculated value to the context variable $_, not updating the array index.
You can either create a new array with the calculated values as follows:
$a = $a | foreach { [int]$_ - 1 }
or mutate the items in the original array in-place:
for( $i = 0; $i -lt $a.Length; $i++ )
{
$a[$i] = [int]$a[$i] - 1
}
Note that your second example doesn't quite do what you think either:
PS> $b = "3"; $b - 2
1
PS> $b
3
The $b - 2 part is an expression which is evaluated and echoed out to console - it doesn't change the value of $b because you haven't assigned the result of the expression back to anything.
Just add the instance variable to the last line of your loop like so:
$a = $a | foreach{$_ = [int]$_ - 1; $_}
I want to create an empty array of arrays in Powershell to hold "tuples" of values (arrays are immutable).
Therefore I try something like:
The type of $arr is Object[]. I've read that += #(1,2) appends the given element (i.e. #(1,2)) to $arr (actually creates a new array). However, in this case it seems that the arrays are concatenated, why?
$arr = #()
$arr += #(1,2)
$arr.Length // 2 (not 1)
If I do as follows, it seems that $arr contains the two arrays #(1,2),#(3,4), which is what I want:
$arr = #()
$arr += #(1,2),#(3,4)
$arr.Length // 2
How do I initialize an empty array of arrays, such that I can add one subarray at a time, like $arr += #(1,2)?
The answer from Bruce Payette will work. The syntax seems a bit awkward to me, but it does work. At least it is not Perl.
Another way to do this would be with an ArrayList. To me, this syntax is more clear and more likely to be understood by another developer (or myself) in six months.
[System.Collections.ArrayList]$al = #()
$al.Add(#(1,2))
$al.Add(#(3,4))
foreach ($e in $al) {
$e
$e.GetType()
}
The + operator concatenates arrays. To add an array as a single element, prefix the element to add with a comma. Something like #() + , (1,2) + , (3, 4).
As far as I can tell, you can't do it natively in PowerShell, nor can you do it with the [System.Array] type. In both cases, you seem to need to define both the length and the type of the array. Because I wanted a completely empty array of arrays, with the ability to store any type of value in the array, I did it this way.
$x=[System.Collections.ArrayList]::new()
$x.Count
$x.GetType()
$x.Add([System.Collections.ArrayList]::new()) > $null
$x.Count
$x[0].Count
$x[0].GetType()
$x[0].Add("first element") > $null
$x.Count
$x[0].Count
$x[0][0]
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.
I have an example of a program that creates an array, and then attempts to assign the value of that array multiple times into another array as a multidimensional array.
$a =#(0,0,0)
$b = #($a,$a,$a)
$b[1][2]=2
$b
'And $a is changed too:'
$a
The output is:
PS E:\Workarea> .\what.ps1
0
0
2
0
0
2
0
0
2
And $a is changed too:
0
0
2
So in this instance, the variable is actually pointing to the original variable. This is very unexpected behavior. It's rather neat that one can do this, although I never did use unions that much in my C programming. But I'd like a way to actually just do the assignment of the value, not of the variable.
$b = #($a.clone(),$a.clone(),$a.clone())
I guess would work, but something tells me that there may be something a little more elegant than that.
Thanks for the input.
This is PowerShell 2.0 under Windows 7 64-bit.
To assign the values of $a to $b instead of the reference to $a, you can wrap the variable in $(). Anything in $() gets evaluated before using it in the command, so $($a) is equivalent to 0, 0, 0.
$a =#(0,0,0)
$b = #($($a),$($a),$($a))
$b[1][2]=2
$b
'$a is not changed.'
$a
Be careful in PowerShell ',' is not the enumerator operator, but an array aoperator. The thing you present as multidimensional array is in fact an array of array, you'll find here under the definition of a multidimensional array :
$a= new-object ‘object[,]’ 3,3
$a[0,2]=3
PS > for ($i=0;$i -lt 3;$i++)
>> {
>> for($j=0;$j -lt 3;$j++)
>> {
>> $a[$i,$j]=$i+$j
>> }
>> }
Everything work here as $b is an array of reference.