2010-09-15 137 views
16

Làm thế nào để sắp xếp một danh sách dựa trên giá trị số nguyên của mụcSắp xếp Danh sách <String> trong C#

Danh sách này cũng giống như

"1" 
"5" 
"3" 
"6" 
"11" 
"9" 
"NUM1" 
"NUM0" 

Kết quả sẽ được như

"1" 
"3" 
"5" 
"6" 
"9" 
"11" 
"NUM0" 
"NUM1" 

là có bất kỳ ý tưởng để làm điều này bằng cách sử dụng biểu thức LINQ hoặc Lambda?

Cảm ơn trước

+0

Do các giá trị chuỗi đại diện cho số thập lục phân? Hoặc nó sẽ có thể cho "S2" xuất hiện trong danh sách nói? –

+0

@El Ronnoco: Không có hệ thập lục phân. Nó có thể là "S2" vv ... (Đã chỉnh sửa) –

Trả lời

16

Làm thế nào về:.

list.Sort((x, y) => 
    { 
     int ix, iy; 
     return int.TryParse(x, out ix) && int.TryParse(y, out iy) 
       ? ix.CompareTo(iy) : string.Compare(x, y); 
    }); 
+4

điều gì về sự khác biệt trong "NUM10" và "NUM2", đối với chúng tôi "NUM2" rõ ràng là trước "NUM10" nhưng sẽ không được sắp xếp theo cách đó – Xander

+5

@Xander - xác định "rõ ràng"; câu hỏi được trích dẫn "giá trị số nguyên" - nhưng trừ khi OP xác định các thuật ngữ/mẫu mà * nên * được cho phép, thì tôi không bao gồm "NUM10" làm giá trị số nguyên. –

+1

Xin lỗi khi tôi có nghĩa là "rõ ràng", đó là về cách một con người sẽ cảm nhận được giá trị có ý nghĩa ... như trong "NUM" và một số nguyên mười "NUM10" ... Đó là nhiều hơn một người đứng đầu lên đến hãy chắc chắn rằng một loại đơn giản đơn giản có thể/có thể không được mong muốn. – Xander

-1

Tôi không nghĩ rằng bạn cần bất cứ điều gì ngoài listName.Sort() vì sort() phương pháp sử dụng comparer mặc định để các nút sắp xếp nhanh chóng. Mặc định Comparer thực hiện chính xác những gì bạn đang quan tâm đến việc

+2

Không hoàn toàn. Bạn sẽ nhận được "11" ngay sau "1" và trước "2". – liggett78

16

này được gọi là "thứ tự sắp xếp tự nhiên", và thường được sử dụng để sắp xếp các mục như những người bạn có, như tên tập tin và như vậy .

Dưới đây là một ngây thơ (theo nghĩa rằng có lẽ nhiều unicode-vấn đề với nó) thực hiện điều đó dường như làm các trick:

Bạn có thể sao chép mã dưới đây vào LINQPad để thực hiện nó và thử nghiệm nó.

Về cơ bản các thuật toán so sánh sẽ xác định số bên trong chuỗi, và xử lý những bằng đệm một ngắn nhất với zero hàng đầu, vì vậy ví dụ hai chuỗi "Test123Abc""Test7X" nên được so sánh như thể chúng là "Test123Abc""Test007X", mà nên sản xuất bạn muốn gì.

Tuy nhiên, khi tôi nói "ngây thơ", tôi có nghĩa là tôi có thể có tấn vấn đề unicode thực sự ở đây, giống như xử lý dấu phụ và ký tự đa điểm. Nếu bất cứ ai có thể thực hiện tốt hơn, tôi rất thích nhìn thấy nó.

Ghi chú:

  • Việc thực hiện không thực sự phân tích các con số, vì vậy tùy tiện số dài nên chỉ làm việc tốt
  • Vì nó không thực sự phân tích những con số như "số", số dấu chấm động sẽ không được xử lý đúng cách, "123.45" và "123.789" sẽ được so sánh là "123.045" so với "123.789", điều đó là sai.

Code:

void Main() 
{ 
    List<string> input = new List<string> 
    { 
     "1", "5", "3", "6", "11", "9", "A1", "A0" 
    }; 
    var output = input.NaturalSort(); 
    output.Dump(); 
} 

public static class Extensions 
{ 
    public static IEnumerable<string> NaturalSort(
     this IEnumerable<string> collection) 
    { 
     return NaturalSort(collection, CultureInfo.CurrentCulture); 
    } 

    public static IEnumerable<string> NaturalSort(
     this IEnumerable<string> collection, CultureInfo cultureInfo) 
    { 
     return collection.OrderBy(s => s, new NaturalComparer(cultureInfo)); 
    } 

    private class NaturalComparer : IComparer<string> 
    { 
     private readonly CultureInfo _CultureInfo; 

