Powershell how to count all elements in a multidimensional array - arrays

I've been trying to figure out how to count all elements in an multidimensional array. But .Count only returns the first dimension.
after i gave up to find a proper solution i just created this loop to move all elements to the first dimension and count them. but this is really only a hack.
$mdarr = #((0,1,2,3,4),(5,6,7,8,9),(10,11,12,13,14))
$filecount = New-Object System.Collections.ArrayList
for($i = 0; $i -lt $mdarr.Length; ++$i) {
$filecount += $mdarr[$i]
}
$filecount.Count
How would this be done properly without processing the array first?

In the loop you are adding the elements of $mdarr[$i]. You later count the elements of the merge result. Instead of the adding to an ArrayList you could keep a count:
$xs = #((0,1,2,3,4),(5,6,7,8,9),(10,11,12,13,14))
$sum = 0;
foreach ($x in $xs) { $sum += $x.Count }
$sum // 15
# alternatively
$xs | % { $sum += $_.Count }
# or
($xs | % { $_.Count } | Measure-Object -Sum).Sum
# or
$xs | % { $_.Count } | Measure-Object -Sum | select -Expand Sum

one line code: you can flatten the multidimensional array into a anonymous array, and count the anonymous array
$xs = #((0,1,2,3,4),(5,6,7,8,9),(10,11,12,13,14))
#($xs | ForEach-Object {$_}).count #result 15
or multiline that is more readable:
$xs = #((0,1,2,3,4),(5,6,7,8,9),(10,11,12,13,14))
$xs_flatten = #($xs | ForEach-Object {$_})
$xs_flatten_count = $xs_flatten.count
echo $xs_flatten_count #result 15

put a dimension identifier index in front of .count
e.g $xs[0].count
this way, instead of returning the count of dimensions, it returns the number of rows for a given dimension

Related

PowerShell foreach() multidimensional array based on first element

I have a fairly basic multidimensional array which looks something like this:
2017,123
2017,25
2018,5
2018,60
2017,11
I wish to run a ForEach() loop or similar function to total the numbers in the second element based on the year indicated in the first so that I end up with an output like this:
2017,159
2018,65
How do I best accomplish this?
The following solution is concise, but not fast:
# input array
$arr =
(2017,123),
(2017,25),
(2018,5),
(2018,60),
(2017,11)
# Group the sub-arrays by their 1st element and sum all 2nd elements
# in each resulting group.
$arr | Group-Object -Property { $_[0] } | ForEach-Object {
, ($_.Name, (($_.Group | ForEach-Object { $_[1] } | Measure-Object -Sum).Sum))
}
Assuming your array looks like "$array" this will give you what you need:
$2017total = 0
$2018total = 0
$array = "2017,123",
"2017,25",
"2018,5",
"2018,60",
"2017,11" | % {
if ($_ -match '2017') {
$2017 = ($_ -split ',')[1]
$2017total += $2017
}
else {
$2018 = ($_ -split ',')[1]
$2018total += $2018
}
}
Write-Host "2017,$2017total"
Write-Host "2018,$2018total"

Averaging part of an array powershell

