Question [Fonction] Inverser une hashtable

Plus d'informations
il y a 1 mois 1 semaine - il y a 1 mois 1 semaine #29863 par Laurent Dardenne
On peut parfois souhaiter déclarer une hashtable (clé=valeur) et tout en créant l'inverse ( valeur=clé). Par exemple :
$h=@{
'un'=1
}

#et 

$h2=@{
1='un'
}
Ce qui permet de manipuler une information soit d'après son nom soit d'après sa valeur qui lui est attribuée :
$key='un'
$H.$key
$key='1'
$H2.$key
L'inconvénient ici est que cette approche doit créer 2 variables, là où le C# par exemple propose des indexers .
Les classes Powershell permettent ce type de construction (voir chapitre 6.5 Indexeur) mais cela nécessite qq connaissances supplémentaires.
De plus on peut encore trouver des environnements en PS v3.0 ou v4.0 ( ce qui est mon cas et je ne peux ni charger de dll ni utiliser Add-Type).

On reprend donc les bases de Powershell, ici ETS, tout en codant simplement. Ce qui nous donne ceci:
<?xml version="1.0" encoding="utf-8" ?>
<Types>
 <Type>
     <Name>System.Collections.Hashtable</Name>
     <Members>
       <ScriptMethod>
         <Name>Reverse</Name>
          <!-- Les types primitifs sont :
            Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double et Single. 
          -->
         <Script>
            $ReverseHashtable=@{}

            Foreach ($Current in $this.GetEnumerator())
            {
              $Key=$Current.Key
              $Value=$Current.Value
              if ($null -eq $Value)
              { Throw "Impossible to reverse the hashtable. The key '$Key' has a value null.The value of a key cannot be null." }
              $ValueType=$Value.Gettype()
              if ( ($ValueType -isnot [string]) -and $ValueType.isPrimitive)
              { 
                try {
                  $ReverseHashtable.Add($Current.Value,$Key) 
                } catch [System.ArgumentException] {
                  Throw "Impossible to reverse the hashtable. Key values '$($Current.Value)' are duplicated."
                }
              }
              else
              { Throw "Impossible to reverse the hashtable. The value '$Key' is not a scalar or a string :'$ValueType'." }
            }
            return ,$ReverseHashtable
         </Script>
       </ScriptMethod>
     </Members>
  </Type>
    <Type>
     <Name>System.Collections.Specialized.OrderedDictionary</Name>
     <Members>
       <ScriptMethod>
         <Name>Reverse</Name>
         <Script>
            if ($this.IsReadOnly)
            { $ReverseHashtable=[ordered]@{} }
            else
            { $ReverseHashtable=@{} }

            Foreach ($Current  in $this.GetEnumerator())
            {
              $Key=$Current.Key
              $Value=$Current.Value
              if ($null -eq $Value)
              { Throw "Impossible to reverse the hashtable. The key the key '$Key' has a value null.The value of a key cannot be null." }              
              $ValueType=$Value.Gettype()

              if ( ($ValueType -isnot [string]) -and $ValueType.isPrimitive)
              { 
                try {
                  $ReverseHashtable.Add($Current.Value,$Key) 
                } catch [System.ArgumentException] {
                  Throw "Impossible to reverse the hashtable. Key values '$($Current.Value)' are duplicated."
                }
              }
              else
              { Throw "Impossible to reverse the hashtable. The value of the key '$Key' is not a scalar or a string :'$ValueType'." }
            }

            if ($this.IsReadOnly)
            { return ,$ReverseHashtable.AsReadOnly() }
            return ,$ReverseHashtable
         </Script>
       </ScriptMethod>
     </Members>
  </Type>
</Types>
Ce qui reste accessible je pense.

On duplique le code car on doit déclarer les types 'System.Collections.Hashtable' et 'System.Collections.Specialized.OrderedDictionary'.
La présence du second type est liée à cette astuce .

Ce qui autorise, à partir d'un hashtable en ReadOnly, la création d'un hashtable inversée et toujours en ReadOnly.

Une fois enregistré ce fichier, on l'utilise ainsi :
$File='..\System.Collections.Hashtable.ps1xml'
Update-TypeData -PrependPath $File

$h=@{
    'un'=1
    'deux'=2
    'trois'=3
}

