How can I enumerate a hashtable as key-value pairs / filter a hashtable by a collection of key values

前端 未结 2 702
闹比i
闹比i 2020-12-17 08:33

Editor\'s note: This question has a complicated history, but boils down to this:
* To learn how to enumerate the entries of a has

相关标签:
2条回答
  • 2020-12-17 09:10

    To complement briantist's helpful answer by focusing on filtering a hashtable by an array of key values (PSv3+ syntax):

    # Sample hashtable.
    $ht = @{ one = 1; two = 2; three = 3 }
    
    # Filter it by an array of key values; applying .GetEnumerator() yields an array
    # of [System.Collections.DictionaryEntry] instances, which have
    # a .Key property and a .Value property.
    $ht.GetEnumerator()  | ? Key -in 'one', 'two'
    
    # Similarly, the *output* - even though it *looks* like a hashtable - 
    # is a regular PS *array* ([Object[]]) containing [System.Collections.DictionaryEntry]
    # entries (2 in this case).
    $arrFilteredEntries = $ht.GetEnumerator()  | ? Key -in 'one', 'two'
    $arrFilteredEntries.GetType().Name # -> Object[]
    

    To further process the matching key-value pairs, simply pipe to % (ForEach-Object) and access $_.Key and $_.Value (value):

    $ht.GetEnumerator()  | ? Key -in 'one', 'two' | 
      % { "Value for key '$($_.Key)': $($_.Value)" }
    

    The equivalent command using a more efficient foreach loop instead of the pipeline:

    foreach ($key in $ht.Keys) { 
      if ($key -in 'one', 'two') { "Value for key '$($key)': $($ht.$key)" }
    }
    

    Note: In PSv2:
    * operator -in is not supported, but you can use -contains instead with the operands swapped:
    'one', 'two' -contains $key
    * in the pipeline, use Where-Object { 'one', 'two' -contains $_.Key }

    With the sample hashtable, this yields:

    Value for key 'two': 2
    Value for key 'one': 1
    

    Note how the key order in the output differs from the definition order; in PSv3+, you can create ordered hashtables ([ordered] @{ ... }) to preserve the definition order.

    The key-filtering technique used above is not limited to filtering by literal key arrays; any (string) collection will do as the RHS of the -in operand, such as the .Keys collection of a different hashtable:

    # Sample input hashtable.
    $htInput = @{ one = 1; two = 2; three = 3 }
    
    # Hashtable by whose keys the input hashtable should be filtered.
    # Note that the entries' *values* are irrelevant here.
    $htFilterKeys = @{ one = $null; two = $null }
    
    # Perform filtering.
    $htInput.GetEnumerator()  | ? Key -in $htFilterKeys.Keys | 
      % { "Value for key '$($_.Key)': $($_.Value)" }
    
    # `foreach` loop equivalent:
    foreach ($key in $htInput.Keys) {
      if ($key -in $htFilterKeys.Keys) { "Value for key '$($key)': $($htInput.$key)" }
    }
    

    The result is the same as in the example with the static filter-keys array.

    Finally, if you want to filter a hashtable in place or create a new hashtable with only the filtered entries:

    # *In-place* Updating of the hashtable.
    # Remove entries other than the ones matching the specified keys.
    # Note: The @(...) around $ht.Keys is needed to clone the keys collection before
    # enumeration, so that you don't get an error about modifying a collection
    # while it is being enumerated.
    foreach ($key in @($ht.Keys)) { 
      if ($key -notin 'one', 'two') { $ht.Remove($key) } 
    } 
    
    # Create a *new* hashtable with only the filtered entries.
    # By accessing the original's .Keys collection, the need for @(...) is obviated.
    $htNew = $ht.Clone()
    foreach ($key in $ht.Keys) { 
      if ($key -notin 'one', 'two') { $htNew.Remove($key) }
    } 
    

    As an aside:

    The default output format for [System.Collections.DictionaryEntry] (and thus hashtables ([System.Collections.Hashtable]) uses column name Name rather than Key; Name is defined as an alias property of Key added by PowerShell (it is not part of the [System.Collections.DictionaryEntry].NET type definition; verify with
    @{ one = 1 }.GetEnumerator() | Get-Member).

    0 讨论(0)
  • 2020-12-17 09:12

    You have some options here.

    Enumerating through keys:

    foreach ($key in $var.Keys) {
        $value = $var[$key]
        # or
        $value = $var.$key 
    }
    

    Enumerating key-value pairs (which you've discovered, but may not be using effectively):

    foreach ($kvp in $var.GetEnumerator()) {
        $key = $kvp.Key
        $val = $kvp.Value
    }
    
    0 讨论(0)
提交回复
热议问题