Not Sorting -- iComparer[T] and Type [System.IO.FileInfo]

110 views Asked by At

I've been putting together a custom folder class and came across this post. In Powershell, how do I sort a Collections.Generic.List of DirectoryInfo?

The folder class is meant to have a sort function that sorts by Length a List of [FileInfo] Objects.

The both classes compile without errors.

But the List of FileInfo Objects does not seem to sort from largest to smallest. Does anybody have an idea why my sort function does not seem to sort?

Custom Comparer Class

class Comparer : System.Collections.Generic.IComparer[System.IO.FileInfo] {
    [int]Compare([System.IO.FileInfo]$a, [System.IO.FileInfo]$b)
    {
        if($a.Length -eq $b.Length)
        {
            $res = 0
        }
        elseif($a.Length -lt $b.Length)
        {
            $res = -1
        }
        else
        {
            $res = 1
        }
        return $res 
    }
}

Custom Folder Class

class Folder {
    [System.IO.DirectoryInfo]$Root
    [System.Collections.Generic.List[System.IO.FileInfo]]$Files
    [int]$Size
    Folder([System.String]$Path){
        $this.Root = [System.IO.DirectoryInfo]::new($Path)
    }
    [void]GetFiles([String[]]$Query){    
        $this.Files = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
        ForEach($Q in $Query){
            try {
                $local:Archivos = $this.Root.EnumerateFiles($Q, "AllDirectories")        
                foreach($Archivo in $Archivos){
                    $File= [System.IO.FileInfo]::new($Archivo.FullName)
                    $this.Files.Add($File)
                }
            }
            catch [System.Management.Automation.MethodInvocationException]{
                if($_.FullyQualifiedErrorID -eq "FileNotFoundException"){
                    [System.Console]::WriteLine($_.Exception.Message)
                }
            }

        }
    }
    [void]ListFiles(){
        [System.Console]::WriteLine("[Root] $($this.Root.FullName)")
        foreach($File in $this.Files){
            [System.Console]::WriteLine("`t[$($this.FormatSize($File.Length))] $($File.Name)")
        }
    }
    [void]SortFiles() {
        $local:Comparer = [Comparer]::new()
        $this.Files.Sort($local:Comparer)
    }
    [void]DeleteFiles(){
        foreach($File in $this.Files){
            $File.Delete()
        }
    }
    [void]GetSize(){
        [System.Int64]$this.Size = 0
        foreach($File in $this.Files){
            $this.Size += $File.Length
        }
    }
    [System.String]FormatSize([System.Int64]$Size){
        $local:Formatato = ""
        switch ($Size) {
            {$Size -lt 1099511627776}{
                $Formatato = "$([System.Math]::Round(($Size/1GB), 2))GB"
            }
            {$Size -lt 1073741824}{
                $Formatato = "$([System.Math]::Round(($Size/1MB), 2))MB"    
            }
            {$Size -lt 1048576}{
                $Formatato = "$([System.Math]::Round(($Size/1KB), 2))KB"
            }
            Default {
                $Formatato = "$([System.Math]::Round(($Size), 2))B"
            }
        }
        return $local:Formatato
    }
}

