Last active
May 23, 2024 11:39
-
-
Save PanosGreg/d5b469c6d0432f1c87d14e67ae211499 to your computer and use it in GitHub Desktop.
Different ways to sort objects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## Different ways to Sort | |
# ======================== | |
# a) via Array class and the .Sort() method | |
# b) via LINQ and the OrderBy() method | |
# c) via the Sort-Object function | |
# d) via Comparer class for | |
# d1) List & SortedSet | |
# d2) ArrayList | |
# And the .Sort() method of those classes | |
# e) via 3rd party library, HPCsharp | |
# Note: if you open this file in ISE, use Ctrl+M for region expansion | |
# if you use VSCode, use the appropriate shortcut for folding | |
#region --- Create Sample list | |
# First, let's create some objects that we would like to sort | |
$Hashes = @( | |
@{Name='aa';Size=10;Date=[datetime]::Parse('1 Jan 2020');Version=[version]'1.1.0'} | |
@{Name='bb';Size=20;Date=[datetime]::Parse('2 Jan 2020');Version=[version]'1.2.0'} | |
@{Name='cc';Size=30;Date=[datetime]::Parse('3 Jan 2020');Version=[version]'1.3.0'} | |
@{Name='dd';Size=40;Date=[datetime]::Parse('4 Jan 2020');Version=[version]'1.4.0'} | |
@{Name='ee';Size=50;Date=[datetime]::Parse('5 Jan 2020');Version=[version]'1.5.0'} | |
) | |
$Items = $Hashes | foreach {[pscustomobject]$_} | Get-Random -Count $Hashes.Length | |
Write-Output $Items | |
#endregion | |
# Now let's sort them | |
# =================== | |
# Note: on all cases, I will sort based on the Size property for this example | |
# but you can change that to any of the other properties of the array | |
#region --- a) Sort with Array | |
# via [Array]::Sort() | |
# this works like so: [array]::Sort(<property to sort by>,<object[]>) | |
# it sorts in place, and it sorts descending (last item is the largest) | |
$ArraySort = $Items.Clone() | |
[array]::Sort($ArraySort.Size,$ArraySort) | |
Write-Output $ArraySort | |
# Note: although this sorts in place, the results are written in a different memory address | |
# since the Array class is immuttable, so keep that in mind. | |
#endregion | |
#region --- b) Sort with LINQ | |
# via [LINQ]::OrderBy() | |
# this works like so: [linq]::OrderBy(<strongly typed array>,<function delegate[the type of an array item, the type of the property to sort by]>{property to sort by as the 1st argument}) | |
# it does not sort in place, instead it outputs the results in a new object and leaves the array as-is | |
# it sorts descending | |
$LinqSort = [System.Linq.Enumerable]::OrderBy([object[]]$Items,[Func[object,int]] {($args[0]).Size}) | |
Write-Output $LinqSort | |
# LINQ is the only option that does NOT sort in-place when using a native .net method | |
# in case you use a specific class for the objects in the array | |
# like for example later on where we define our own type of [MyExample.MyObject], then | |
# the LINQ command would be like so: | |
# $LinqSort = [System.Linq.Enumerable]::OrderBy([MyExample.MyObject[]]$Items,[Func[MyExample.MyObject,int]] {($args[0]).Size}) | |
#endregion | |
#region --- c) Sort with Sort-Object | |
# you just need to pass the array to the function | |
# either through the pipeline or via the InputObject parameter | |
# it does not sort in place, it can sort either descending or ascending | |
$FunctionSort = $Items | Sort-Object -Property Size | |
Write-Output $FunctionSort | |
# Note: This is the slowest sorting option of all | |
# but it's the most user-friendly and easy to use | |
# so as long as you have a relatively small number of items to sort, it might be preferable | |
#endregion | |
# via Comparer class | |
<# | |
you first need to create the class, in either C# or PowerShell | |
use one way, either PS (powershell) or CS (csharp) | |
NOTES | |
1) the comparer class must inherit from IComparer | |
2) the comparer class needs a Compare() method | |
This method is inherited by IComparer | |
3) this Compare() method must return an int, which needs to be 0,1 or -1 | |
4) the Compare() method always takes 2 parameters. | |
Which will compare with each other | |
#> | |
#region --- d1ps) Sort with List & SortedSet using PowerShell class | |
# Comparer class in PowerShell for Generic types (List,SortedSet) | |
class MyExampleComparer : System.Collections.Generic.IComparer[Management.Automation.PSObject] { | |
# Properties | |
[string]$PropertyName | |
[bool]$Descending = $false | |
# Constructors | |
MyExampleComparer([string]$Property) { | |
$this.PropertyName = $Property | |
} | |
MyExampleComparer([string]$Property, [bool]$Descending) { | |
$this.PropertyName = $Property | |
$this.Descending = $Descending | |
} | |
# Compare method | |
[int] Compare ([Management.Automation.PSObject]$a, | |
[Management.Automation.PSObject]$b) { | |
$ValueA = $a.$($this.PropertyName) | |
$ValueB = $b.$($this.PropertyName) | |
if ($ValueA -eq $ValueB) {$result = 0} | |
elseif ($ValueA -lt $ValueB) {$result = -1} | |
else {$result = 1} | |
if($this.Descending) {$result *= -1} | |
return $result | |
} | |
} #PS class | |
# NOTE: the above is not technically correct 100% because it implies | |
# that the property used to compare the items with each other | |
# can be used with a comparison operator like -eq or -lt | |
# whereas in CSharp this must be explicit to avoid exceptions | |
# the PowerShell class allows you to select the property to sort by | |
# make sure though that it is a "simple" object type, like string/char,number,date,version | |
# and not a nested property or a complex object that cannot be directly compared with. | |
# and can be used like so: | |
$SizeComparer = [MyExampleComparer]::new('Size') | |
# with List | |
# this sorts in place | |
# initialize a list | |
$ListSort = [System.Collections.Generic.List[psobject]]::new() | |
$ListSort.AddRange($Items.ForEach({$_})) | |
# and now sort | |
$ListSort.Sort($SizeComparer) | |
Write-Output $ListSort | |
# with SortedSet | |
# this sorts immediately when you add the item to the set | |
$SetSort = [System.Collections.Generic.SortedSet[psobject]]::new($SizeComparer) | |
$Items | ForEach {[void]$SetSort.Add($_)} | |
Write-Output $SetSort | |
# Note: SortedSet does not have the .AddRange() method | |
# HashSet does not have a .Sort() method | |
# the main difference between a SortedSet and a HashSet, is that the HashSet | |
# does not support ordering of the items, whereas SortedSet maintains a sorted order of the items. | |
#endregion | |
#region --- d2ps) Sort with ArrayList using PowerShell class | |
# Comparer class in PowerShell for Non-Generic types (ArrayList) | |
class MyExampleComparerNonGen : System.Collections.IComparer { | |
# Properties | |
[string]$PropertyName | |
[bool]$Descending = $false | |
# Constructors | |
MyExampleComparerNonGen([string]$Property) { | |
$this.PropertyName = $Property | |
} | |
MyExampleComparerNonGen([string]$Property, [bool]$Descending) { | |
$this.PropertyName = $Property | |
$this.Descending = $Descending | |
} | |
# Compare method | |
[int] Compare ($a,$b) { | |
$ValueA = $a.$($this.PropertyName) | |
$ValueB = $b.$($this.PropertyName) | |
if ($ValueA -eq $ValueB) {$result = 0} | |
elseif ($ValueA -lt $ValueB) {$result = -1} | |
else {$result = 1} | |
if($this.Descending) {$result *= -1} | |
return $result | |
} | |
} #PS class | |
$SizeComparer = [MyExampleComparerNonGen]::new('Size') | |
# with ArrayList | |
$ArrayListSort = [System.Collections.ArrayList]::new($Items) | |
$ArrayListSort.Sort($SizeComparer) | |
# this sorts in place | |
Write-Output $ArrayListSort | |
# NOTE: Microsoft has recommended that ArrayList should not be used anymore | |
# and that Generic data types should be preferred instead. | |
#endregion | |
#region --- d1cs) Sort with List & SortedSet using CSharp class | |
# Comparer class in C# | |
# here we also need to define a class for the objects that we'll use | |
$Class = @' | |
using System; | |
using System.Collections.Generic; // <-- for IComparer | |
namespace MyExample { | |
public class MyObject { | |
public string Name; | |
public int Size; | |
public DateTime Date; | |
public Version Version; | |
public MyObject() {} | |
} | |
public class SizeComparer : IComparer<MyObject> { | |
// properties | |
public bool Descending = false; | |
// constructors | |
public SizeComparer() {} | |
public SizeComparer(bool descending) {this.Descending = descending;} | |
// method | |
public int Compare(MyObject a, MyObject b) { | |
int Result; | |
if (a.Size == b.Size) {Result = 0;} | |
else if (a.Size < b.Size) {Result = -1;} | |
else {Result = 1;} | |
if (this.Descending) {Result *= -1;} | |
return Result; | |
} | |
} //SizeComparer | |
} //namespace | |
'@ | |
# the C# class allows sorting only on a specific property (in this example I sort based on Size) | |
# if you want to change that, then change the above code or create another C# class | |
# and can be used like so: | |
Add-Type -TypeDefinition $Class | |
$SizeComparer = [MyExample.SizeComparer]::new() | |
# with List | |
# this sorts in place | |
$ListSort = [System.Collections.Generic.List[MyExample.MyObject]]::new() | |
$ListSort.AddRange([MyExample.MyObject[]]$Items) | |
# and now sort | |
$ListSort.Sort($SizeComparer) | |
Write-Output $ListSort | |
# with SortedSet | |
# this sorts immediately when you add the item to the set | |
$SetSort = [System.Collections.Generic.SortedSet[MyExample.MyObject]]::new($SizeComparer) | |
$Items | ForEach {[void]$SetSort.Add([MyExample.MyObject]$_)} | |
Write-Output $SetSort | |
# Note: SortedSet does not have the .AddRange() method | |
#endregion | |
#region --- e) Sort with the HPCsharp library | |
# you'll need to download the library from nuget.org | |
# you can do it manually from a browser, or you can do it in the CLI | |
# in the CLI, you can use the Install-Package function, but that is extremely slow | |
# preferably you can use the nuget.exe tool | |
# if you don't have nuget.exe, you'll need to download it | |
$url = 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' | |
$file = 'C:\temp\nuget.exe' | |
[System.Net.WebClient]::new().DownloadFile($url,$file) | |
# Note: NuGet.exe 5.0 and later requires .NET Framework 4.7.2 or later to execute. | |
# Some links for this library | |
# Library: https://www.nuget.org/packages/HPCsharp | |
# GitHub: https://github.com/DragonSpit/HPCsharp | |
# Article: https://duvanenko.tech.blog/2018/05/23/faster-sorting-in-c/ | |
# https://duvanenko.tech.blog/2020/08/11/even-faster-sorting-in-c/ | |
# Example: https://github.com/DragonSpit/HPCsharp/blob/master/HPCsharpExamples/HPCsharpExamples/SortingUsageExamples.cs | |
# load the library | |
Add-Type -Path .\HPCsharp.dll | |
# Note: for this example I was on PS7 and used the .dll from .NET 6.0, which can be found here: | |
# .\HPCsharp.3.16.6\lib\net6.0\HPCsharp.dll | |
# But I've also tried it in PS5.1 with the .dll from the .\netstandard2.0 folder as well. | |
# optionally if you want to discover it's classes | |
$All = Add-Type -Path .\HPCsharp.dll -PassThru | |
$HPC = $All | where IsPublic | sort -Unique Name | |
# make sure you have the previous custom C# classes loaded | |
# if not, then load them, which will provide the class for the comparer and our example class | |
Add-Type -TypeDefinition $Class | |
# now let's sort using the library | |
# sort in-place, just like Array.Sort() or List[T].Sort() | |
$HpcSort1 = [MyExample.MyObject[]]$Items.Clone() | |
[HPCsharp.Algorithm]::SortMergeInPlace($HpcSort1,$SizeComparer,16384) | |
Write-Output $HpcSort1 | |
# sort not in-place, just like Linq::OrderBy() | |
$List = [System.Collections.Generic.List[MyExample.MyObject]]::new([MyExample.MyObject[]]$Items) | |
$HpcSort2 = [HPCsharp.Algorithm]::SortMerge($List,$true,$SizeComparer) | |
Write-Output $HpcSort2 | |
# Note: The library also supports parallel processing for the Sort() method, as well as other algorithms | |
# but these are implemented as extension methods to the native [Array] type, and thus are not easily | |
# accessible in PowerShell (if at all). For example there are the following extension methods: | |
# .SortMergeInPlaceAdaptivePar() for in-place parallel sorting | |
# .SortMergePar() for not in-place parallel sorting | |
#endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment