Powershell. How to check that no string in array starts with some character?

Viewed 116

I already know how to check if a string in an array DOES start with some character:

foreach ($str in $arr) {
    if ($str.StartsWith("abc")) {
        do this
    }
}

But I need the code that will have somewhat different logic: "if no string in this array starts with abc, then do this"

Please help me to build code for that with the means of Powershell. I have no clue.

5 Answers

This is not an answer, it is a performance test, and results, of the answers

This is the test code and results for the code examples provided by Mathias, iRon, and Santiago.

If anyone spots any errors, logic or otherwise, please let me know so I can correct it.

Setup for the test:

Defines the array and variables used in the test.

function Get-RandomAlphaNumericString{
    [OutputType([string])]
    param (
        [parameter(Mandatory = $false, Position = 0)]
        [ValidateScript({$_ -gt 0})]
        [int]$Length = 1
    )
    [char[]]$Chars = [char[]]::new($Length)
    for ($i = 0; $i -lt $Length; $i++) {
        $Chars[$i] = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'[(Get-Random -Maximum 62)]
    }
    return [string]::new($Chars)
}
function Get-RandomAlphaNumericStrings{
    [OutputType([string[]])]
    param (
        [parameter(Mandatory = $false, Position = 0)]
        [ValidateScript({$_ -gt 0})]
        [int]$ArrayLength = 1,
        [parameter(Mandatory = $false, Position = 1)]
        [ValidateScript({$_ -gt 0})]
        [int]$StringLength = 1
    )
    [string[]]$Strings = [string[]]::new($ArrayLength)
    for ($i = 0; $i -lt $ArrayLength; $i++) {
        $Strings[$i] = Get-RandomAlphaNumericString $StringLength
    }
    return $Strings
}
$ArraySize = 100000
$LoopCount = 100
$SearchStringLength = 5

$arr = Get-RandomAlphaNumericStrings $ArraySize 20
[int]$IdxAt10Percent = $arr.Length * 0.1
[int]$IdxAt50Percent = $arr.Length * 0.5
[int]$IdxAt90Percent = $arr.Length * 0.9
$At10PercentPrefix = $arr[$IdxAt10Percent].Substring(0, $SearchStringLength)
$At50PercentPrefix = $arr[$IdxAt50Percent].Substring(0, $SearchStringLength)
$At90PercentPrefix = $arr[$IdxAt90Percent].Substring(0, $SearchStringLength)
$IsMissingPrefix = '####################'.Substring(0, $SearchStringLength)
$At10PercentLike = $At10PercentPrefix + '*'
$At50PercentLike = $At50PercentPrefix + '*'
$At90PercentLike = $At90PercentPrefix + '*'
$ThatIsMissingLike = $IsMissingPrefix + '*'

Mathias test code:

