2011-02-23 190 views
24

我有一个string[]其中每个元素都以某个数值结尾。使用LINQ的字母数字排序

string[] partNumbers = new string[] 
{ 
    "ABC10", "ABC1","ABC2", "ABC11","ABC10", "AB1", "AB2", "Ab11" 
}; 

我想上述阵列使用LINQ如下排序,但我没有得到预期的结果。

var result = partNumbers.OrderBy(x => x); 

实际结果:

AB1
AB11
AB2
ABC1
ABC10
ABC10
ABC11
ABC2

预期结果

AB1
AB2
AB11
..

+0

有关字母数字排序(预期结果)与ASCII排序(实际结果)的比较[有用文章](http://www.dotnetperls.com/alphanumeric-sorting) – mcdon 2014-10-21 21:12:13

回答

24

,这是因为对于字符串的默认排序是标准的字母数字字典(词典)排序,并且ABC11将在ABC2之前出现,因为排序总是从左到右进行。

为了得到你想要的东西,你需要垫by子句在您的订单数字部分,是这样的:

var result = partNumbers.OrderBy(x => PadNumbers(x)); 

其中PadNumbers可以定义为:

public static string PadNumbers(string input) 
{ 
    return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0')); 
} 

这片零对于任何出现在输入字符串中的数字(或数字),以便OrderBy看到:

ABC0000000010 
ABC0000000001 
... 
AB0000000011 

填充只发生在用于比较的键上。原始字符串(无填充)将保留在结果中。

请注意,此方法假设输入中的数字的最大位数。

+3

@geek:没有预定义的函数那个名字。我建议你用我描述的行为来实现一个函数,使用正则表达式或一些这样的方法。函数名称仅用于说明目的。 – Nathan 2011-02-23 17:12:16

+1

我继续前进,添加了一个简单的函数来完成填充。 – 2016-06-24 18:42:12

1

嗯看起来像它做了辞书订购不论小或资本字符。

您可以尝试在该lambda中使用一些自定义表达式来执行该操作。

-2

我不知道该怎么做,在LINQ,但也许你喜欢这种方式:

Array.Sort(partNumbers, new AlphanumComparatorFast()); 

//显示结果

foreach (string h in partNumbers) 
{ 
Console.WriteLine(h); 
} 
3
public class AlphanumComparatorFast : IComparer 
{ 
    List<string> GetList(string s1) 
    { 
     List<string> SB1 = new List<string>(); 
     string st1, st2, st3; 
     st1 = ""; 
     bool flag = char.IsDigit(s1[0]); 
     foreach (char c in s1) 
     { 
      if (flag != char.IsDigit(c) || c=='\'') 
      { 
       if(st1!="") 
       SB1.Add(st1); 
       st1 = ""; 
       flag = char.IsDigit(c); 
      } 
      if (char.IsDigit(c)) 
      { 
       st1 += c; 
      } 
      if (char.IsLetter(c)) 
      { 
       st1 += c; 
      } 


     } 
     SB1.Add(st1); 
     return SB1; 
    } 



    public int Compare(object x, object y) 
    { 
     string s1 = x as string; 
     if (s1 == null) 
     { 
      return 0; 
     } 
     string s2 = y as string; 
     if (s2 == null) 
     { 
      return 0; 
     } 
     if (s1 == s2) 
     { 
      return 0; 
     } 
     int len1 = s1.Length; 
     int len2 = s2.Length; 
     int marker1 = 0; 
     int marker2 = 0; 

     // Walk through two the strings with two markers. 
     List<string> str1 = GetList(s1); 
     List<string> str2 = GetList(s2); 
     while (str1.Count != str2.Count) 
     { 
      if (str1.Count < str2.Count) 
      { 
       str1.Add(""); 
      } 
      else 
      { 
       str2.Add(""); 
      } 
     } 
     int x1 = 0; int res = 0; int x2 = 0; string y2 = ""; 
     bool status = false; 
     string y1 = ""; bool s1Status = false; bool s2Status = false; 
     //s1status ==false then string ele int; 
     //s2status ==false then string ele int; 
     int result = 0; 
     for (int i = 0; i < str1.Count && i < str2.Count; i++) 
     { 
      status = int.TryParse(str1[i].ToString(), out res); 
      if (res == 0) 
      { 
       y1 = str1[i].ToString(); 
       s1Status = false; 
      } 
      else 
      { 
       x1 = Convert.ToInt32(str1[i].ToString()); 
       s1Status = true; 
      } 

      status = int.TryParse(str2[i].ToString(), out res); 
      if (res == 0) 
      { 
       y2 = str2[i].ToString(); 
       s2Status = false; 
      } 
      else 
      { 
       x2 = Convert.ToInt32(str2[i].ToString()); 
       s2Status = true; 
      } 
      //checking --the data comparision 
      if(!s2Status && !s1Status) //both are strings 
      { 
       result = str1[i].CompareTo(str2[i]); 
      } 
      else if (s2Status && s1Status) //both are intergers 
      { 
       if (x1 == x2) 
       { 
        if (str1[i].ToString().Length < str2[i].ToString().Length) 
        { 
         result = 1; 
        } 
        else if (str1[i].ToString().Length > str2[i].ToString().Length) 
         result = -1; 
        else 
         result = 0; 
       } 
       else 
       { 
        int st1ZeroCount=str1[i].ToString().Trim().Length- str1[i].ToString().TrimStart(new char[]{'0'}).Length; 
        int st2ZeroCount = str2[i].ToString().Trim().Length - str2[i].ToString().TrimStart(new char[] { '0' }).Length; 
        if (st1ZeroCount > st2ZeroCount) 
         result = -1; 
        else if (st1ZeroCount < st2ZeroCount) 
         result = 1; 
        else 
        result = x1.CompareTo(x2); 

       } 
      } 
      else 
      { 
       result = str1[i].CompareTo(str2[i]); 
      } 
      if (result == 0) 
      { 
       continue; 
      } 
      else 
       break; 

     } 
     return result; 
    } 
} 

用途本类:

List<string> marks = new List<string>(); 
       marks.Add("M'00Z1"); 
       marks.Add("M'0A27"); 
       marks.Add("M'00Z0"); 
marks.Add("0000A27"); 
       marks.Add("100Z0"); 

    string[] Markings = marks.ToArray(); 

       Array.Sort(Markings, new AlphanumComparatorFast()); 
3

可以使用的PInvoke得到快速和良好的效果:

class AlphanumericComparer : IComparer<string> 
{ 
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] 
    static extern int StrCmpLogicalW(string s1, string s2); 

    public int Compare(string x, string y) => StrCmpLogicalW(x, y); 
} 

您可以使用它像AlphanumComparatorFast从上面的答案。

3

如果你想使用LINQ和一个自定义比较像一个由Dave Koelle,你会做这样的事情一个特定的属性来排序对象的列表:

... 

items = items.OrderBy(x => x.property, new AlphanumComparator()).ToList(); 

... 

你还必须改变Dave的类从System.Collections.Generic.IComparer<object>而不是基本IComparer继承所以类签名变为:

... 

public class AlphanumComparator : System.Collections.Generic.IComparer<object> 
{ 

    ... 

就个人而言,我通过James McCormack喜欢的实现,因为它实现IDisposable,日尽管我的基准测试显示它稍慢。