【问题标题】:C# Sort files by natural number ordering in the name?C#按名称中的自然数排序对文件进行排序?
【发布时间】:2012-08-22 16:08:07
【问题描述】:

我在这样的目录中有文件

0-0.jpeg
0-1.jpeg
0-5.jpeg
0-9.jpeg
0-10.jpeg
0-12.jpeg

....

当我加载文件时:

FileInfo[] files = di.GetFiles();

他们的顺序错误(他们应该像上面那样):

0-0.jpeg
0-1.jpeg
0-10.jpeg
0-12.jpeg
0-5.jpeg
0-9.jpeg

如何解决?

我试图对它们进行排序,但没有办法:

1) Array.Sort(files, (f1, f2) => f1.Name.CompareTo(f2.Name));

2) Array.Sort(files, (x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.Name, y.Name)); 

【问题讨论】:

  • “错误的顺序”根据谁? 0-12.jpeg 出现在 0-5.jpeg 之前,因为 0-1 出现在 0-5 之前。排序方法着眼于字符,而不是数值。
  • 文件名按字符串排序,不是数字的问题。因此,12 总是在 9 之前。您可以确保文件名是 0-09.jpeg 或实现一个比较器将文件名转换为数字并进行比较。
  • @Mark Sherretta 我猜你是对的

标签: c#


【解决方案1】:

我知道这可能会迟到,但这是另一个完美的解决方案

FileInfo[] files = di.GetFiles().OrderBy(n => Regex.Replace(n.Name, @"\d+", n => n.Value.PadLeft(4, '0')));

OrderBy Clause 中使用正则表达式替换:

Regex.Replace(n.Name, @"\d+", n => n.Value.PadLeft(4, '0'))

那么这是做什么的,它pads文件名中的数值,每个数字的长度为4个字符:

0-0.jpeg     ->   0000-0000.jpeg
0-1.jpeg     ->   0000-0001.jpeg
0-5.jpeg     ->   0000-0005.jpeg
0-9.jpeg     ->   0000-0009.jpeg
0-10.jpeg    ->   0000-0010.jpeg
0-12.jpeg    ->   0000-0012.jpeg

但这仅发生在OrderBy 子句中,它不会以任何方式触及原始文件名。您最终在数组中的顺序是“人类自然”顺序。

【讨论】:

  • 这里展示的最简单的解决方案。为了以防万一,我将填充增加到 9 个字符。
  • 很棒的解决方案! :-)
  • 值得一提的是,这使用了Linq OrderBy,您的脚本文件中需要using System.Linq; ;)
  • @InteXX 记住,虽然这会增加一些不必要的开销(我有点确定在大多数情况下你不会有像 1000000000 个编号的文件^^).. 你应该保持填充为尽可能低,但在您的特定用例中尽可能高;)
  • 在遇到各种编译错误后,我不得不将其更改为FileInfo[] files = di.GetFiles().OrderBy(n => Regex.Replace(n.Name, @"\d+", x => x.Value.PadLeft(4, '0'))).ToArray();,例如Cannot implicitly convert type 'System.Linq.IOrderedEnumerable<System.IO.FileInfo>' to 'System.IO.FileInfo[]' (CS0029)A local variable named 'n' cannot be declared in this scope because it would give a different meaning to 'n', which is already used in a 'parent or current' scope to denote something else (CS0136)
【解决方案2】:

按字母顺序,“错误”的顺序实际上是正确的。如果您希望它按数字排序,那么您需要:

  1. 将文件名转换为数字列表并对其进行排序
  2. 以字母和数字排序相同的方式命名文件(0-001.jpeg 和 0-030.jpg)
  3. 依靠文件创建时间来排序(假设文件是​​按顺序创建的)。

#3 的示例请参见Sorting Directory.GetFiles() 的答案。

【讨论】:

  • 此链接对我很有帮助,我可以通过仅读取文件名 stackoverflow.com/a/9080636/1503372 的数字部分来执行数字排序。这有助于我在不更改文件命名规则的情况下应用排序。
【解决方案3】:

参见“CustomSort”函数here

List<string> list = new List<string>() { 
                    "0-5.jpeg",
                    "0-9.jpeg",
                    "0-0.jpeg",
                    "0-1.jpeg",
                    "0-10.jpeg",
                    "0-12.jpeg"};
list.CustomSort().ToList().ForEach(x => Console.WriteLine(x));

它的输出:

0-0.jpeg
0-1.jpeg
0-5.jpeg
0-9.jpeg
0-10.jpeg
0-12.jpeg

