Search code examples
arrayspowershellcompare

Check if a list of values has the same values as in other list


I parse a text file with values and collect the individual values in a list. If there are sufficient bytes the list is sent to the output in Intel-HEX format. I want to suppress the lines where all values are 0xFF.

For that purpose I define a list $emptyline and fill it with the desired pattern. Now I want to compare the two list.

$empty = (0xFF, 0xFF, 0xFF, 0xFF)
$values = (0xFF, 0xFF, 0xFF, 0xFF)

$values.Equals($empty)

Surprisingly the last expression results in False. How do I check for the line with all values 0xFF?


Solution

  • tl;dr

    • Proper array comparison:
    $empty = (0xFF, 0xFF, 0xFF, 0xFF)
    $values = (0xFF, 0xFF, 0xFF, 0xFF)
    
    # -> $true
    ([Collections.IStructuralEquatable] $values).Equals(
      $empty,
      [Collections.Generic.EqualityComparer[int]]::Default
    )
    

    See the next section for an explanation.

    • Pragmatic shortcut via string comparison, which assumes that the array elements stringify meaningfully and unambiguously (which is the case here):
    # -> $true, i.e. the values are equal
    "$values" -eq "$empty"
    

    This relies on PowerShell's built-in array (collection) stringification, which joins the (stringified) elements with spaces (e.g., "$(1, 2)" -> "1 2")

    • Looking for non-0xFF values in a single array:
    # -> $true, i.e. all values are 0xFF
    ($values -ne 0xFF).Count -eq 0
    

    This relies on PowerShell's comparison operators such as -eq and its negated form, -ne, acting as filters if their LHS is an array (collection), i.e. they return a (new) sub-array of matching values.


    As for what you tried:

    $values.Equals($empty)
    Surprisingly the last expression results in False

    Your .Equals call calls the bool Equals(System.Object obj) method overload, which tests for reference equality, meaning that $true is only returned if the operands refer to the exact same array instance; e.g.:

    # !! $false, because the two arrays - despite having the same(-valued) elements -
    # !! are *different array instances*.
    @(1, 2).Equals(@(1, 2))
    

    However, there is an .Equals() overload that does what you want, namely that provided by the System.Collections.IStructuralEquatable interface, which has two parameters:

    bool IStructuralEquatable.Equals(System.Object other, System.Collections.IEqualityComparer comparer)

    Using that overload performs the element-by-element comparison you want, as shown above, with the help of a default System.Collections.Generic.EqualityComparer<T> instance that implements the System.Collections.IEqualityComparer interface.


    An alternative is to use the Compare-Object cmdlet, which is quite flexible and would also allow you identify specific differences between two arrays, if needed:

    $empty = (0xFF, 0xFF, 0xFF, 0xFF)
    $values = (0xFF, 0xFF, 0xFF, 0xFF)
    
    # -> $true
    -not (Compare-Object -SyncWindow 0 $empty $values | Select-Object -First 1)
    
    • Compare-Object returns only elements that differ between the input arrays (collections) by default, so the presence of any output implies that at least one difference was found.

    • -not implicitly treats the output as a [bool], which, based on PowerShell's implicit to-Boolean coercion rules (see the bottom section of this answer), means that no output yields $false, whereas a [pscustomobject] as output by Compare-Object yields $true.

    • -SyncWindow 0 and | Select-Object -First 1 are optimizations (which may not be necessary for small arrays):

      • -SyncWindow 0 ensures that only directly corresponding elements are compared; by default, the order of the elements doesn't matter. This allows for much faster comparison.
      • Select-Object -First 1 stops processing once Compare-Object has found the first difference, if any.