     public NaturalComparer(CultureInfo cultureInfo) 
     { 
      _CultureInfo = cultureInfo; 
     } 

     public int Compare(string x, string y) 
     { 
      // simple cases 
      if (x == y) // also handles null 
       return 0; 
      if (x == null) 
       return -1; 
      if (y == null) 
       return +1; 

      int ix = 0; 
      int iy = 0; 
      while (ix < x.Length && iy < y.Length) 
      { 
       if (Char.IsDigit(x[ix]) && Char.IsDigit(y[iy])) 
       { 
        // We found numbers, so grab both numbers 
        int ix1 = ix++; 
        int iy1 = iy++; 
        while (ix < x.Length && Char.IsDigit(x[ix])) 
         ix++; 
        while (iy < y.Length && Char.IsDigit(y[iy])) 
         iy++; 
        string numberFromX = x.Substring(ix1, ix - ix1); 
        string numberFromY = y.Substring(iy1, iy - iy1); 

        // Pad them with 0's to have the same length 
        int maxLength = Math.Max(
         numberFromX.Length, 
         numberFromY.Length); 
        numberFromX = numberFromX.PadLeft(maxLength, '0'); 
        numberFromY = numberFromY.PadLeft(maxLength, '0'); 

        int comparison = _CultureInfo 
         .CompareInfo.Compare(numberFromX, numberFromY); 
        if (comparison != 0) 
         return comparison; 
       } 
       else 
       { 
        int comparison = _CultureInfo 
         .CompareInfo.Compare(x, ix, 1, y, iy, 1); 
        if (comparison != 0) 
         return comparison; 
        ix++; 
        iy++; 
       } 
      } 

      // we should not be here with no parts left, they're equal 
      Debug.Assert(ix < x.Length || iy < y.Length); 

      // we still got parts of x left, y comes first 
      if (ix < x.Length) 
       return +1; 

      // we still got parts of y left, x comes first 
      return -1; 
     } 
    } 
} 
+0

Tôi đã mở một câu hỏi để tìm ra cách tốt hơn để xử lý dấu phụ và ký tự đa điểm, tại đây: http://stackoverflow.com/questions/3717132/writing-a-better-natural-sort –

2

Jeff Atwood có blog post về phân loại tự nhiên nơi ông liên kết với một số hiện thực có sẵn của thuật toán mong muốn.

Một trong Jeffs liên kết điểm để Dave Koelle cách có C# implementation:

/* 
* The Alphanum Algorithm is an improved sorting algorithm for strings 
* containing numbers. Instead of sorting numbers in ASCII order like 
* a standard sort, this algorithm sorts numbers in numeric order. 
* 
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com 
* 
* Based on the Java implementation of Dave Koelle's Alphanum algorithm. 
* Contributed by Jonathan Ruckwood <[email protected]> 
* 
* Adapted by Dominik Hurnaus <[email protected]> to 
* - correctly sort words where one word starts with another word 
* - have slightly better performance 
* 
* Released under the MIT License - https://opensource.org/licenses/MIT 
* 
* Permission is hereby granted, free of charge, to any person obtaining 
* a copy of this software and associated documentation files (the "Software"), 
* to deal in the Software without restriction, including without limitation 
* the rights to use, copy, modify, merge, publish, distribute, sublicense, 
* and/or sell copies of the Software, and to permit persons to whom the 
* Software is furnished to do so, subject to the following conditions: 
* 
* The above copyright notice and this permission notice shall be included 
* in all copies or substantial portions of the Software. 
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 
* USE OR OTHER DEALINGS IN THE SOFTWARE. 
* 
*/ 
using System; 
using System.Collections; 
using System.Text; 

/* 
* Please compare against the latest Java version at http://www.DaveKoelle.com 
* to see the most recent modifications 
*/ 
namespace AlphanumComparator 
{ 
    public class AlphanumComparator : IComparer 
    { 
     private enum ChunkType {Alphanumeric, Numeric}; 
     private bool InChunk(char ch, char otherCh) 
     { 
      ChunkType type = ChunkType.Alphanumeric; 

      if (char.IsDigit(otherCh)) 
      { 
       type = ChunkType.Numeric; 
      } 

      if ((type == ChunkType.Alphanumeric && char.IsDigit(ch)) 
       || (type == ChunkType.Numeric && !char.IsDigit(ch))) 
      { 
       return false; 
      } 

      return true; 
     } 