【讨论】:

    【解决方案4】:

    为了解决这个问题,您可以使用 StrCmpLogicalW windows API。

    更多详情请见This Artice

    【讨论】:

      【解决方案5】:

      为您的具体情况实现Comparison 方法并在Array.Sort 中使用它。

      private int CompareByNumericName(FileInfo firstFile, FileInfo secondFile)
      {
          /* First remove '0-' and '.jpeg' from both filenames then... */
      
          int firstFileNumericName = Int32.Parse(firstFile.Name);
          int secondFileNumericName = Int32.Parse(secondFile.Name);
      
          return firstFileNumericName.CompareTo(secondFileNumericName);
      }
      

       

      FileInfo[] files = di.GetFiles();
      Array.Sort<FileInfo>(files, CompareByNumericName);
      

      【讨论】:

        【解决方案6】:

        您的文件名似乎是结构化的。如果您只是对它们进行排序,它们将作为普通字符串进行排序。您需要:

        1. 将文件名解析为其组成部分。
        2. 将数字段转换为数值。
        3. 按所需顺序比较该结构以获得预期的整理顺序。

        就个人而言,我会创建一个类来表示文件名中隐含的结构。也许它应该包装FileInfo。该类的构造函数应将文件名解析为其组成部分并适当地实例化该类的属性。

        该类应实现IComparable/IComparable&lt;T&gt;(或者您可以创建Comparer 的实现)。

        对您的对象进行排序,然后它们应该按照您想要的排序顺序出现。

        如果您的文件名看起来由 3 部分组成:

        • 一个高阶数值(我们称之为'hi'),
        • 一个低阶数值(我们称之为'lo'),
        • 和一个扩展名(我们称之为“ext”)

        所以你的班级可能看起来像

        public class MyFileInfoWrapper : IComparable<MyFileInfoWrapper>,IComparable
        {
          public MyFileInfoWrapper( FileInfo fi )
          {
            // your implementation here
            throw new NotImplementedException() ;
          }
        
          public int    Hi         { get ; private set ; }
          public int    Lo         { get ; private set ; }
          public string Extension  { get ; private set ; }
        
          public FileInfo FileInfo { get ; private set ; }
        
          public int CompareTo( MyFileInfoWrapper other )
          {
            int cc ;
            if      ( other   == null     ) cc = -1 ;
            else if ( this.Hi <  other.Hi ) cc = -1 ;
            else if ( this.Hi >  other.Hi ) cc = +1 ;
            else if ( this.Lo <  other.Lo ) cc = -1 ;
            else if ( this.Lo >  other.Lo ) cc = +1 ;
            else                            cc = string.Compare( this.Extension , other.Extension , StringComparison.InvariantCultureIgnoreCase ) ;
            return cc ;
          }
        
          public int CompareTo( object obj )
          {
            int cc ;
            if      ( obj == null              ) cc = -1 ;
            else if ( obj is MyFileInfoWrapper ) cc = CompareTo( (MyFileInfoWrapper) obj ) ;
            else throw new ArgumentException("'obj' is not a 'MyFileInfoWrapper' type.", "obj") ;
            return cc ;
          }
        
        }
        

        【讨论】:

          【解决方案7】:

          这是我的解决方案。我先解析了文件路径,然后定义了顺序规则。

          对于以下代码,您需要命名空间System.Text.RegularExpressions

          Regex parseFileNameForNaturalSortingRegex = new Regex(
              @"
                  ^
                  (?<DirectoryName>.*)
                  (?<FullFileName>
                      (?<FileNameBasePart>[^/\\]*)
                      (?<FileNameNumericPart>\d+)?
                      (?>
                      \.
                      (?<FileNameExtension>[^./\\]+)
                      )?
                  )
                  $
              ",
              RegexOptions.IgnorePatternWhitespace | RegexOptions.RightToLeft
          );
          
          FileInfo[] filesOrdered = System.IO.Directory.GetFiles(@"C:\my\source\directory")
              .Select(fi =>
              {
                  Match match = parseFileNameForNaturalSortingRegex.Match(fi.FullName);
                  return new
                  {
                      FileInfo = fi,
                      DirectoryName = match.Groups["DirectoryName"].Value,
                      FullFileName = match.Groups["FullFileName"].Value,
                      BasePart = match.Groups["FileNameBasePart"].Value,
                      NumericPart = match.Groups["FileNameNumericPart"].Success ? int.Parse(match.Groups["FileNameNumericPart"].Value) : -1,
                      HasFileNameExtension = match.Groups["FileNameExtension"].Success,
                      FileNameExtension = match.Groups["FileNameExtension"].Value
                  };
              })
              .OrderBy(r => r.DirectoryName)
              .ThenBy(r => r.BasePart)
              .ThenBy(r => r.NumericPart)
              .ThenBy(r => r.HasFileNameExtension)
              .ThenBy(r => r.FileNameExtension)
              .Select(r => r.FileInfo)
              .ToArray();
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-06-25
            • 1970-01-01
            • 1970-01-01
            • 2011-07-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多