在.net中,对以1,10和2开头的字符串排序并尊重数字顺序的最短方法是什么?

本文关键字:数字 顺序 是什么 方法 字符串 对以 net 开头 排序 | 更新日期: 2023-09-27 18:06:48

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

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

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

Edit:目的是获得与在Windows资源管理器中选择"按名称排序"时相同的排序。

在.net中,对以1,10和2开头的字符串排序并尊重数字顺序的最短方法是什么?

可以使用Win32 CompareStringEx函数。在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
);

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

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; 
  }
}

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

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

您也可以使用StrCmpLogicalW,这是Windows资源管理器使用的功能。它自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);
  }
}

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

如果你的文件名总是只由数字组成,你可以使用Path.GetFileNameWithoutExtension()来丢弃文件扩展名,使用convert . toint32()(或类似的)来将文件名转换为整数以进行比较:

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

在一般情况下,或者如果您正在寻找一种更"标准"的方式来做到这一点,您可以调用StrCmpLogicalW(), Explorer使用它在其视图中对文件名进行排序。但是,如果您想使用OrderBy(),那么这样做将迫使您实现IComparer<string>

你应该选择其中之一

  • http://www.interact-sw.co.uk/iangblog/2007/12/13/natural-sorting
  • http://www.davekoelle.com/alphanum.html

最简单(不一定是最快/最优)的方法是将它们全部左填充到预定义的最大长度为零的位置。例如

var data = new[] { "1.log", "10.log", "2.log" };
data.OrderBy(x => x.PadLeft(10, '0')).Dump();

你可以删除所有的非数字字符,解析为整型,然后排序:

Regex r = new Regex(@"[^'d]");
OrderBy(fn => int.Parse(r.Replace(fn, "")));

当您可以确保您的名称的格式是NUMBER时,您可以这样做。价值:

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));

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

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

可以使用LogBase-Field

进行排序

如果按字典顺序排列就容易多了,

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

在不看整个数字的情况下,你想怎么处理呢?

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