     public int Compare(object x, object y) 
     { 
      String s1 = x as string; 
      String s2 = y as string; 
      if (s1 == null || s2 == null) 
      { 
       return 0; 
      } 

      int thisMarker = 0, thisNumericChunk = 0; 
      int thatMarker = 0, thatNumericChunk = 0; 

      while ((thisMarker < s1.Length) || (thatMarker < s2.Length)) 
      { 
       if (thisMarker >= s1.Length) 
       { 
        return -1; 
       } 
       else if (thatMarker >= s2.Length) 
       { 
        return 1; 
       } 
       char thisCh = s1[thisMarker]; 
       char thatCh = s2[thatMarker]; 

       StringBuilder thisChunk = new StringBuilder(); 
       StringBuilder thatChunk = new StringBuilder(); 

       while ((thisMarker < s1.Length) && (thisChunk.Length==0 ||InChunk(thisCh, thisChunk[0]))) 
       { 
        thisChunk.Append(thisCh); 
        thisMarker++; 

        if (thisMarker < s1.Length) 
        { 
         thisCh = s1[thisMarker]; 
        } 
       } 

       while ((thatMarker < s2.Length) && (thatChunk.Length==0 ||InChunk(thatCh, thatChunk[0]))) 
       { 
        thatChunk.Append(thatCh); 
        thatMarker++; 

        if (thatMarker < s2.Length) 
        { 
         thatCh = s2[thatMarker]; 
        } 
       } 

       int result = 0; 
       // If both chunks contain numeric characters, sort them numerically 
       if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0])) 
       { 
        thisNumericChunk = Convert.ToInt32(thisChunk.ToString()); 
        thatNumericChunk = Convert.ToInt32(thatChunk.ToString()); 

        if (thisNumericChunk < thatNumericChunk) 
        { 
         result = -1; 
        } 

        if (thisNumericChunk > thatNumericChunk) 
        { 
         result = 1; 
        } 
       } 
       else 
       { 
        result = thisChunk.ToString().CompareTo(thatChunk.ToString()); 
       } 

       if (result != 0) 
       { 
        return result; 
       } 
      } 

      return 0; 
     } 
    } 
} 
2

Hãy thử viết một lớp helper nhỏ để phân tích và đại diện cho thẻ của bạn.Ví dụ, không quá nhiều chi phiếu:

public class NameAndNumber 
{ 
    public NameAndNumber(string s) 
    { 
     OriginalString = s; 
     Match match = Regex.Match(s,@"^(.*?)(\d*)$"); 
     Name = match.Groups[1].Value; 
     int number; 
     int.TryParse(match.Groups[2].Value, out number); 
     Number = number; //will get default value when blank 
    } 

    public string OriginalString { get; private set; } 
    public string Name { get; private set; } 
    public int Number { get; private set; } 
} 

Bây giờ nó trở nên dễ dàng để viết một Comparer, hoặc sắp xếp nó bằng tay:

var list = new List<string> { "ABC", "1", "5", "NUM44", "3", 
           "6", "11", "9", "NUM1", "NUM0" }; 

var sorted = list.Select(str => new NameAndNumber(str)) 
    .OrderBy(n => n.Name) 
    .ThenBy(n => n.Number); 

Cung cấp kết quả:

1, 3, 5, 6, 9, 11, ABC, NUM0, NUM1, NUM44

+0

Như một lưu ý phụ , mã chỉ liên quan đến số gần cuối chuỗi - 'a123b12' ->' Tên: a123b', 'Số: 12' – Kobi

0

Đây là thuật toán nhanh nhất - đưa tôi 2 triệu để sắp xếp 5 0 sản phẩm ~

static void Sort() 
{ 
    string[] partNumbers = new string[] {"A1", "A2", "A10", "A111"}; 
    string[] result = partNumbers.OrderBy(x => PadNumbers(x)).ToArray(); 
} 


public static string PadNumbers(string input) 
{ 
     const int MAX_NUMBER_LEN = 10; 

     string newInput = ""; 
     string currentNumber = ""; 
     foreach (char a in input) 
     { 
      if (!char.IsNumber(a)) 
      { 
       if (currentNumber == "") 
       { 
        newInput += a; 
        continue; 
       } 
       newInput += "0000000000000".Substring(0, MAX_NUMBER_LEN - currentNumber.Length) + currentNumber; 
       currentNumber = ""; 
      } 
      currentNumber += a; 
     } 
     if (currentNumber != "") 
     { 
      newInput += "0000000000000".Substring(0, MAX_NUMBER_LEN - currentNumber.Length) + currentNumber; 
     } 

     return newInput; 
    } 

~

-1

Đây là một C# 7 giải pháp (giả danh sách có tên a):

var numericList = a.Where(i => int.TryParse(i, out _)).OrderBy(j => int.Parse(j)).ToList(); 
    var nonNumericList = a.Where(i => !int.TryParse(i, out _)).OrderBy(j => j).ToList(); 
    a.Clear(); 
    a.AddRange(numericList); 
    a.AddRange(nonNumericList); 
Các vấn đề liên quan