Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merging hashtables in PowerShell: how?

Tags:

I am trying to merge two hashtables, overwriting key-value pairs in the first if the same key exists in the second.

To do this I wrote this function which first removes all key-value pairs in the first hastable if the same key exists in the second hashtable.

When I type this into PowerShell line by line it works. But when I run the entire function, PowerShell asks me to provide (what it considers) missing parameters to foreach-object.

function mergehashtables($htold, $htnew) {     $htold.getenumerator() | foreach-object     {         $key = $_.key         if ($htnew.containskey($key))         {             $htold.remove($key)         }     }     $htnew = $htold + $htnew     return $htnew } 

Output:

PS C:\> mergehashtables $ht $ht2  cmdlet ForEach-Object at command pipeline position 1 Supply values for the following parameters: Process[0]: 

$ht and $ht2 are hashtables containing two key-value pairs each, one of them with the key "name" in both hashtables.

What am I doing wrong?

like image 431
Andrew J. Brehm Avatar asked Jan 10 '12 08:01

Andrew J. Brehm


People also ask

How do I merge two hash tables in PowerShell?

Adding values of the hash table is simple as the adding string. We just need to use the addition operator (+) to merge two hash table values.

How do I get key value pairs in PowerShell?

The key/value pairs might appear in a different order each time that you display them. Although you cannot sort a hash table, you can use the GetEnumerator method of hash tables to enumerate the keys and values, and then use the Sort-Object cmdlet to sort the enumerated values for display.

How do I add values to a Hashtable in PowerShell?

You can also use the Hashtable method called Add() to add the value. The format is as below. To remove the Key-value from the Hashtable, you need to use the Remove(Key) method. You cannot remove the hashtable entry with the values.

How do I iterate through a Hashtable in PowerShell?

In the first method, the one that I prefer, you can use the GetEnumerator method of the hash table object. In the second method, instead of iterating over the hash table itself, we loop over the Keys of the hash table. Both of these methods produce the same output as our original version.


2 Answers

Merge-Hashtables

Instead of removing keys you might consider to simply overwrite them:

$h1 = @{a = 9; b = 8; c = 7} $h2 = @{b = 6; c = 5; d = 4} $h3 = @{c = 3; d = 2; e = 1}   Function Merge-Hashtables {     $Output = @{}     ForEach ($Hashtable in ($Input + $Args)) {         If ($Hashtable -is [Hashtable]) {             ForEach ($Key in $Hashtable.Keys) {$Output.$Key = $Hashtable.$Key}         }     }     $Output } 

For this cmdlet you can use several syntaxes and you are not limited to two input tables: Using the pipeline: $h1, $h2, $h3 | Merge-Hashtables
Using arguments: Merge-Hashtables $h1 $h2 $h3
Or a combination: $h1 | Merge-Hashtables $h2 $h3
All above examples return the same hash table:

Name                           Value ----                           ----- e                              1 d                              2 b                              6 c                              3 a                              9 

If there are any duplicate keys in the supplied hash tables, the value of the last hash table is taken.


(Added 2017-07-09)

Merge-Hashtables version 2

In general, I prefer more global functions which can be customized with parameters to specific needs as in the original question: "overwriting key-value pairs in the first if the same key exists in the second". Why letting the last one overrule and not the first? Why removing anything at all? Maybe someone else want to merge or join the values or get the largest value or just the average...
The version below does no longer support supplying hash tables as arguments (you can only pipe hash tables to the function) but has a parameter that lets you decide how to treat the value array in duplicate entries by operating the value array assigned to the hash key presented in the current object ($_).

Function

Function Merge-Hashtables([ScriptBlock]$Operator) {     $Output = @{}     ForEach ($Hashtable in $Input) {         If ($Hashtable -is [Hashtable]) {             ForEach ($Key in $Hashtable.Keys) {$Output.$Key = If ($Output.ContainsKey($Key)) {@($Output.$Key) + $Hashtable.$Key} Else  {$Hashtable.$Key}}         }     }     If ($Operator) {ForEach ($Key in @($Output.Keys)) {$_ = @($Output.$Key); $Output.$Key = Invoke-Command $Operator}}     $Output } 

Syntax

HashTable[] <Hashtables> | Merge-Hashtables [-Operator <ScriptBlock>] 

Default By default, all values from duplicated hash table entries will added to an array:

PS C:\> $h1, $h2, $h3 | Merge-Hashtables  Name                           Value ----                           ----- e                              1 d                              {4, 2} b                              {8, 6} c                              {7, 5, 3} a                              9 

Examples To get the same result as version 1 (using the last values) use the command: $h1, $h2, $h3 | Merge-Hashtables {$_[-1]}. If you would like to use the first values instead, the command is: $h1, $h2, $h3 | Merge-Hashtables {$_[0]} or the largest values: $h1, $h2, $h3 | Merge-Hashtables {($_ | Measure-Object -Maximum).Maximum}.

More examples:

PS C:\> $h1, $h2, $h3 | Merge-Hashtables {($_ | Measure-Object -Average).Average} # Take the average values"  Name                           Value ----                           ----- e                              1 d                              3 b                              7 c                              5 a                              9   PS C:\> $h1, $h2, $h3 | Merge-Hashtables {$_ -Join ""} # Join the values together  Name                           Value ----                           ----- e                              1 d                              42 b                              86 c                              753 a                              9   PS C:\> $h1, $h2, $h3 | Merge-Hashtables {$_ | Sort-Object} # Sort the values list  Name                           Value ----                           ----- e                              1 d                              {2, 4} b                              {6, 8} c                              {3, 5, 7} a                              9 
like image 50
iRon Avatar answered Oct 21 '22 15:10

iRon


I see two problems:

  1. The open brace should be on the same line as Foreach-object
  2. You shouldn't modify a collection while enumerating through a collection

The example below illustrates how to fix both issues:

function mergehashtables($htold, $htnew) {     $keys = $htold.getenumerator() | foreach-object {$_.key}     $keys | foreach-object {         $key = $_         if ($htnew.containskey($key))         {             $htold.remove($key)         }     }     $htnew = $htold + $htnew     return $htnew } 
like image 34
jon Z Avatar answered Oct 21 '22 13:10

jon Z