Hi I am trying to return the average for part of an array, when the array is set out like
$multi = New-Object 'object[,]' $nucount,($readings + 2 )
and contains "1,4,2,6,3,4,5,nameofitem, cost of item"
I want to get an average for the first 7 elements which I know will always be the first 7 and always be a number. In the case above the number of elements containing a number will be held in a variable called $readings. So the sum I want to do is add up the elements in the array up to the $readings value. and then device by $readings.
I know of ways like this for the whole array
$Avg = ($array | Measure-Object -Average);
$Avg.Average;
or simple looping through and calculating the average buy adding and deviding.
But is there any short hand way to do this. I was going to used the ... operator
$multi[2,0..$readings] but i get an error of
Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Int32".
Any help with this would be great if it can provide a short code to replace the
for (x=1 ; $x -eq $readings ; $X++)
{
$sum = $sum + $multi[2,$x]
{
$avg= $sum/$readings
You are going right way:
$readings=7
$array=1,4,2,6,3,4,5,"nameofitem", "cost of item"
$Avg = ($array[0..($readings - 1)] | Measure-Object -Average);
$Avg.Average;
Note that arrays are zero based…
maybe it works like this:
0..$readings | % { $sum += $multi[$_] }
or if not:
0..[int]$readings | % { $sum += $multi[$_] }
and first you have to initate $sum
$sum = 0
0..$readings | % { $sum += $multi[$_] }
$avg = $sum/$readings
here is another eample of what will work:
$arr = 1..50
$readings = 15
$arr[2..$readings]
regards Eldo.Ob
As you have indicated in your comment that array is 1-dimentional indeed, solution is straightforward:
$multi | select -First 7 | Measure-Object -Average
Try this:
$avg = (0..7 |% { $multi[2,$_] } | Measure-Object -Average)

Better "Split-ArrayInChunks" for PowerShell

When using an script that based on arrays for my "Split-ArrayInChunks" method takes it ages to process 190.000+ records, my initial version based on this code (see Split up an array into chunks and start a job on each one.)
$computers = gc c:\somedir\complist.txt
$n = 6
$complists = #{}
$count = 0
$computers |% {$complists[$count % $n] += #($_);$count++}
0..($n-1) |% {
start-job -scriptblock {gwmi win32_operatingsystem -computername $args} -argumentlist $complists[$_]
}
I found this article Performance: The += Operator (and When to Avoid It) and basically recommends the author to use "System.Collections.Generic.List" or "System.Collections.ArrayList" instead of arrays. So I came up with this implementation:
function Split-ArrayInChunks_UsingGenericList($inArray, $numberOfChunks) {
$list = New-Object System.Collections.Generic.List[System.Collections.Generic.List[PSCustomObject]]
$count = 0
# populate with empty lists
0..($numberOfChunks-1) | % {
$list.Add((New-Object System.Collections.Generic.List[PSCustomObject]))
}
# create packages
$inArray | % {
$list[$count % $numberOfChunks].Add($_);
$count++
}
return $list.ToArray()
}
I also tried to use "System.Collections.ArrayList", but this function returns an flat array. Inside the function is $arrayList an nested array, but once outside the function do I have an flat array (192169 items instead of 10 chunks).
function Split-ArrayInChunks_UsingArrayList($inArray, $numberOfChunks) {
$arryList = New-Object System.Collections.ArrayList
$count = 0
# populate
0..($numberOfChunks-1) | % {
$arryList.Add((New-Object System.Collections.ArrayList))
}
$inArray | % {
$arryList[$count % $numberOfChunks].Add($_);
$count++
}
Write-Host 'Number of arryList:'$arryList.Count
Write-Host 'Number of items in first arryList:' $arryList[0].Count
return $arryList
}
To illustrate the "flat" problem generates the following code...
Write-Host '-------------------------------'
$packages1 = Split-ArrayInChunks_UsingGenericList $data.CrmRecords 10
Write-Host 'Number of packages1:'$packages1.Count
Write-Host 'Number of items in first package1:' $packages1[0].Count
Write-Host '-------------------------------'
$packages2 = Split-ArrayInChunks_UsingArrayList $data.CrmRecords 10
Write-Host 'Number of packages2:'$packages2.Count
Write-Host 'Number of items in first package2:' $packages2[0].Count
...this output:
-------------------------------
Number of packages1: 10
Number of items in first package1: 19215
-------------------------------
Number of arryList: 10
Number of items in first arryList: 19215
Number of packages2: 192169
Number of items in first package2: 1
So I have two questions:
Any option to improve improve my "Split-ArrayInChunks_UsingArrayList" version (e.g. faster, more readable)?
Why is the return value of "ArrayInChunks_UsingArrayList" an flat array, inside the function is %arrayList an nested array?
Update 2016-02-04: I updated my code based on the feedback (use [void] to prevent the polluting of the output) and it works. The only strang thing is the fact that when I use |format-table is my version (Split-ArrayInChunks_UsingArrayList) again printed as flat list:
function Split-ArrayInChunks_UsingArrayList($inArray, $numberOfChunks) {
$arryList = New-Object System.Collections.ArrayList
$count = 0
# populate
0..($numberOfChunks-1) | % {
[void]$arryList.Add((New-Object System.Collections.ArrayList))
}
$inArray | % {
[void]$arryList[$count % $numberOfChunks].Add($_);
$count++
}
return $arryList
}
function Split-ArrayInChunks_CommunityVersion($inArray, $numberOfChunks) {
$Lists = #{}
$count = 0
# populate
0..($numberOfChunks-1) | % {
$Lists[$_] = New-Object System.Collections.ArrayList
}
$inArray | % {
[void]$Lists[$count % $numberOfChunks].Add($_);
$count++
}
return $Lists
}
When I execute this code...
Write-Host 'CommunityVersion'
Write-Host '-------------------------------'
Split-ArrayInChunks_CommunityVersion $list 6 | Format-Table -AutoSize
Write-Host 'ArrayInChunks_UsingArrayList'
Write-Host '-------------------------------'
Split-ArrayInChunks_UsingArrayList $list 6 | Format-Table -AutoSize
... is this the output in the console:
CommunityVersion
-------------------------------
Name Value
---- -----
5 {denn, getan, verhaftet}
4 {haben, Böses, Morgens, war}
3 {verleumdet, etwas, eines, es}
2 {Josef K., er, er, er}
1 {musste, dass, wurde, sagte}
0 {Jemand, ohne, hätte, »Wie ein Hund!«}
ArrayInChunks_UsingArrayList
-------------------------------
Jemand
ohne
hätte
»Wie ein Hund!«
musste
dass
wurde
sagte
Josef K.
er
er
er
verleumdet
etwas
eines
es
haben
Böses
Morgens
war
denn
getan
verhaftet
I do not understand why "ArrayInChunks_UsingArrayList" is printed as list, it is an nested array, just like "ArrayInChunks_CommunityVersion".
Okay, here's how I'd do that:
function Split-ArrayInChunks_UsingArrayList($inArray, $numberOfChunks) {
$Lists = #{}
$count = 0
# populate
0..($numberOfChunks-1) | % {
$Lists[$_] = New-Object System.Collections.ArrayList
}
$inArray | % {
[void]$Lists[$count % $numberOfChunks].Add($_);
$count++
}
Write-Host 'Number of arryList:'$Lists.Count
Write-Host 'Number of items in first arryList:' $Lists[0].Count
return $Lists
}
Turns out the usage of "$inArray | % " makes the operation so slow. When using a normal foreach loop takes it less then 2 second to create the chunks. When using the "$inArray | % " based version it takes 20 seconds:
function Split-ArrayInChunks_Fast($inArray, $numberOfChunks) {
$arrayList = New-Object System.Collections.ArrayList
$count = 0
# populate
0..($numberOfChunks-1) | % {
[void]$arrayList.Add((New-Object System.Collections.ArrayList))
}
foreach($elem in $inArray) {
[void]$arrayList[$count % $numberOfChunks].Add($elem)
$count++
}
return $arrayList.ToArray()
}
function Split-ArrayInChunks_Slow($inArray, $numberOfChunks) {
$arrayList = New-Object System.Collections.ArrayList
$count = 0
# populate
0..($numberOfChunks-1) | % {
[void]$arrayList.Add((New-Object System.Collections.ArrayList))
}
$inArray | % {
[void]$arrayList[$count % $numberOfChunks].Add($_);
$count++
}
return $arrayList.ToArray()
}

How to store outlook email body in array - Powershell?

the script below reads my outlook emails but how do I access the output. I'm new too Powershell and I'm still getting used to certain things. I just want to get the body of 10 unread outlook emails and store them in an Array called $Body.
$olFolderInbox = 6
$outlook = new-object -com outlook.application;
$ns = $outlook.GetNameSpace("MAPI");
$inbox = $ns.GetDefaultFolder($olFolderInbox)
#checks 10 newest messages
$inbox.items | select -first 10 | foreach {
if($_.unread -eq $True) {
$mBody = $_.body
#Splits the line before any previous replies are loaded
$mBodySplit = $mBody -split "From:"
#Assigns only the first message in the chain
$mBodyLeft = $mbodySplit[0]
#build a string using the –f operator
$q = "From: " + $_.SenderName + ("`n") + " Message: " + $mBodyLeft
#create the COM object and invoke the Speak() method
(New-Object -ComObject SAPI.SPVoice).Speak($q) | Out-Null
}
}
This may not be a factor here, since you're looping through only ten elements, but using += to add elements to an array is very slow.
Another approach would be to output each element within the loop, and assign the results of the loop to $body. Here's a simplified example, assuming that you want $_.body:
$body = $inbox.items | select -first 10 | foreach {
if($_.unread -eq $True) {
$_.body
}
}
This works because anything that is output during the loop will be assigned to $body. And it can be much faster than using +=. You can verify this for yourself. Compare the two methods of creating an array with 10,000 elements:
Measure-Command {
$arr = #()
1..10000 | % {
$arr += $_
}
}
On my system, this takes just over 14 seconds.
Measure-Command {
$arr = 1..10000 | % {
$_
}
}
On my system, this takes 0.97 seconds, which makes it over 14 times faster. Again, probably not a factor if you are just looping through 10 items, but something to keep in mind if you ever need to create larger arrays.
define $body = #(); before your loop
Then just use += to add the elements
Here's another way:
$body = $inbox.Items.Restrict('[Unread]=true') | Select-Object -First 10 -ExpandProperty Body

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