Объединение хэш-таблиц в 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 ответов
Я вижу две проблемы:
- открытая скобка должна быть на той же строке, что и
Foreach-object
- вы не должны изменять коллекцию при перечислении через коллекцию
В приведенном ниже примере показано, как исправить обе проблемы:
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
/Андрей