How conditionally sort array in one line? - arrays

I want to sort array only if it contains more than N elements, something like this:
$myArray | if $myArray.Count() > N -> | Sort-Object
How can I do this in one line?

You can just use an if statement and do everything in one line:
if ($myArray.Length -gt N) { $myArray = $myArray | Sort-Object }
But why would you wan't to do it? I would prefer it this way:
if ($myArray.Length -gt N)
{
$myArray = $myArray | Sort-Object
}
You might wan't to find a solution without an if statement (only pipeline) but I don't see a reason for that.

Related

Query PSCustomObject Array for row with largest value

I'm trying to find the row with an attribute that is larger than the other row's attributes. Example:
$Array
Name Value
---- ----
test1 105
test2 101
test3 512 <--- Selects this row as it is the largest value
Here is my attempt to '1 line' this but It doesn't work.
$Array | % { If($_.value -gt $Array[0..($Array.Count)].value){write-host "$_.name is the largest row"}}
Currently it outputs nothing.
Desired Output:
"test1 is the largest row"
I'm having trouble visualizing how to do this efficiently with out some serious spaghetti code.
You could take advantage of Sort-Object to rank them by the property "Value" like this
$array = #(
[PSCustomObject]#{Name='test1';Value=105}
[PSCustomObject]#{Name='test2';Value=101}
[PSCustomObject]#{Name='test3';Value=512}
)
$array | Sort-Object -Property value -Descending | Select-Object -First 1
Output
Name Value
---- -----
test3 512
To incorporate your write host you can just run the one you select through a foreach.
$array | Sort-Object -Property value -Descending |
Select-Object -First 1 | Foreach-Object {Write-host $_.name,"has the highest value"}
test3 has the highest value
Or capture to a variable
$Largest = $array | Sort-Object -Property value -Descending | Select-Object -First 1
Write-host $Largest.name,"has the highest value"
test3 has the highest value
PowerShell has many built in features to make tasks like this easier.
If this is really an array of PSCustomObjects you can do something like:
$Array =
#(
[PSCustomObject]#{ Name = 'test1'; Value = 105 }
[PSCustomObject]#{ Name = 'test2'; Value = 101 }
[PSCustomObject]#{ Name = 'test3'; Value = 512 }
)
$Largest = ($Array | Sort-Object Value)[-1].Name
Write-host $Largest,"has the highest value"
This will sort your array according to the Value property. Then reference the last element using the [-1] syntax, then return the name property of that object.
Or if you're a purist you can assign the variable like:
$Largest = $Array | Sort-Object Value | Select-Object -Last 1 -ExpandProperty Name
If you want the whole object just remove .Name & -ExpandProperty Name respectively.
Update:
As noted PowerShell has some great tools to help with common tasks like sorting & selecting data. However, that doesn't mean there's never a need for looping constructs. So, I wanted to make a couple of points about the OP's own answer.
First, if you do need to reference array elements by index use a traditional For loop, which might look something like:
For( $i = 0; $i -lt $Array.Count; ++$i )
{
If( $array[$i].Value -gt $LargestValue )
{
$LargestName = $array[$i].Name
$LargestValue = $array[$i].Value
}
}
$i is commonly used as an iteration variable, and within the script block is used as the array index.
Second, even the traditional loop is unnecessary in this case. You can stick with the ForEach loop and track the largest value as and when it's encountered. That might look something like:
ForEach( $Row in $array )
{
If( $Row.Value -gt $LargestValue )
{
$LargestName = $Row.Name
$LargestValue = $Row.Value
}
}
Strictly speaking you don't need to assign the variables beforehand, though it may be a good practice to precede either of these with:
$LargestName = ""
$LargestValue = 0
In these examples you'd have to follow with a slightly modified Write-Host command
Write-host $LargestName,"has the highest value"
Note: Borrowed some of the test code from Doug Maurer's Fine Answer. Considering our answers were similar, this was just to make my examples more clear to the question and easier to test.
Figured it out, hopefully this isn't awful:
$Count = 1
$CurrentLargest = 0
Foreach($Row in $Array) {
# Compare This iteration vs the next to find the largest
If($Row.value -gt $Array.Value[$Count]){$CurrentLargest = $Row}
Else {$CurrentLargest = $Array[$Count]}
# Replace the existing largest value with the new one if it is larger than it.
If($CurrentLargest.Value -gt $Largest.Value){ $Largest = $CurrentLargest }
$Count += 1
}
Write-host $Largest.name,"has the highest value"
Edit: its awful, look at the other answers for a better way.

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"

Empty value powershell array

