【问题标题】:How to order list of files by file name with number?如何按文件名和数字排序文件列表?
【发布时间】:2015-11-18 16:22:15
【问题描述】:

我在一个目录中有一堆文件,我试图根据它们的类型来获取它们。一旦我有了它们,我想按文件名排序(其中有一个数字,我想这样排序)

我返回的文件是:

file-1.txt
file-2.txt
...
file-10.txt
file-11.txt
...
file-20.txt

但是我得到它们的顺序看起来更接近于这个:

file-1.txt
file-10.txt
file-11.txt
...
file-2.txt
file-20.txt

现在我正在使用Directory.GetFiles() 并尝试使用linq OrderBy 属性。但是,我在订购文件列表(如上面的第一个列表)时需要做的事情非常糟糕。

Directory.GetFiles() 似乎正在返回字符串列表,因此我无法获取文件属性列表,例如 filenamename

这是我目前的代码:

documentPages = Directory.GetFiles(documentPath, "*.txt").OrderBy(Function(p) p).ToList()

有人有什么想法吗?

【问题讨论】:

  • 我猜你的比较函数必须测试两个文件名是否在第一个 (?) 点之前有一个数字部分,如果有,比较数字之前的前缀:如果它们相同(大小写不敏感?)然后按数字解析和排序。在所有其他情况下,使用正常的字符串排序。

标签: vb.net linq sorting


【解决方案1】:

听起来您可能正在寻找“NaturalSort” - 文件资源管理器用于对包含数字的文件名进行排序的那种显示。为此,您需要一个自定义比较器:

Imports System.Runtime.InteropServices

Partial Class NativeMethods
    <DllImport("shlwapi.dll", CharSet:=CharSet.Unicode)>
    Private Shared Function StrCmpLogicalW(s1 As String, s2 As String) As Int32
    End Function

    Friend Shared Function NaturalStringCompare(str1 As String, str2 As String) As Int32
        Return StrCmpLogicalW(str1, str2)
    End Function
End Class

Public Class NaturalStringComparer
    Implements IComparer(Of String)

    Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare
        Return NativeMethods.NaturalStringCompare(x, y)
    End Function
End Class

用它对你得到的结果进行排序:

Dim myComparer As New NaturalStringComparer

' OP post only shows the filename without path, so strip off path:
' (wont affect the result, just the display)
Dim files = Directory.EnumerateFiles(path_name_here).
                 Select(Function(s) Path.GetFileName(s)).ToList
Console.WriteLine("Before: {0}", String.Join(", ", files))

' sort the list using the Natural Comparer:
files.Sort(myComparer)
Console.WriteLine("After: {0}", String.Join(", ", files))

结果(单行以节省空间):

之前:file-1.txt、file-10.txt、file-11.txt、file-19.txt、file-2.txt、file-20.txt、file-3.txt、file-9 .txt、文件-99.txt
之后:file-1.txt、file-2.txt、file-3.txt、file-9.txt、file-10.txt、file-11.txt、file-19.txt、file-20.txt、文件-99.txt

这样做的一个优点是它不依赖于特定的模式或编码。它更通用,可以处理文本中的多组数字:

权力的游戏\4 - 群鸦盛宴\1 - Prologue.mp3
权力的游戏\4 - 群鸦盛宴\2 - 先知.mp3
...
权力的游戏\4 - 乌鸦的盛宴\10 - 布蕾妮 II.mp3
权力的游戏\4 - 群鸦的盛宴\11 - Sansa.mp3


Natural String Sort 非常方便,我个人不介意通过创建扩展来污染 Intellisense:

' List<string> version
<Extension>
Function ToNaturalSort(l As List(Of String)) As List(Of String)
    l.Sort(New NaturalStringComparer())
    Return l
End Function

' array version
<Extension>
Function ToNaturalSort(a As String()) As String()
    Array.Sort(a, New NaturalStringComparer())
    Return a
End Function

现在使用更简单:

Dim files = Directory.EnumerateFiles(your_path).
              Select(Function(s) Path.GetFileName(s)).
              ToList.
              ToNaturalSort()

