.NET中从1,10和2开始排序字符串并遵守数字排序的最短路径是什么?

时间:2011-08-26 12:32:20

标签: c# .net string sorting lexicographic

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

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

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

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

8 个答案:

答案 0 :(得分:7)

您可以使用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
);

然后,您可以创建使用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; 
  }

}

然后,您可以在各种排序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);
  }

}

更简单,但你对比较的控制力较弱。

答案 1 :(得分:4)

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

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

在一般情况下,或者如果您正在寻找更“标准”的方法来执行此操作,您可以p / invoke StrCmpLogicalW(),资源管理器使用它来对其视图中的文件名进行排序。但是,如果您要使用IComparer<string>,这样做会强制您实施OrderBy()

答案 2 :(得分:3)

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

最简单(不一定是最快/最佳)的方式是恕我直言,将它们全部左键填充到零预定义的最大长度。即。

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

答案 5 :(得分:0)

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

字符串比较总是逐字逐句。

如何在不查看整数的情况下处理这个问题?

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

答案 6 :(得分:0)

不,我不这么认为 - 我想你必须自己写,只要你的数据只是一个字符串。 如果您将数据变成类似

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

您可以使用LogBase-Field

进行排序

答案 7 :(得分: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));