2011-02-23 310 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,日儘管我的基準測試顯示它稍慢。