Объединение хеш-таблиц в PowerShell: как? - PullRequest
20 голосов
/ 10 января 2012

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

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

Когда я набираю это в 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" в обеих хеш-таблицах.

Что я делаю не так?

Ответы [ 11 ]

23 голосов
/ 01 октября 2015

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
15 голосов
/ 10 января 2012

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

  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
}
7 голосов
/ 16 октября 2014

Не новый ответ, он функционально такой же, как @ 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 голосов
/ 10 января 2012

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

Это так, потому что код в {...}на самом деле значение параметра -Process командлета ForEach-Object.

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

Это поможет вам справиться с текущей проблемой.

1 голос
/ 10 марта 2019

В случае, если вы хотите объединить все дерево хеш-таблиц

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 голос
/ 27 августа 2018

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

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

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

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

1 голос
/ 09 апреля 2018

Чтобы «наследовать» значения ключей от родительской хеш-таблицы ($htOld) до дочерних хеш-таблиц ($htNew), без изменения значений уже существующих ключей в дочерних хеш-таблицах,

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

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

1 голос
/ 01 ноября 2013

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

Function merge_hashtables($htold, $htnew) {
    $htold.Keys | ? { $htnew.ContainsKey($_) } | % {
        $htold.Remove($_)
    }
    $htold += $htnew
    return $htold
}
0 голосов
/ 16 ноября 2016

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

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

Я позаимствовал его у Объединение и пересечение хеш-таблиц в PowerShell

0 голосов
/ 14 марта 2014

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

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

    return $a + $b
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...