使用LINQ进行字母数字排序

时间:2011-02-23 16:35:31

标签: c# linq sorting

我有一个string[],其中每个元素都以一些数字值结束。

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
  ..

10 个答案:

答案 0 :(得分:43)

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

要获得您想要的内容,您需要在order 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

填充仅发生在用于比较的密钥上。原始字符串(没有填充)将保留在结果中。

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

答案 1 :(得分:9)

可以在Dave Koelle的网站上找到“正常工作”的字母数字排序方法的正确实现。 C# version is here。{{3}}。

答案 2 :(得分:5)

如果你想使用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,尽管我的基准测试表明它稍慢。

答案 3 :(得分:3)

您可以 PInvoke StrCmpLogicalW(Windows函数)来执行此操作。见这里:Natural Sort Order in C#

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

答案 5 :(得分: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一样使用它。

答案 6 :(得分:1)

看起来好像是在进行字典排序,而不管是小型还是大写字母。

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

答案 7 :(得分:1)

在.NET中没有自然的方法,but have a look at this blog post on natural sorting

您可以将其放入扩展方法并使用它而不是OrderBy

答案 8 :(得分:0)

由于开头的字符数是可变的,因此使用正则表达式会有所帮助:

var re = new  Regex(@"\d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => int.Parse(re.Match(x).Value));

如果前缀数量固定,那么您可以使用Substring方法从相关字符开始提取

// parses the string as a number starting from the 5th character
var result = partNumbers.OrderBy(x => int.Parse(x.Substring(4)));

如果数字可能包含小数点分隔符或千位分隔符,则正则表达式也需要允许这些字符:

var re = new Regex(@"[\d,]*\.?\d+$");
var result = partNumbers.OrderBy(x => double.Parse(x.Substring(4)));

如果正则表达式或Substring返回的字符串可能无法被int.Parse / double.Parse解析,则使用相关的TryParse变体:

var re = new  Regex(@"\d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => {
    int? parsed = null;
    if (int.TryParse(re.Match(x).Value, out var temp)) {
        parsed = temp;
    }
    return parsed;
});

答案 9 :(得分:-2)

我不知道如何在LINQ中这样做,但也许你喜欢这样:

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

//显示结果

foreach (string h in partNumbers )
{
Console.WriteLine(h);
}