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 }