Объединение хэш-таблиц в Powershell: как?

Я пытаюсь объединить две хэш-таблицы, перезаписывая пары ключ-значение в первой, если тот же ключ существует во второй.

для этого я написал эту функцию, которая сначала удаляет все пары ключ-значение в первой hastable, если тот же ключ существует во второй hashtable.

когда я набираю это в Powershell строка за строкой, это работает. Но когда я запускаю всю функцию, Powershell просит меня предоставить (что он считает) отсутствующие параметры по каждому элементу-объекта.

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-хэш-таблицы, содержащие по две пары ключ-значение, одна из которых имеет ключ "имя" в обеих хэш-таблицах.

есть идеи, что я делаю неправильно?

10 ответов


Я вижу две проблемы:

  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
}

Слияние-Хеш-Таблицы

вместо удаления ключей вы можете просто перезаписать их:

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

для этого командлета можно использовать несколько синтаксисов и не ограничиваться двумя входными таблицами: Использование конвейера:$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)

Merge-Hashtables версия 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

не новый ответ, это функционально то же самое, что и @Josh-Petitt с улучшениями.

в ответ:

  • Merge-HashTable использует правильный синтаксис powershell, если вы хотите поместить это в модуль
  • не идемпотентом. Я добавил клонирование ввода HashTable, иначе ваш ввод был clobbered, а не intention
  • добавлен правильный пример использования
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

открытая скобка должна быть на той же линии, что и ForEach-Object или вы должны использовать символ продолжения строки (Апостроф).

это так, потому что код внутри { ... } действительно значение для .

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

Это поможет вам преодолеть текущие проблемы.


Я просто хотел расширить или упростить ответ Джона Z. Кажется, что слишком много строк и упущенных возможностей использовать Where-Object. Вот моя упрощенная версия:

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

Я хотел указать, что не следует ссылаться на базовые свойства хэш-таблицы без разбора в общих функциях, поскольку они могут быть переопределены (или перегружены) элементами хэш-таблицы.

например, hashtable $hash=@{'keys'='lots of them'} будет иметь базовое свойство hashtable,Keys переопределено элементом keys, и таким образом делает foreach ($key in $hash.Keys) вместо этого перечислит хэшированный элемент keys ' s значение, вместо базового свойства Keys.

вместо метод getenumerator метод или keys свойства PSBase свойство, которое нельзя переопределить, должно использоваться в функциях, которые могут не знать, были ли переопределены базовые свойства.

таким образом, ответ Джона Z является лучшим.


вот версия функции, которая не использует конвейер (не то, что конвейер плохой, просто еще один способ сделать это). Он также возвращает объединенную хэш-таблицу и оставляет оригинал без изменений.

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

    return $a + $b
}

Я думаю, что самый компактный код будет такой:

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

я позаимствовал его у https://stackoverflow.com/a/33839784/880076


еще один ответ!

to 'inherit' key-values from parent hashtable ($htOld) для дочерних хэш-таблиц ($htNew), без изменения значений уже существующих ключей в хеш-таблицы ребенка,

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

обратите внимание, что это изменит объект $htNew.


Мне просто нужно было сделать это и найти это работает.

$HT += $HT2

содержимое $HT2 добавляется к содержимому $HT

/Андрей