Подтвердить что ты не робот

Объединение хеш-таблиц в 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" в обеих хеш-таблицах.

Что я делаю неправильно?

4b9b3361

Ответ 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
}

Ответ 11

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

$HT += $HT2

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