' or without the path stripping:
Dim files = Directory.EnumerateFiles(your_path).ToList.ToNaturalSort()

【讨论】:

    【解决方案2】:

    我假设 file.txt 部分是可变的,在这里只是作为文件名和类型的占位符可以变化。

    我不经常使用正则表达式,所以这可能还需要一些工作,但这绝对是你需要去的方向:

    Dim exp As String = "-([0-9]+)[.][^.]*$"
    documentPages = Directory.GetFiles(documentPath, "*.txt").OrderBy(Function(p) Integer.Parse(Regex.Matches(p, exp)(0).Groups(1).Value)).ToList()
    

    再看一遍,我发现我错过了您正在按*.txt 文件过滤,这可以帮助我们缩小表达式:

    Dim exp As String = "-([0-9]+)[.]txt$"
    

    包含测试数据的其他答案带来的另一个可能的改进是允许- 和数字之间的空格:

    Dim exp As String = "-[ ]*([0-9]+)[.]txt$"
    

    进一步值得注意的是,如果存在不符合模式的文本文件,上述失败。如果需要,我们可以考虑到这一点:

    Dim exp As String = "-[ ]*([0-9]+)[.][^.]*$"
    Dim docs = Directory.GetFiles(documentPath, "*.txt")
    documentPages = docs.OrderBy(
         Function(p) 
                Dim matches As MatchCollection = Regex.Matches(p, exp)
                If matches.Count = 0 OrElse matches(0).Groups.Count < 2 Then Return 0
                Return Integer.Parse(matches(0).Groups(1).Value)
         End Function).ToList()
    

    您也可以使用Integer.MaxValue 作为默认选项,具体取决于您希望它们出现在列表的开头还是结尾。

    【讨论】:

    • 感谢您的回复。我有点看不到您在 OrderBy 函数中所做的事情。我尝试使用您的代码查看会发生什么,但出现错误。 Data type(s) of the type parameter(s) in extension method 'Public Function OrderBy(Of TKey)(keySelector As System.Func(Of String, TKey)) As System.Linq.IOrderedEnumerable(Of String)' defined in 'System.Linq.Enumerable' cannot be inferred from these arguments. Specifying the data type(s) explicitly might correct this error。你能解释一下你期望发生的更好的事情吗?
    • 现在应该会好一些。正如我所说:我不经常使用这些,我在回复窗口中输入了这些东西,根本没有使用 Visual Studio。
    • @varocarbas - 我真的认为你需要放松一下,哈哈。正如我在评论中指定的那样,我喜欢 Joel 使用的方法,主要是因为我已经以相同的方式紧密设置了我的代码。我还在 cmets 中注意到我使用了一组不同的正则表达式,这段代码非常适合我。我只是想找人来帮助我。我不想开始冗长的辩论。如果你今晚真的要为此失眠,我可以向你展示一个使用正确正则表达式的 Joel 代码的工作示例......我的天哪......
    • @varocarbas - 完全是为了传达您的信念,但是就应该如何运行 SO 和向 Joel 糟糕的答案发送垃圾邮件并不是这样做的地方。然后通过我发布的问题的各个方面抱怨你为什么没有被投票,因为答案很荒谬。我不认识你,也不认识你在哪里工作,或者和谁一起工作。老实说,我真的不想知道或听到它。如果你有兴趣清楚地传达想法,你就不会因为这种幼稚的行为而炸毁某人的回答线程。
    • @varocarbas - 说真的,我不是想冒犯你。我只是觉得你这样做的方式非常不正确。显然,我们使用 SO 来了解更多信息。回答你的问题(我想我已经说过“再次,这对我来说是客观的,宁愿使用正则表达式”,但要理解它在所有文本中都丢失了)是的,我认为如果使用正确,正则表达式可以是一种更强大的方法在某些情况下提供解决方案。但是,如果您真的对 SO 中发布的百万个问题中的 1 个问题感到满意,并且您想停止与我交谈,我建议您停止在我的线程中交谈。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-03
    • 2010-11-08
    • 2013-05-30
    • 1970-01-01
    • 2022-07-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多