$h.Reverse()
# Name                           Value
# ----                           -----
# 3                              trois
# 2                              deux
# 1                              un
Les cas d'erreur étant :
$h=@{
    'un'=1
    'deux'=2
    'trois'=$Null
}
$h2=$h.Reverse()
#Exception lors de l'appel de «Reverse» avec «0» argument(s): «Impossible to reverse the hashtable. 
#The key 'trois' has a value null.The value of a key cannot be null.»
#
#Une clé de hashtable ne peut $etre $null

$h=@{
    'un'=1
    'deux'=2
    'trois'=1
}
$h2=$h.Reverse()
#Exception lors de l'appel de «Reverse» avec «0» argument(s): «Impossible to reverse the hashtable. 
#Key values '1' are duplicated.»
#
#les valeurs devenant des clés on ne peut dupliquer un nom de clé

$h=@{
    'un'=1
    'deux'=(get-item 'G:\PS\Hashtable\Reverse.Tests.ps1') #$PSCommandPath)
    'trois'=3
}
$h2=$h.Reverse()
#Exception lors de l'appel de «Reverse» avec «0» argument(s): «Impossible to reverse the hashtable. 
#The value 'deux' is not a scalar or a string :'System.IO.FileInfo'.»
#
#Pour un usage sous Powershell on évite ce type de construction.

Une fonction identique qui ne s'appuie pas sur ETS :
function New-ReversedHashtable {
    param($Hashtable)
    
    if(-not  ( ($Hashtable -is [System.Collections.Hashtable]) -OR ($Hashtable-is [System.Collections.Specialized.OrderedDictionary])) )
    { Throw "The argument `$Hashtable([$($Hashtable.GetType().Fullname)]) the argument must be one of the following types : [System.Collections.Hashtable], [System.Collections.Specialized.OrderedDictionary]" }
    $isReadOnly=$Hashtable.IsReadOnly
   
    if ($isReadOnly)
    { $ReverseHashtable=[ordered]@{} }
    else
    { $ReverseHashtable=@{} }  
    
    Foreach ($Current in $Hashtable.GetEnumerator())
    {
       $Key=$Current.Key
       $Value=$Current.Value
       if ($null -eq $Value) #ArgumentNullException
       { Throw "Impossible to reverse the hashtable. The key '$Key' has a value null.The value of a key cannot be null." }
       $ValueType=$Value.Gettype()
   
       if ( ($ValueType -isnot [string]) -and $ValueType.isPrimitive)
       { 
           try {
               $ReverseHashtable.Add($Current.Value,$Key) 
           } catch [System.ArgumentException] { #ArgumentException
               Throw "Impossible to reverse the hashtable. Key values '$($Current.Value)'' are duplicated."
           }
       }
       else
       { Throw "Impossible to reverse the hashtable. The value of the key '$Key' is not a scalar or a string :'$ValueType'." }
    }
    
    if ($isReadOnly)
    { return ,$ReverseHashtable.AsReadOnly() }
    return ,$ReverseHashtable
}
Mais j’entends une personne au fond de la salle près du radiateur qui me dit "Comment inverser une hashtable générique ?"
Exemple :
$h = new-object 'System.Collections.Generic.Dictionary[String,Int]'
$h.'Un'=1
$h.'Deux'=2
$h.'Trois'=3
Bha, c'est un autre sujet (*).
;-)
$KeyDelegate =  [Func[[System.Collections.Generic.KeyValuePair[String,Int]],String]]{ $args[0].Key }
$ValueDelegate =  [Func[[System.Collections.Generic.KeyValuePair[String,Int]],Int]]{ $args[0].Value}
$Result=[Linq.Enumerable]::ToDictionary($h, $ValueDelegate,$KeyDelegate)
$Result.GetType().Fullname
$Result
Powershell c'est facile, mais ce n'est pas tout les jours facile...

Note: je n'ai pas de lien sur ETS (Extended Type System) à proposer car MS joue au bonneteau avec la doc de Powershell et surtout avec ce sujet.

*
Le sujet étant d'inverser n'importe quel type de hashtable générique, voir d'utiliser d'autres types avec la méthode [Linq.Enumerable]::ToDictionary
---

Tutoriels PowerShell
Dernière édition: il y a 1 mois 1 semaine par Laurent Dardenne. Raison: Balise code

Connexion ou Créer un compte pour participer à la conversation.

Temps de génération de la page : 0.158 secondes
Propulsé par Kunena