PowerShell: сортировка в коллекциях значений хеш-таблицы

У меня есть тестовый входной CSV-файл, а именно:

ID;Product;Price;Discount;Level
1;Alpha;23.00;0.03;A
2;Bravo;17.00;0.01;A
3;Charlie;11.00;0.05;A
4;Delta;17.00;0.05;A
5;Echo;29.00;0.07;A
6;Foxtrot;11.00;0.01;A
7;Golf;11.00;0.01;A
1;Hotel;53.00;0.11;B
2;India;53.00;0.13;B
3;Juliet;61.00;0.11;B
1;Kilo;79.00;0.23;C
2;Lima;89.00;0.23;C
3;Mike;97.00;0.29;C
4;November;83.00;0.17;C
5;Oscar;79.00;0.11;C

и я хотел бы создать следующий выходной файл:

ID;Product;Price;Discount;Level
1;Alpha;23.00;0.03;A
5;Echo;29.00;0.07;A
2;India;53.00;0.13;B
3;Juliet;61.00;0.11;B
2;Lima;89.00;0.23;C
3;Mike;97.00;0.29;C

То есть для каждого уровня я хочу выбрать две верхние строки, отсортированные по цене, а затем скидку. Например, для уровняB, Я хочу Juliet а также India не Juliet а также Hotel.

У меня есть следующий фрагмент кода, который не совсем подходит!

$input = '.\TestInput.csv'
$products = @(Import-CSV -Path $input -Delimiter ";")
$levels = $products | 
    Group-Object -Property Level -AsHashTable 

$sales = $levels.GetEnumerator() | 
    Sort-Object -Property @{ Expression = { [int]($_.Price) } ; Descending = $true },
                          @{ Expression = { [int]($_.Discount) } ; Descending = $true } | 
    Select-Object -first 2

$output = '.\TestOutput.csv'
$sales | Export-Csv -Path $output -Delimiter ";" -NoTypeInformation

Что мне не хватает?

2 ответа

Решение

Использовать Group-Object прямо на Import-Csv выход:

Import-Csv '.\TestInput.csv' -Delimiter ';' | 
  Group-Object Level | 
    ForEach-Object { 
      $_.Group |
        Sort-Object { [int] $_.Price }, { [int] $_.Discount } |
          Select-Object -Last 2
    } | 
        Export-Csv -Path '.\TestOutput.csv' -Delimiter ";" -NoTypeInformation

Примечание. В PowerShell [Core] v6+ вы можете заменить | Select-Object Last 2 с -Bottom 2, при условии Sort-Object теперь поддерживает -Top а также -Bottom параметры.


Что касается того, что вы пробовали:

  • Пока Group-Object обычно сортирует результирующие группы по заданным критериям группировки (Level, в данном случае), эта сортировка больше не гарантируется, если вы используете -AsHashtable, учитывая, что записи хеш-таблицы по своей природе неупорядочены.

    • Чтобы предотвратить это, либо используйте вывод по умолчанию (нет -AsHashtable) - что дает отдельные групповые объекты - как показано выше, или добавить последний Sort-Object назовите это сортировкой по Level.
  • $levels.GetEnumerator()отправляет пары ключ-значение (System.Collections.DictionaryEntry экземпляров) через конвейер, чьи .Key свойство - критерий группировки (.Level) и чьи .Value свойство - это связанная группа.

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

  • Поскольку ваша сортировка выполняется в порядке убывания,Select-Object -First 2покажет два самых высоких значения, но в порядке убывания, тогда как желаемый результат запрашивает их в порядке возрастания.

    • Чтобы получить последнее, отсортируйте по возрастанию, затем выберите последние 2 объекта.

Чтобы собрать все вместе (но обратите внимание, что решение вверху концептуально проще и эффективнее):

$sales = 
  $levels.Values | ForEach-Object { 
      $_ |  # process the group at hand
        Sort-Object -Property { [int] $_.Price }, { [int] $_.Discount } | 
          Select-Object -Last 2
    } | Sort-Object Level
$sales = ForEach ($Level in $levels.Keys | Sort-Object) { $levels.$Level | Sort-Object -Property  price,discount | Select-Object -last 2 }
Другие вопросы по тегам