I have a strange issue, this is my CSV:
Serveur;Carte;Cordon;IP;Mac;Vmnic ;Vmnic mac;Connect;Port
Dexter;eth1;405;172.16.5.117;00:24:e8:36:36:df;Vmnic0;00:50:56:56:36:df;sw-front-1;A1
Dexter;eth2;14;192.168.140.17;00:24:e8:36:36:e1;Vmnic1;00:50:56:56:36:e1; sw_eq_ds_1;3
;;;;;;;;
Gordon;eth1;404;172.16.5.124;b8:ac:6f:8d:ac:b4;Vmnic0;00:50:56:5d:ac:b4;;
Gordon;eth2;35;192.168.140.114;b8:ac:6f:8d:ac:b6;Vmnic1;00:50:56:5d:ac:b6;;
Gordon;eth3;254;192.168.33.10;b8:ac:6f:8d:ac:b8;Vmnic2;00:50:56:5d:ac:b8;;
So I imported it into an array with the following code:
$Serveur = #()
Import-Csv C:\Users\aasif\Desktop\myfile.csv -Delimiter ";" |`
ForEach-Object {
$Serveur += $_.Serveur
}
And to remove duplicate values I did this :
$Serveur = $Serveur | sort -uniq
So when I display my Array, I obtain these two values : Dexter and Gordon and a third null value
But I also get an empty value
The following code return 3
$Serveur.count
Why?
Thanks for your help
If you want exclude empty values you can do like this
$Serveur = $Serveur | ? { $_ } | sort -uniq
In case someone (like me) needs to remove empty elements from array, but without sorting:
$Serveur = $Serveur | Where-Object { $_ } | Select -Unique
You have an array with 3 elements, so the count is 3. The element you got from the line ;;;;;;;; isn't $null, but an empty string (""), so it counts as a valid element. If you want to omit empty elements from the array, filter them out as C.B. suggested.
On a more general note, I'd recommend against using the += operator. Each operation copies the entire array to a new array, which is bound to perform poorly. It's also completely unnecessary here. Simply echo the value of the field and assign the output as a whole back to a variable:
$csv = 'C:\Users\aasif\Desktop\myfile.csv'
$Serveur = Import-Csv $csv -Delim ';' | % { $_.Serveur } | ? { $_ } | sort -uniq

I can't get PowerShell to cast as int correctly

I am currently importing a CSV file which has a column that is all numbers. I am attempting to cast it as an int and only pull ones that are greater than 100. I have manually gone through this CSV file, and I can confirm that there are three rows with a greater-than-100% value. However, this always returns 0. What am I doing wrong?
$percentTooLarge = Import-Csv path\file.csv | Foreach-Object { $_.SumHoldingPercent = $_.SumHoldingPercent -as [int]; $_ } | Where-Object { $_.SumHoldingPercent -gt 100 } | Measure-Object
$numPercentTooLarge = $percentTooLarge.Count
Because of the way compare operators work in PowerShell, this should do the trick:
$percentTooLarge = Import-Csv path\file.csv |
Where-Object { 100 -lt $_.SumHoldingPercent} |
Measure-Object
Basically, PowerShell, when you compare things, tries to convert right to the type of left. If you put a value from ipcsv first - left will be a string. If you put a numeric first - it will convert the value from the CSV file to a number (it will be smart enough to keep the type big-enough ;))
I tested with this code:
#"
foo,bar,percent
alfa,beta,120.5
beta,gamma,99.9
foo,bar,30.4
works,cool,120.7
"# | ConvertFrom-Csv | where { 100 -lt $_.percent }
... and the results seems OK.
Looking at your conversation with #Shay Levy, I would use:
[System.Globalization.CultureInfo] $culture = "en-us"
So you can try something like :
([decimal]::parse($_.SumHoldingPercent ,$culture)) -gt 100

In Powershell how can I check if all items from one array exist in a second array?

So let's say I have this array:
$requiredFruit= #("apple","pear","nectarine","grape")
And I'm given a second array called $fruitIHave. How can I check that $fruitIHave has everything in $requiredFruit. It doesn't matter if there are more items in $fruitIHave just as long as everything in $requiredFruit is there.
I know I could just iterate over the list, but that seems inefficient, is there a built-in method for doing this?
Do you try Compare-Object :
$requiredFruit= #("apple","pear","nectarine","grape")
$HaveFruit= #("apple","pin","nectarine","grape")
Compare-Object $requiredFruit $haveFruit
InputObject SideIndicator
----------- -------------
pin =>
pear <=
Compare-Object $requiredFruit $haveFruit | where {$_.sideindicator -eq "<="} | % {$_.inputobject}
pear
If you have the arrays:
$requiredFruit= #("apple","pear","nectarine","grape")
$someFruit= #("apple","banana","pear","nectarine","orange","grape")
$moreFruit= #("apple","banana","nectarine","grape")
You can get a boolean result with:
'Check $someFruit for $requiredFruit'
-not #($requiredFruit| where {$someFruit -notcontains $_}).Count
'Check $moreFruit for $requiredFruit'
-not #($requiredFruit| where {$moreFruit -notcontains $_}).Count
Using the count of an array protects against a single value not matching that evaluates as False. For example:
# Incorrect result
-not (0| where {(1,2) -notcontains $_})
# Correct result
-not #(0| where {(1,2) -notcontains $_}).Count
With PowerShell v3, you can use select -first 1 to stop the pipeline when the first mismatch is found (in v2 select -first 1 allows only one object through, but previous elements of the pipeline continue to process).
-not #($requiredFruit| where {$moreFruit -notcontains $_}| select -first 1).Count
Not exactly "builtin" but:
[regex] $RF_regex = ‘(?i)^(‘ + (($requiredFruit |foreach {[regex]::escape($_)}) –join “|”) + ‘)$’
($fruitIHave -match $RF_regex).count -eq $requiredFruit.count
That creates an alternating regex from the elements of $requiredFruit. Matched against $fruitIHave, it will return all the items that matched. If $fruitIhave could potentially have duplicates of the same fruit you may need to run that match result through get-unique before you do the count. It may be slower than iterating over the list for a single comparison, but once you have the regex built it will do repetitive matches very efficiently.
One way or the other, you're going to have to iterate through one or both arrays. Here's a one-liner approach:
$hasAllRequiredFruit = ($requiredFruit | Where-Object { $fruitIHave -contains $_ }).Length -eq $requiredFruit.Length;
A foreach loop would be better because you can stop iterating as soon as you find a required fruit that is missing:
$hasAllRequiredFruit = $true;
foreach ($f in $requiredFruit)
{
if ($fruitIHave -notcontains $f)
{
$hasAllRequiredFruit = $false;
break;
}
}

Resources