Выберите значения одного свойства для всех объектов массива в PowerShell

Допустим, у нас есть массив объектов $ objects. Допустим, эти объекты имеют свойство "Имя".

Это то, что я хочу сделать

 $results = @()
 $objects | %{ $results += $_.Name }

Это работает, но можно ли сделать это лучше?

Если я сделаю что-то вроде:

 $results = objects | select Name

$results является массивом объектов, имеющих свойство Name. Я хочу, чтобы $results содержал массив Имен.

Есть ли способ лучше?

5 ответов

Решение

Я думаю, что вы могли бы использовать ExpandProperty параметр Select-Object,

Например, чтобы получить список текущего каталога и просто отобразить свойство Name, нужно сделать следующее:

ls | select -Property Name

Это все еще возвращает объекты DirectoryInfo или FileInfo. Вы всегда можете проверить тип, проходящий через конвейер, отправив по конвейеру Get-Member (псевдоним gm).

ls | select -Property Name | gm

Таким образом, чтобы расширить объект до типа свойства, на которое вы смотрите, вы можете сделать следующее:

ls | select -ExpandProperty Name

В вашем случае вы можете просто сделать следующее, чтобы переменная была массивом строк, где строки являются свойством Name:

$objects = ls | select -ExpandProperty Name

В качестве еще более простого решения вы можете просто использовать:

$results = $objects.Name

Который должен заполнить $results с массивом всех значений свойств 'Name' элементов в $objects,

В дополнение к уже существующим, полезные ответы с указанием того, когда использовать какой подход и сравнение производительности.

  • Вне трубопровода используйте:

     $ объекты . название 
    (PSv3+), как показано в ответе rageandqq, который синтаксически проще и намного быстрее.

    • Доступ к свойству на уровне коллекции для получения значений его членов в виде массива называется перечислением членов и является функцией PSv3+;
    • В качестве альтернативы, в PSv2 используйте foreach оператор, чей вывод вы также можете назначить непосредственно переменной:
       $ results = foreach ($ obj in $ objects) {$ obj.Name} 
    • Компромиссы:
      • Как входная коллекция, так и выходной массив должны помещаться в память в целом.
      • Если входная коллекция сама является результатом команды (конвейера) (например, (Get-ChildItem).Name), эта команда должна сначала выполняться до завершения, прежде чем будут доступны элементы результирующего массива.
  • В конвейере, где результат должен обрабатываться дальше или результаты не помещаются в память в целом, используйте:

     $ объекты | Select-Object -ExpandProperty Name 

    • Нужда в -ExpandProperty объясняется в ответе Скотта Саада.
    • Вы получаете обычные преимущества конвейерной обработки поочередной обработки, которая обычно производит вывод сразу и поддерживает постоянное использование памяти (если вы все равно не соберете результаты в памяти).
    • Обмен:
      • Использование трубопровода является относительно медленным.

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


Вот простая в вводе альтернатива, которая, однако, является самым медленным подходом; он использует упрощенный ForEach-Object Синтаксис вызвал оператор операции (опять же, PSv3+):; Например, следующее решение PSv3+ легко добавить к существующей команде:

$objects | % Name      # short for: $objects | ForEach-Object -Process { $_.Name }

Ради полноты: малоизвестный PSv4+ .ForEach() Метод сбора является еще одной альтернативой:

# By property name (string):
$objects.ForEach('Name')

# By script block (much slower):
$objects.ForEach({ $_.Name })
  • Этот подход аналогичен перечислению членов с теми же компромиссами, за исключением того, что конвейерная логика не применяется; это немного медленнее, хотя все же заметно быстрее, чем конвейер.

  • Для извлечения единственного значения свойства по имени (строковый аргумент) это решение наравне с перечислением членов (хотя последнее синтаксически проще).

  • Вариант блока сценариев, хотя и намного медленнее, допускает произвольные преобразования; это более быстрая - все в памяти за один раз - альтернатива на основе конвейера ForEach-Object командлет


Сравнивая эффективность различных подходов

Вот примеры времени для различных подходов, основанные на входной коллекции 100,000 объекты, усредненные по 100 пробегам; абсолютные числа не важны и варьируются в зависимости от многих факторов, но они должны дать вам представление об относительной эффективности:

Command                                         FriendlySecs (100-run avg.) Factor
-------                                         --------------------------- ------
$objects.ForEach('Number')                      0.078                       1.00
$objects.Number                                 0.079                       1.02
foreach($o in $objects) { $o.Number }           0.188                       2.42
$objects | Select-Object -ExpandProperty Number 0.881                       11.36
$objects.ForEach({ $_.Number })                 0.925                       11.93
$objects | % { $_.Number }                      1.564                       20.16
$objects | % Number                             2.974                       38.35
  • Решение с использованием метода сбора данных, основанного на перечислении членов / свойствах, работает в 10 раз быстрее, чем самое быстрое решение на основе конвейера.

  • foreach Решение оператора примерно в 2,5 раза медленнее, но все же примерно в 4-5 раз быстрее, чем самое быстрое конвейерное решение.

  • Использование блока скрипта с решением метода сбора (.ForEach({ ... }) существенно замедляет процесс, так что он практически на одном уровне с самым быстрым конвейерным решением (Select-Object -ExpandProperty).

  • % Number (ForEach-Object Number), что любопытно, работает хуже, хотя % Number является концептуальным эквивалентом % { $_.Number }).


Исходный код для тестов:

Примечание: функция загрузки Time-Command из этого Gist, чтобы запустить эти тесты.

$count = 1e5 # input-object count (100,000)
$runs  = 100  # number of runs to average 

# Create sample input objects.
$objects = 1..$count | % { [pscustomobject] @{ Number = $_ } }

# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Number },
              { $objects | % Number },
              { $objects | % { $_.Number } },
              { $objects.ForEach('Number') },
              { $objects.ForEach({ $_.Number }) },
              { $objects.Number },
              { foreach($o in $objects) { $o.Number } }

# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Command, FriendlySecs*, Factor

Внимание! Перечисление членов работает только в том случае, если в самой коллекции нет члена с таким же именем. Итак, если у вас есть массив объектов FileInfo, вы не можете получить массив длин файлов, используя

 $files.length # evaluates to array length

И прежде чем вы скажете "хорошо, очевидно", подумайте об этом. Если у вас был массив объектов со свойством емкости, тогда

 $objarr.capacity

будет работать нормально, ЕСЛИ $objarr на самом деле не [Array], а, например, [ArrayList]. Поэтому перед использованием перечисления членов вам, возможно, придется заглянуть в черный ящик, содержащий вашу коллекцию.

(Примечание для модераторов: это должен быть комментарий к ответу rageandqq, но у меня еще недостаточно репутации.)

Каждый день узнаю что-то новое! Спасибо тебе за это. Я пытался добиться того же. Я делал это прямо: Что в основном сделало мою переменную снова объектом! Позже я понял, что мне нужно сначала определить его как пустой массив, $ListOfGGUIDs = @()

Другие вопросы по тегам