Fusionner des hashtables dans Powershell: comment?

j'essaye de fusionner deux hashtables, en écrasant les paires clé-valeur dans la première si la même clé existe dans la seconde.

pour ce faire, j'ai écrit cette fonction qui supprime d'abord toutes les paires de clés dans le premier hastable si la même clé existe dans le second hashtable.

quand je tape ceci dans Powershell ligne par ligne ça marche. Mais quand J'exécute la fonction entière, Powershell me demande de fournir (ce qu'il considère) les paramètres manquants pour foreach-object.

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

Sortie:

PS C:> mergehashtables $ht $ht2

cmdlet ForEach-Object at command pipeline position 1
Supply values for the following parameters:
Process[0]:

$ht et $ht2 sont des hashtables contenant deux paires de clés chacune, l'une avec la clé "name" dans les deux hashtables.

une idée de ce que je fais de mal?

18
demandé sur Andrew J. Brehm 2012-01-10 12:28:42

10 réponses

je vois deux problèmes:

  1. le corset ouvert doit être sur la même ligne que Foreach-object
  2. vous ne devriez pas modifier une collection tout en énumérant à travers une collection

L'exemple ci-dessous illustre comment résoudre ces deux problèmes:

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
}
12
répondu jon Z 2012-01-10 08:45:22

Fusionner Les, Tables De Hachage

au Lieu de supprimer les clés, vous pourriez envisager de simplement remplacer:

$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
}

pour ce cmdlet vous pouvez utiliser plusieurs syntaxes et vous n'êtes pas limité à deux tables d'entrée: Utilisation du pipeline:$h1, $h2, $h3 | Merge-Hashtables

Utilisation des arguments:Merge-Hashtables $h1 $h2 $h3

Ou une combinaison: $h1 | Merge-Hashtables $h2 $h3

Tous les exemples ci-dessus renvoient la même table de hachage:

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

S'il y a des clés dupliquées dans le les tables de hachage, la valeur de la dernière table de hachage est pris.


(Ajouté 2017-07-09)

Fusionner les, tables de hachage version 2

en général, je préfère des fonctions plus globales qui peuvent être personnalisées avec des paramètres à des besoins spécifiques comme dans la question originale: "l'écrasement de paires clé-valeur dans la première si la même clé existe dans le deuxième". Pourquoi laisser le dernier annuler et pas la première? Pourquoi les enlever quoi que ce soit? Peut-être quelqu'un d'autre veut fusionner ou joindre les valeurs ou obtenir la plus grande valeur ou juste la moyenne...

La version ci-dessous ne supporte plus la fourniture de tables de hachage comme arguments (vous ne pouvez utiliser que les tables de hachage pipe pour la fonction) mais a un paramètre qui vous permet de décider comment traiter le tableau de valeurs dans les entrées en double en utilisant le tableau de valeurs assigné à la clé de hachage présentée dans l'objet courant ($_).

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
}

Syntaxe

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

par Défaut Par défaut, toutes les valeurs des entrées de la table de hachage dupliquées seront ajoutées à un tableau:

PS C:\> $h1, $h2, $h3 | Merge-Hashtables

Name                           Value
----                           -----
e                              1
d                              {4, 2}
b                              {8, 6}
c                              {7, 5, 3}
a                              9

Exemples Pour obtenir le même résultat que la version 1 (en utilisant dernières valeurs) utilisez la commande:$h1, $h2, $h3 | Merge-Hashtables {$_[-1]}. Si vous souhaitez utiliser le valeurs à la place, la commande est: $h1, $h2, $h3 | Merge-Hashtables {$_[0]} ou valeurs les plus élevées:$h1, $h2, $h3 | Merge-Hashtables {($_ | Measure-Object -Maximum).Maximum}.

plus d'exemples:

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
17
répondu iRon 2017-10-26 06:30:49

ce n'est pas une nouvelle réponse, c'est fonctionnellement la même chose que @Josh-Petitt avec des améliorations.

Dans cette réponse:

  • Merge-HashTable utilise la bonne syntaxe powershell si vous voulez déposer ceci dans un module
  • N'était pas idempotent. J'ai ajouté le clonage de l'input HashTable, sinon votre input a été saboté, pas une intention
  • ajout d'un bon exemple d'utilisation
function Merge-HashTable {
    param(
        [hashtable] $default, # your original set
        [hashtable] $uppend # the set you want to update/append to the original set
    )

    # clone for idempotence
    $default1 = $default.Clone() ;

    # we need to remove any key-value pairs in $default1 that we will
    # be replacing with key-value pairs from $uppend
    foreach ($key in $uppend.Keys) {
        if ($default1.ContainsKey($key)) {
            $default1.Remove($key) ;
        }
    }

    # union both sets
    return $default1 + $uppend ;
}

