Объединение хеш-таблиц в PowerShell: как?
Я пытаюсь объединить две хеш-таблицы, перезаписывая пары ключ-значение в первом, если тот же ключ существует во втором.
Для этого я написал эту функцию, которая сначала удаляет все пары ключ-значение в первой хеш-таблице, если такой же ключ существует во второй хеш-таблице.
Когда я набираю это в PowerShell построчно, это работает. Но когда я запускаю всю функцию, PowerShell просит меня предоставить (что она считает) недостающие параметры для объекта foreach.
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 - это хеш-таблицы, каждая из которых содержит две пары ключ-значение, по одной с ключом "name" в обеих хеш-таблицах.
Что я делаю неправильно?
Ответы
Ответ 1
Я вижу две проблемы:
- Открытая скобка должна быть на той же строке, что и
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
}
Ответ 2
Merge-HashTables
Вместо удаления ключей вы можете просто перезаписать их:
$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 version 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
Ответ 3
Не новый ответ, это функционально так же, как @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
Ответ 4
Открытая фигурная скобка должна быть в той же строке, что и ForEach-Object
, или вам нужно использовать символ продолжения строки (backtick).
Это так, потому что код внутри {...} действительно является значением параметра -Process
командлета ForEach-Object
.
-Process <ScriptBlock[]>
Specifies the script block that is applied to each incoming object.
Это позволит вам пройти мимо текущей проблемы.
Ответ 5
Я просто хотел расширить или упростить ответ от Jon Z. Кажется, что слишком много строк и упущенных возможностей использовать Where-Object. Вот моя упрощенная версия:
Function merge_hashtables($htold, $htnew) {
$htold.Keys | ? { $htnew.ContainsKey($_) } | % {
$htold.Remove($_)
}
$htold += $htnew
return $htold
}
Ответ 6
"Наследовать" значения ключей от родительской хеш- $htOld
($htOld
) до дочерних хеш- $htNew
($htNew
) без изменения значений уже существующих ключей в дочерних хеш-таблицах,
function MergeHashtable($htOld, $htNew)
{
$htOld.Keys | %{
if (!$htNew.ContainsKey($_)) {
$htNew[$_] = $htOld[$_];
}
}
return $htNew;
}
Обратите внимание, что это $htNew
объект $htNew
.
Ответ 7
Я хотел бы отметить, что не следует ссылаться на базовые свойства хеш-таблицы без разбора в универсальных функциях, поскольку они могут быть переопределены (или перегружены) элементами хеш-таблицы.
Например, хеш-таблица [email protected]{'keys'='lots of them'}
будет иметь базовое свойство hashtable, Keys
переопределены keys
элементов, и, следовательно, вместо этого будет использоваться foreach ($key in $hash.Keys)
перечислите значение keys
хешированного элемента вместо базового свойства Keys
.
Вместо этого метод GetEnumerator или свойство keys
свойства PSBase
, которое нельзя переопределить, следует использовать в функциях, которые могут не знать, были ли переопределены базовые свойства.
Таким образом, ответ Jon Z является лучшим.
Ответ 8
Если вы хотите объединить все дерево хеш-таблиц
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"
}
}
Ответ 9
Вот версия функции, которая не использует конвейер (не то, что конвейер плохой, просто другой способ сделать это). Он также возвращает объединенную хеш-таблицу и оставляет исходный неизменным.
function MergeHashtable($a, $b)
{
foreach ($k in $b.keys)
{
if ($a.containskey($k))
{
$a.remove($k)
}
}
return $a + $b
}
Ответ 10
Я думаю, что самый компактный код будет следующим:
function Merge-Hashtables($htold, $htnew)
{
$htnew.keys | where {$_ -notin $htold.keys} | foreach {$htold[$_] = $htnew[$_]}
}
Я позаимствовал это у Объединения и Пересечения Hashtables в PowerShell
Ответ 11
Мне просто нужно было сделать это и нашел это работает:
$HT += $HT2
Содержимое $HT2
добавляется к содержимому $HT
.