Test of Classes in action

    $Downloads = [Folder]::new($Path)
    $Downloads.GetFiles("*")
    $Downloads.SortFiles()
    $Downloads.ListFiles()
    [Root] C:\Users\charles.murray\Downloads
            [0.6KB] index.html.example
            [0.91KB] prometheus.yml
            [1.3KB] prometheus.html
            [1.42KB] node.html
            [2.61KB] node-cpu.html
            [2.82KB] menu.lib
            [3.44KB] node-disk.html
            [3.68KB] NOTICE
            [4.01KB] prometheus-overview.html
            [6.01KB] prom.lib
            [19.53KB] queries.active
            [73.48KB] rufus.log
            [896KB] 00000000
            [8.72MB] MS108EUP_V1.0.1.9-runtime.image
            [108.23MB] promtool.exe
            [114.81MB] prometheus.exe
            [128MB] 000001
    
    $Downloads.Files
    
    Mode                 LastWriteTime         Length Name
    ----                 -------------         ------ ----
    -a----         7/17/2023   2:01 PM            616 index.html.example
    -a----         7/17/2023   2:00 PM            934 prometheus.yml
    -a----          8/4/2023  11:31 PM           1114 MS108EUP_V1.0.1.9_Release_Notes.html
    -a----         7/17/2023   2:01 PM           1334 prometheus.html
    -a----         7/17/2023   2:01 PM           1453 node.html
    -a----         7/17/2023   2:01 PM           2675 node-cpu.html
    -a----         7/17/2023   2:01 PM           2888 menu.lib
    -a----         7/17/2023   2:01 PM           3522 node-disk.html
    -a----         7/17/2023   2:00 PM           3773 NOTICE
    -a----         7/17/2023   2:01 PM           4103 prometheus-overview.html
    -a----         7/17/2023   2:01 PM           5783 node-overview.html
    -a----         7/17/2023   2:01 PM           6152 prom.lib
    -a----         7/17/2023   2:00 PM          11357 LICENSE
    -a----         7/17/2023   2:01 PM          20001 queries.active
    -a----         7/26/2023  11:57 AM          75243 rufus.log
    -a----         7/17/2023   3:11 PM         917504 00000000
    -a----          8/4/2023  11:31 PM        9143718 MS108EUP_V1.0.1.9-runtime.image
    -a----         7/17/2023   2:01 PM      113488896 promtool.exe
    -a----         7/17/2023   2:00 PM      120385536 prometheus.exe
    -a----         7/17/2023   3:11 PM      134217728 000001
2

There are 2 answers

0
Santiago Squarzon On

Change your Compare method, it can be greatly simplified:

class Comparer : System.Collections.Generic.IComparer[System.IO.FileInfo] {
    [int] Compare([System.IO.FileInfo] $a, [System.IO.FileInfo] $b) {
        return $b.Length.CompareTo($a.Length)
    }
}

Then descending sorting works properly:

$folder = [Folder]::new($pwd)
$folder.GetFiles('*')
$folder.SortFiles()
$folder.Files

For ascending order you would be using:

class Comparer : System.Collections.Generic.IComparer[System.IO.FileInfo] {
    [int] Compare([System.IO.FileInfo] $a, [System.IO.FileInfo] $b) {
        return $a.Length.CompareTo($b.Length)
    }
}

As aside, I would recommend you to add a constructor that takes a comparer and add the comparer as a property of your class, then you could add an ascending and descending method without having to instantiate a new comparer:

class Folder {
    [System.IO.DirectoryInfo] $Root
    [System.Collections.Generic.List[System.IO.FileInfo]] $Files
    [int] $Size
    [Comparer] $Comparer = [Comparer]::new()

    Folder([string] $Path) {
        $this.Root = [System.IO.DirectoryInfo]::new($Path)
    }

    Folder([string] $Path, [Comparer] $comparer) {
        $this.Root = [System.IO.DirectoryInfo]::new($Path)
        $this.Comparer = $comparer
    }

    [void] SortFilesAscending() {
        $this.Files.Sort($this.Comparer)
    }

    [void] SortFilesDescending() {
        $this.SortFilesAscending()
        $this.Files.Reverse()
    }

    # same code here below...
}
0
mklement0 On

Does anybody have an idea why my sort function does not seem to sort?

It does sort, in ascending order, i.e from smallest to largest.

does not seem to sort from largest to smallest.

Indeed it doesn't, for the reasons stated. You want sorting in descending order.

Thus you need to invert the logic of your Compare() method implementation:

The immediate fix is to swap the $res = -1 and $res = 1 statements in your .Compare() method implementation.

The better fix is to delegate the comparison to the .CompareTo() method of the .Length property values of the [System.IO.FileInfo] instances, in reverse order, as shown in Santiago's helpful answer.