Skip to content

Instantly share code, notes, and snippets.

@Copy-link
Forked from PanosGreg/Sorting.ps1
Created March 30, 2024 16:44
Show Gist options
  • Save Copy-link/e5721e39a9c0af6e0e259c3e635923b0 to your computer and use it in GitHub Desktop.
Save Copy-link/e5721e39a9c0af6e0e259c3e635923b0 to your computer and use it in GitHub Desktop.
Different ways to sort objects
## 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