web-dev-qa-db-ja.com

PowerShellでのハッシュテーブルのマージ:どのように?

同じキーが2番目に存在する場合、最初のハッシュテーブルのキーと値のペアを上書きして、2つのハッシュテーブルをマージしようとしています。

これを行うために、同じキーが2番目のハッシュテーブルに存在する場合、最初に最初のハスタブルのすべてのキーと値のペアを削除するこの関数を作成しました。

これをPowerShellに1行ずつ入力すると、機能します。しかし、関数全体を実行すると、PowerShellは、欠落しているパラメーターをforeach-objectに提供するように要求します。

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

出力:

PS C:\> mergehashtables $ht $ht2

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

$ htと$ ht2は、それぞれ2つのキーと値のペアを含むハッシュテーブルであり、そのうちの1つは両方のハッシュテーブルにキー「name」があります。

私は何が間違っているのですか?

20
Andrew J. Brehm

2つの問題があります。

  1. 中括弧はForeach-objectと同じ行にある必要があります
  2. コレクションを列挙している間は、コレクションを変更しないでください。

以下の例は、両方の問題を修正する方法を示しています。

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
}
15
jon Z

マージ-ハッシュテーブル

キーを削除する代わりに、単にキーを上書きすることを検討してください。

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

このコマンドレットでは、いくつかの構文を使用でき、2つの入力テーブルに制限されません。パイプラインの使用:_$h1, $h2, $h3 | Merge-Hashtables_
引数の使用:_Merge-Hashtables $h1 $h2 $h3_
または組み合わせ:_$h1 | Merge-Hashtables $h2 $h3_
上記の例はすべて同じハッシュテーブルを返します。

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

提供されたハッシュテーブルに重複するキーがある場合は、最後のハッシュテーブルの値が取得されます。


(2017-07-09追加)

マージ-ハッシュテーブルバージョン2

一般に、元の質問のように、特定のニーズに合わせてパラメーターを使用してカスタマイズできる、よりグローバルな関数を好みます:「同じキーが2番目に存在する場合、最初のキーと値のペアを上書きする」 。なぜ最初のものではなく最後のものを却下させるのですか?なぜ何も削除しないのですか?たぶん、他の誰かが値をマージまたは結合したり、最大の値または単に平均を取得したい...
以下のバージョンでは、ハッシュテーブルを引数として指定することはサポートされていません(ハッシュテーブルを関数にパイプすることしかできません)が、値配列を操作して重複エントリの値配列を処理する方法を決定できるパラメーターがあります現在のオブジェクト(_$__)に表示されるハッシュキーに割り当てられます。

関数

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

構文

_HashTable[] <Hashtables> | Merge-Hashtables [-Operator <ScriptBlock>]
_

デフォルトデフォルトでは、複製されたハッシュテーブルエントリのすべての値が配列に追加されます。

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

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

バージョン1と同じ結果を得るには(最後の値を使用)、コマンド_$h1, $h2, $h3 | Merge-Hashtables {$_[-1]}_を使用します。代わりに最初の値を使用する場合、コマンドは_$h1, $h2, $h3 | Merge-Hashtables {$_[0]}_または最大値$h1, $h2, $h3 | Merge-Hashtables {($_ | Measure-Object -Maximum).Maximum}です。

その他の例:

_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
_
23
iRon

新しい答えではありません。これは機能的には@ Josh-Petittと同じですが、改善されています。

この答えでは:

  • Merge-HashTableこれをモジュールにドロップする場合は、正しいPowerShell構文を使用します
  • べき等ではありませんでした。 HashTable入力のクローンを追加しました。そうしないと、意図ではなく、入力が破壊されました。
  • 適切な使用例を追加しました
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
7
sonjz

中括弧はForEach-Objectと同じ行にある必要があります。そうでない場合は、行継続文字(バッククォート)を使用する必要があります。

これは、{...}内のコードが、実際には-ProcessコマンドレットのForEach-Objectパラメーターの値であるためです。

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

これにより、目前の現在の問題を乗り越えることができます。

4
Andy Arismendi

ハッシュテーブルのアイテムによってオーバーライド(またはオーバーロード)されている可能性があるため、ジェネリック関数でハッシュテーブルの基本プロパティを無差別に参照するべきではないことを指摘したいと思います。

たとえば、ハッシュテーブル_$hash=@{'keys'='lots of them'}_には基本ハッシュテーブルプロパティがあり、Keysはアイテムkeysによってオーバーライドされるため、foreach ($key in $hash.Keys)を実行すると代わりにハッシュが列挙されます。基本プロパティkeysの代わりに、item Keysの値。

代わりに、メソッドGetEnumeratorまたはkeysプロパティのPSBaseプロパティは、オーバーライドできませんが、基本プロパティがオーバーライドされているかどうかわからない関数で使用する必要があります。

したがって、ジョンZの答えは最高です。

1
msftrncs

jon Zの答え を拡張または単純化したかっただけです。行が多すぎて、使用する機会を逃したようです Where-Object 。これが私の簡略版です:

Function merge_hashtables($htold, $htnew) {
    $htold.Keys | ? { $htnew.ContainsKey($_) } | % {
        $htold.Remove($_)
    }
    $htold += $htnew
    return $htold
}
1
Tony L.

ハッシュテーブルツリー全体をマージしたい場合

function Join-HashTableTree {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $SourceHashtable,

        [Parameter(Mandatory = $true, Position = 0)]
        [hashtable]
        $JoinedHashtable
    )

    $output = $SourceHashtable.Clone()

    foreach ($key in $JoinedHashtable.Keys) {
        $oldValue = $output[$key]
        $newValue = $JoinedHashtable[$key]

        $output[$key] =
        if ($oldValue -is [hashtable] -and $newValue -is [hashtable]) { $oldValue | ~+ $newValue }
        elseif ($oldValue -is [array] -and $newValue -is [array]) { $oldValue + $newValue }
        else { $newValue }
    }

    $output;
}

次に、次のように使用できます。

Set-Alias -Name '~+' -Value Join-HashTableTree -Option AllScope

@{
    a = 1;
    b = @{
        ba = 2;
        bb = 3
    };
    c = @{
        val = 'value1';
        arr = @(
            'Foo'
        )
    }
} |

~+ @{
    b = @{
        bb = 33;
        bc = 'hello'
    };
    c = @{
        arr = @(
            'Bar'
        )
    };
    d = @(
        42
    )
} |

ConvertTo-Json

次の出力が生成されます。

{
  "a": 1,
  "d": 42,
  "c": {
    "val": "value1",
    "arr": [
      "Foo",
      "Bar"
    ]
  },
  "b": {
    "bb": 33,
    "ba": 2,
    "bc": "hello"
  }
}
1
AndrIV

子ハッシュテーブル内の既存のキーの値を変更せずに、親ハッシュテーブル($htOld)から子ハッシュテーブル($htNew)にキー値を「継承」するには、

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

これにより、$htNewオブジェクトが変更されることに注意してください。

1
Sen Jacob

これはパイプラインを使用しない関数バージョンです(パイプラインが悪いわけではなく、それを行う別の方法です)。また、マージされたハッシュテーブルを返し、元のハッシュテーブルを変更しません。

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

    return $a + $b
}
0
Josh Petitt

最もコンパクトなコードはこれだと思います:

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

PowerShellのハッシュテーブルの結合と共通部分から借用しました

0
Mehrdad Mirreza