# real life example of dealing with IIS AppPool parameters
$defaults = @{
    enable32BitAppOnWin64 = $false;
    runtime = "v4.0";
    pipeline = 1;
    idleTimeout = "1.00:00:00";
} ;
$options1 = @{ pipeline = 0; } ;
$options2 = @{ enable32BitAppOnWin64 = $true; pipeline = 0; } ;

$results1 = Merge-HashTable -default $defaults -uppend $options1 ;
# Name                           Value
# ----                           -----
# enable32BitAppOnWin64          False
# runtime                        v4.0
# idleTimeout                    1.00:00:00
# pipeline                       0

$results2 = Merge-HashTable -default $defaults -uppend $options2 ;
# Name                           Value
# ----                           -----
# idleTimeout                    1.00:00:00
# runtime                        v4.0
# enable32BitAppOnWin64          True
# pipeline                       0
5
répondu sonjz 2018-03-13 17:21:25

L'open corset doit être sur la même ligne que ForEach-Object ou vous devez utiliser le caractère de continuation de ligne (backtick).

C'est le cas parce que le code dans { ... } est vraiment la valeur de l' -Process paramètre ForEach-Object applet de commande.

-Process <ScriptBlock[]> 
Specifies the script block that is applied to each incoming object.

cela vous permettra de dépasser le problème actuel à portée de main.

4
répondu Andy Arismendi 2012-01-10 08:46:36

je voulais simplement développer ou simplifier la réponse de jon Z. Il semble juste y avoir trop de lignes et d'occasions manquées d'utiliser où-objet. Voici ma version simplifiée:

Function merge_hashtables($htold, $htnew) {
    $htold.Keys | ? { $htnew.ContainsKey($_) } | % {
      $htold.Remove($_)
    }
    $htold += $htnew
    return $htold
}
1
répondu Tony L. 2017-06-30 21:34:55

je voulais faire remarquer qu'il ne faut pas référencer les propriétés de base du hashtable de façon indistincte dans les fonctions génériques, car elles peuvent avoir été supplantées (ou surchargées) par des éléments du hashtable.

par exemple, le hashtable $hash=@{'keys'='lots of them'} la base de la table de hachage de la propriété, Keys remplacé par l'élément keys, et donc de faire un foreach ($key in $hash.Keys) énumérera à la place l'item hashé keys ' s valeur, au lieu de la propriété de base Keys.

à la Place de la méthode GetEnumerator ou le keys propriété de l' PSBase la propriété, qui ne peut pas être annulée, doit être utilisée dans des fonctions qui n'ont aucune idée si les propriétés de base ont été annulées.

ainsi, la réponse de Jon Z est la meilleure.

1
répondu msftrncs 2018-08-27 00:25:24

Voici une fonction version qui n'utilise pas le pipeline (pas que le pipeline est mauvais, juste une autre façon de le faire). Il renvoie aussi un hashtable fusionné et laisse l'original inchangé.

function MergeHashtable($a, $b)
{
    foreach ($k in $b.keys)
    {
        if ($a.containskey($k))
        {
            $a.remove($k)
        }
    }

    return $a + $b
}
0
répondu Josh Petitt 2014-03-13 21:47:29

je pense que le plus compact de code serait ceci:

function Merge-Hashtables($htold, $htnew)
{
   $htnew.keys | where {$_ -notin $htold.keys} | foreach {$htold[$_] = $htnew[$_]}
}

je l'ai emprunté à partir de https://stackoverflow.com/a/33839784/880076

0
répondu Mehrdad Mirreza 2017-05-23 11:54:28

encore une réponse!

Pour "hériter" clé-valeurs du parent hashtable ($htOld) pour enfant les tables de hashage($htNew), sans modifier les valeurs des clés déjà existantes dans les hashtables enfant,

function MergeHashtable($htOld, $htNew)
{
    $htOld.Keys | %{
        if (!$htNew.ContainsKey($_)) {
            $htNew[$_] = $htOld[$_];
        }
    }
    return $htNew;
}

veuillez noter que ceci modifiera l'objet $htNew.

0
répondu Sen Jacob 2018-04-09 07:17:11

j'ai juste besoin de faire cela et trouvé cela fonctionne.

$HT += $HT2

le contenu de $HT2 est ajouté au contenu de $HT

/Andrew

-2
répondu Scriptimus Prime 2013-09-08 15:09:01