11

我需要对文件名进行如下排序:1.log、2.log、10.log

但是当我使用 OrderBy(fn => fn) 时,它会将它们排序为:1.log、10.log、2.log

我显然知道这可以通过编写另一个比较器来完成,但是有没有更简单的方法可以从字典顺序更改为自然排序顺序?

编辑:目标是获得与在 Windows 资源管理器中选择“按名称排序”时相同的排序。

4

8 回答 8

7

您可以使用 Win32CompareStringEx功能。在 Windows 7 上,它支持您需要的排序。您将使用 P/Invoke:

static readonly Int32 NORM_IGNORECASE = 0x00000001;
static readonly Int32 NORM_IGNORENONSPACE = 0x00000002;
static readonly Int32 NORM_IGNORESYMBOLS = 0x00000004;
static readonly Int32 LINGUISTIC_IGNORECASE = 0x00000010;
static readonly Int32 LINGUISTIC_IGNOREDIACRITIC = 0x00000020;
static readonly Int32 NORM_IGNOREKANATYPE = 0x00010000;
static readonly Int32 NORM_IGNOREWIDTH = 0x00020000;
static readonly Int32 NORM_LINGUISTIC_CASING = 0x08000000;
static readonly Int32 SORT_STRINGSORT = 0x00001000;
static readonly Int32 SORT_DIGITSASNUMBERS = 0x00000008; 

static readonly String LOCALE_NAME_USER_DEFAULT = null;
static readonly String LOCALE_NAME_INVARIANT = String.Empty;
static readonly String LOCALE_NAME_SYSTEM_DEFAULT = "!sys-default-locale";

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern Int32 CompareStringEx(
  String localeName,
  Int32 flags,
  String str1,
  Int32 count1,
  String str2,
  Int32 count2,
  IntPtr versionInformation,
  IntPtr reserved,
  Int32 param
);

然后,您可以创建一个IComparer使用该SORT_DIGITSASNUMBERS标志的:

class LexicographicalComparer : IComparer<String> {

  readonly String locale;

  public LexicographicalComparer() : this(CultureInfo.CurrentCulture) { }

  public LexicographicalComparer(CultureInfo cultureInfo) {
    if (cultureInfo.IsNeutralCulture)
      this.locale = LOCALE_NAME_INVARIANT;
    else
      this.locale = cultureInfo.Name;
  }

  public Int32 Compare(String x, String y) {
    // CompareStringEx return 1, 2, or 3. Subtract 2 to get the return value.
    return CompareStringEx( 
      this.locale, 
      SORT_DIGITSASNUMBERS, // Add other flags if required.
      x, 
      x.Length, 
      y, 
      y.Length, 
      IntPtr.Zero, 
      IntPtr.Zero, 
      0) - 2; 
  }

}

然后,您可以IComparer在各种排序 API 中使用:

var names = new [] { "2.log", "10.log", "1.log" };
var sortedNames = names.OrderBy(s => s, new LexicographicalComparer());

您还可以使用Windows Explorer 使用的函数StrCmpLogicalW 。它从 Windows XP 开始可用:

[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
static extern Int32 StrCmpLogical(String x, String y);

class LexicographicalComparer : IComparer<String> {

  public Int32 Compare(String x, String y) {
    return StrCmpLogical(x, y);
  }

}

更简单,但您对比较的控制较少。

于 2011-08-26T13:59:24.300 回答
4

如果您的文件名始终只包含数字,您可以使用Path.GetFileNameWithoutExtension()丢弃文件扩展名和Convert.ToInt32()(或类似方法)将文件名转换为整数以进行比较:

var ordered = yourFileNames.OrderBy(
    fn => Convert.ToInt32(Path.GetFileNameWithoutExtension(fn)));

在一般情况下,或者如果您正在寻找一种更“标准”的方式来执行此操作,您可以 p/invoke StrCmpLogicalW(),Explorer 使用它在其视图中对文件名进行排序。但是,IComparer<string>如果您想使用OrderBy().

于 2011-08-26T12:39:02.960 回答
3

你应该采取其中之一

于 2011-08-26T12:45:43.130 回答
2

最简单(不一定是最快/最佳)的方法是恕我直言,将它们全部左填充到某个预定义的最大长度,并用零。IE

var data = new[] { "1.log", "10.log", "2.log" };
data.OrderBy(x => x.PadLeft(10, '0')).Dump();
于 2011-08-26T12:37:12.927 回答
2

您可以删除所有非数字字符,解析为 int 然后排序:

Regex r = new Regex(@"[^\d]");
OrderBy(fn => int.Parse(r.Replace(fn, "")));
于 2011-08-26T12:35:37.170 回答
0

当您可以确保您的姓名格式为 NUMBER.VALUE 时,您可以执行以下操作:

var q = strings.Select(s => s.Split(new[] {'.'}, 2))
    .Select(s => new
                        {
                            Number = Convert.ToInt32(s[0]),
                            Name = s[1]
                        })
    .OrderBy(s => s.Number)
    .Select(s => string.Format("{0}.{1}", s.Number, s.Name));
于 2011-08-26T12:38:19.883 回答
0

如果它是一个字典顺序会更容易,。

字符串比较总是一个字母一个字母。

您想如何在不查看整数的情况下处理它?

不,单独的比较器是唯一的解决方案。

于 2011-08-26T12:34:45.033 回答
0

不,我不这么认为——我猜你必须自己写,只要你的数据只是一个字符串。如果你把你的数据变成类似的东西

struct LogDescription
{
   public int LogBase { get; set; }
   public override ToString()
   { return string.Format("{0}.log", LogBase); }
}

您可以使用 LogBase-Field 进行排序

于 2011-08-26T12:35:57.387 回答