XcOder
XcOder

Reputation: 157

Sort Fileinfo list in asc order

I have a list of files as follows

10_2017
123_2018
500_2017
20_2019
100_2017
25_2017
32_2018

Which i want to be sorted like

10_2017
25_2017
100_2017
500_2017
32_2018
123_2018
20_2019

I can sort the array by Array.sort(f1,new FileInfoSort) if I read the years separate but I need it in one sorted array or list

I have tried several threads on here including this method. used f1.Sort(Function(x, y) x.Name.CompareTo(y.Name)) and Array.Sort(f1.toArray, New FileInfoSort) without any success I have even tried to separate the files into folders by year and read them separately into lists and combining them which didnt seem to work either

Using the code

Dim d1 As New DirectoryInfo(AppFolder)
Dim f1 As List(Of FileInfo) = d1.GetFiles("*.LBK", SearchOption.TopDirectoryOnly).ToList
'f1.Sort(Function(x, y) x.Name.CompareTo(y.Name))
f1.SortNatural(Function(x) x.Name)

Separating the files into folders by year and then reading it and combining

        Dim d1 As New DirectoryInfo(AppFolder.User.data)
        Dim d2 As List(Of DirectoryInfo) = New List(Of DirectoryInfo)
        'data folders have names like "d_2019"
        For Each d As DirectoryInfo In d1.GetDirectories
            If d.Name.ToString.Substring(0, 1) = "d" Then d2.Add(d)
        Next
        Dim f1 As List(Of FileInfo) = New List(Of FileInfo)
        For Each Dir As DirectoryInfo In d2
            f1.AddRange(Dir.GetFiles("*.LBK", SearchOption.TopDirectoryOnly))
            'trying to sort
            'f1.Sort(Function(x, y) x.Name.CompareTo(y.Name))
            f1.SortNatural(Function(x) x.Name)
        Next
Module ListExt
    <DllImport("shlwapi.dll", CharSet:=CharSet.Unicode)>
    Private Function StrCmpLogicalW(ByVal lhs As String, ByVal rhs As String) As Integer

    End Function

    <Extension()>
    Sub SortNatural(Of T)(ByVal self As List(Of T), ByVal stringSelector As Func(Of T, String))
        self.Sort(Function(lhs, rhs) StrCmpLogicalW(stringSelector(lhs), stringSelector(rhs)))
    End Sub

    <Extension()>
    Sub SortNatural(ByVal self As List(Of String))
        self.Sort(AddressOf StrCmpLogicalW)
    End Sub
End Module

Any method Ive used so far outputs

10_2017
20_2019
25_2017
32_2018
100_2017
123_2018
500_2017

I have no idea how to approach this even. If such a list/array is not possible ideas of how i might structure my files so that i can read in the files in the order i prefer above would be welcome too!

Upvotes: 0

Views: 73

Answers (1)

Joel Coehoorn
Joel Coehoorn

Reputation: 416121

You'll have to write code to parse the separate sections of the name and treat them as numbers. Any built-in comparer you use will treat strings as strings, where anything that starts with a 1 comes before anything that starts with a 2, even when the values are '100' and '2'. Even the so-called "natural" sorts are only good enough to check the first section.

You also need to strip the LBK extension from the name.

Dim splitChars() As Char = {"_"c}
Dim d1 As New DirectoryInfo(AppFolder)
Dim f1 As List(Of FileInfo) = 
   d1.EnumerateFiles("*.LBK", SearchOption.TopDirectoryOnly).
   OrderBy(Function(fi) 
              Dim parts = fi.Name.Replace(".LBK", "").Split(splitChars)
              Return (Integer.Parse(parts(1)) * 1000) + Integer.Parse(parts(0))
           End Function).
   ToList()

For fun, here's a Regex version:

Dim exp As New Regex("(\d{1,3})_(\d{4})");

Dim d1 As New DirectoryInfo(AppFolder)
Dim f1 As List(Of FileInfo) = 
   d1.EnumerateFiles("*.LBK", SearchOption.TopDirectoryOnly).
   OrderBy(Function(fi) 
              Dim parts = exp.Matches(fi.Name)(0).Groups
              Return (Integer.Parse(parts(2).Value) * 1000) + Integer.Parse(parts(1).Value)
           End Function).
   ToList()

See it work here:

https://dotnetfiddle.net/UAae8P


I also wanted to comment on this line from one of the samples in the question:

If d.Name.ToString.Substring(0, 1) = "d" Then d2.Add(d)

It stood out as needing some attention. There's a ton of extra work going on here that isn't needed:

  • d.Name is already a string, no need to call ToString()
  • Since you just want one character you can access it via subscript, no need to call Substring()
  • You're comparing one character, no need to do string compares.

You really want this:

If d.Name(0) = "d"c Then d2.Add(d)

That's a lot less code, and it will perform so much better, and it will be worth your time to study this and understand why.

Upvotes: 1

Related Questions