Write-Host "`nMathias:"
Write-Host "    Array of $ArraySize elements"
Write-Host "    Repeat for $LoopCount loops"
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        if(-not(@($arr) -like $At10PercentLike)){
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Index Position = ${IdxAt10Percent}: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        if(-not(@($arr) -like $At50PercentLike)){
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Index Position = ${IdxAt50Percent}: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        if(-not(@($arr) -like $At90PercentLike)){
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Index Position = ${IdxAt90Percent}: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        if(-not(@($arr) -like $ThatIsMissingLike)){
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Missing: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}

iRon's test code:

Write-Host "`niRon:"
Write-Host "    Array of $ArraySize elements"
Write-Host "    Repeat for $LoopCount loops"
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        if (!($Arr.StartsWith($At10PercentPrefix) -eq $True)) {
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Index Position = ${IdxAt10Percent}: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        if (!($Arr.StartsWith($At50PercentPrefix) -eq $True)) {
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Index Position = ${IdxAt50Percent}: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        if (!($Arr.StartsWith($At90PercentPrefix) -eq $True)) {
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Index Position = ${IdxAt90Percent}: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        if (!($Arr.StartsWith($IsMissingPrefix) -eq $True)) {
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Missing: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}

Santiago's test code:

Write-Host "`nSantiago:"
Write-Host "    Array of $ArraySize elements"
Write-Host "    Repeat for $LoopCount loops"
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        $comparison = foreach ($str in $arr) {
            if ($str.StartsWith($At10PercentPrefix)) {
                $true
                break
            }
        }
        if(-not $comparison) {
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Index Position = ${IdxAt10Percent}: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        $comparison = foreach ($str in $arr) {
            if ($str.StartsWith($At50PercentPrefix)) {
                $true
                break
            }
        }
        if(-not $comparison) {
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Index Position = ${IdxAt50Percent}: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        $comparison = foreach ($str in $arr) {
            if ($str.StartsWith($At90PercentPrefix)) {
                $true
                break
            }
        }
        if(-not $comparison) {
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Index Position = ${IdxAt90Percent}: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}
$NotFound = $false
Measure-Command {&{
    for ($i = 0; $i -lt $LoopCount; $i++) {
        $comparison = foreach ($str in $arr) {
            if ($str.StartsWith($IsMissingPrefix)) {
                $true
                break
            }
        }
        if(-not $comparison) {
            $NotFound = $true
        }
    }
}} | ForEach-Object { Write-Host "Missing: { $($_.Seconds + $_.Milliseconds/1000) seconds }"}

Typical PowerShell 5.1 results:

Mathias:
    Array of 100000 elements
    Repeat for 100 loops
Index Position = 10000: { 3.937 seconds }
Index Position = 50000: { 3.917 seconds }
Index Position = 90000: { 3.919 seconds }
Missing: { 3.919 seconds }

iRon:
    Array of 100000 elements
    Repeat for 100 loops
Index Position = 10000: { 17.514 seconds }
Index Position = 50000: { 17.589 seconds }
Index Position = 90000: { 17.45 seconds }
Missing: { 17.502 seconds }

Santiago:
    Array of 100000 elements
    Repeat for 100 loops
Index Position = 10000: { 0.749 seconds }
Index Position = 50000: { 3.706 seconds }
Index Position = 90000: { 6.684 seconds }
Missing: { 7.287 seconds }

Typical PowerShell Core 7.2.6 results:

Mathias:
    Array of 100000 elements
    Repeat for 100 loops
Index Position = 10000: { 1.063 seconds }
Index Position = 50000: { 1.063 seconds }
Index Position = 90000: { 1.057 seconds }
Missing: { 1.051 seconds }

iRon:
    Array of 100000 elements
    Repeat for 100 loops
Index Position = 10000: { 10.514 seconds }
Index Position = 50000: { 10.818 seconds }
Index Position = 90000: { 10.713 seconds }
Missing: { 10.57 seconds }

Santiago:
    Array of 100000 elements
    Repeat for 100 loops
Index Position = 10000: { 0.367 seconds }
Index Position = 50000: { 1.697 seconds }
Index Position = 90000: { 3.085 seconds }
Missing: { 3.329 seconds }

Code Corrections:

Santiago pointed out:

When doing performance tests with Measure-Command, I would recommend you to always wrap all the code inside another scriptblock and execute it with & to have fair and more accurate results

To my surprise, this makes a notable difference. It isolates the code being tested from the code doing the test. Read Santiago's comment for his description of the problem.

The -like comparison operator will act as a filter when you apply it to an array - making it easy to determine if any string in the array starts with abc:

if(@($arr) -like 'abc*'){
    # at least 1 string in $arr starts with abc
}

To get the opposite, use the -not operator:

if(-not(@($arr) -like 'abc*')){
    # no string in $arr starts with abc
}

Keeping the code you already have and showing a different alternative, you can loop through each element of the array and when the condition becomes true you can return $true and break your loop as no further comparison is needed:

$comparison = foreach ($str in $arr) {
    if ($str.StartsWith("abc")) {
        $true
        break
    }
}

if(-not $comparison) {
    # here is if no element of `$arr` starts with abc
}

Using member-access enumeration and this comparison operator feature:

When the input of an operator is a scalar value, the operator returns a Boolean value. When the input is a collection, the operator returns the elements of the collection that match the right-hand value of the expression. If there are no matches in the collection, comparison operators return an empty array. For example:

if (!($Arr.StartsWith("abc") -eq $True)) {
    'no string in $arr starts with abc'
}

If it concerns a very large array and you don't want to search further when any string starting with abc is already found (as the helpful suggestion from Santiago), you might also use the PowerShell Where method:

if (!($Arr.Where({ $_.StartsWith("abc") }, 'First'))) {
    'no string in $arr starts with abc'
}

Note that in comparison with PowerShell's native case sensitivity the .Net StartsWith method is case sensitive.

Using your example:

foreach ($str in $arr) {
    if (-not $str.StartsWith("abc")) {
        do this
    }
}

Or,

$Filter = @('abc', 'def', 'abcghj') | Where-Object { -not $_.startswith('a') }

Then you can iterate over $Filter

Edit: I understood OPs question wrong,

# This var is empty if all elements do not match
$MatchFound = @('abc', 'def', 'abcghj') | Where-Object { $_.StartsWith('efg') }

if (-not $MatchFound)
{
    # Do something
}

From your code:

# If match found $MatchFound will be set to True
$MatchFound = foreach ($str in $arr) {
    if ($str.StartsWith("abc")) {
       $True 
       break
    }
}

# Only do something when no matches were found, i.e. $MatchFound was empty
if (-not $MatchFound)
{
    # Do something
}
Related