简体   繁体   English

如何使用C#以数字方式对值进行排序?

[英]How can I use C# to sort values numerically?

I have a string that contains numbers separated by periods. 我有一个字符串,其中包含以句点分隔的数字。 When I sort it appears like this since it is a string: (ascii char order) 当我排序时它看起来像这样,因为它是一个字符串:(ascii char order)

3.9.5.2.1.1
3.9.5.2.1.10
3.9.5.2.1.11
3.9.5.2.1.12
3.9.5.2.1.2
3.9.5.2.1.3
3.9.5.2.1.4

etc. 等等

I want it to sort like this: (in numeric order) 我希望它像这样排序:(按数字顺序)

3.9.5.2.1.1
3.9.5.2.1.2
3.9.5.2.1.3
...
3.9.5.2.1.9
3.9.5.2.1.10
3.9.5.2.1.11
3.9.5.2.1.12

I know that I can: 我知道我可以:

  1. Use the Split function to get the individual numbers 使用“分割”功能可获取各个数字
  2. Put the values into an object 将值放入对象中
  3. Sort the object 对对象排序

I prefer to avoid all of that work if it is duplicating existing functionality. 如果重复现有功能,我宁愿避免所有这些工作。 Is a method in the .net framework that does this already? .net框架中的方法是否已经执行此操作?

Here's my working solution that also takes care of strings that are not in the right format (eg contain text). 这是我的工作解决方案,它也处理不正确格式的字符串(例如包含文本)。

The idea is to get the first number within both strings and compare these numbers. 我们的想法是获取两个字符串中的第一个数字并比较这些数字。 If they match, continue with the next number. 如果匹配,请继续下一个号码。 If they don't, we have a winner. 如果他们不这样做,我们就有了胜利者。 If one if these numbers isn't a number at all, do a string comparison of the part, which wasn't already compared. 如果这些数字不是一个数字,那么对该部分进行字符串比较,该部分尚未比较。

It would be easy to make the comparer fully compatible to natural sort order by changing the way to determine the next number. 通过改变确定下一个数字的方式,可以很容易地使比较器与自然排序顺序完全兼容。

Look at that.. just found this question . 看那个......刚发现这个问题

The Comparer: 比较者:

class StringNumberComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        int compareResult;
        int xIndex = 0, yIndex = 0;
        int xIndexLast = 0, yIndexLast = 0;
        int xNumber, yNumber;
        int xLength = x.Length;
        int yLength = y.Length;

        do
        {
            bool xHasNextNumber = TryGetNextNumber(x, ref xIndex, out xNumber);
            bool yHasNextNumber = TryGetNextNumber(y, ref yIndex, out yNumber);

            if (!(xHasNextNumber && yHasNextNumber))
            {
                // At least one the strings has either no more number or contains non-numeric chars
                // In this case do a string comparison of that last part
                return x.Substring(xIndexLast).CompareTo(y.Substring(yIndexLast));
            }

            xIndexLast = xIndex;
            yIndexLast = yIndex;

            compareResult = xNumber.CompareTo(yNumber);
        }
        while (compareResult == 0
            && xIndex < xLength
            && yIndex < yLength);

        return compareResult;
    }

    private bool TryGetNextNumber(string text, ref int startIndex, out int number)
    {
        number = 0;

        int pos = text.IndexOf('.', startIndex);
        if (pos < 0) pos = text.Length;

        if (!int.TryParse(text.Substring(startIndex, pos - startIndex), out number))
            return false;

        startIndex = pos + 1;

        return true;
    }
}

Usage: 用法:

public static void Main()
{
    var comparer = new StringNumberComparer();

    List<string> testStrings = new List<string>{
        "3.9.5.2.1.1",
        "3.9.5.2.1.10",
        "3.9.5.2.1.11",
        "3.9.test2",
        "3.9.test",
        "3.9.5.2.1.12",
        "3.9.5.2.1.2",
        "blabla",
        "....",
        "3.9.5.2.1.3",
        "3.9.5.2.1.4"};

    testStrings.Sort(comparer);

    DumpArray(testStrings);

    Console.Read();
}

private static void DumpArray(List<string> values)
{
    foreach (string value in values)
    {
        Console.WriteLine(value);
    }
}

Output: 输出:

....
3.9.5.2.1.1
3.9.5.2.1.2
3.9.5.2.1.3
3.9.5.2.1.4
3.9.5.2.1.10
3.9.5.2.1.11
3.9.5.2.1.12
3.9.test
3.9.test2
blabla

No, I don't believe there's anything in the framework which does this automatically. 不,我不相信框架中有任何自动执行此操作的内容。 You could write your own IComparer<string> implementation which doesn't do any splitting, but instead iterates over both strings, only comparing as much as is required (ie parsing just the first number of each, then continuing if necessary etc) but it would be quite fiddly I suspect. 您可以编写自己的IComparer<string>实现, 它不进行任何拆分,而是迭代两个字符串,只比较所需的数量(即只解析每个字符串的第一个数字,然后在必要时继续等)但它我怀疑是非常繁琐的。 It would also need to make assumptions about how "1.2.3.4.5" compared with "1.3" for example (ie where the values contain different numbers of numbers). 它还需要假设“1.2.3.4.5”与“1.3”相比如何(即值包含不同数量的数字)。

Since the comparison you want to do on the strings is different from how strings are normally compared in .Net, you will have to use a custom string string comparer 由于您要对字符串进行的比较与.Net中通常比较字符串的方式不同,因此您必须使用自定义字符串字符串比较器

 class MyStringComparer : IComparer<string>
        {
            public int Compare(string x, string y)
            {
                // your comparison logic
                // split the string using '.' separator
                // parse each string item in split array into an int
                // compare parsed integers from left to right
            }
        }

Then you can use the comparer in methods like OrderBy and Sort 然后,您可以在OrderBy和Sort等方法中使用比较器

var sorted = lst.OrderBy(s => s, new MyStringComparer());

lst.Sort(new MyStringComparer());

This will give you the desired result. 这将为您提供所需的结果。 If not then just tweak the comparer. 如果没有,那么只需调整比较器。

What you are looking for is the natural sort order and Jeff Atwood bloged about it and has links to implementations in different languages . 您正在寻找的是自然排序顺序, Jeff Atwood对此充满了兴趣,并且链接到不同语言的实现 The .NET Framework does not contain an implementation. .NET Framework不包含实现。

You can use the awesome AlphanumComparator Alphanum natural sort algorithm by David Koelle. 您可以使用David Koelle提供的令人敬畏的AlphanumComparator Alphanum自然排序算法

Code: 码:

OrderBy(o => o.MyString, new AlphanumComparator())

If you're gonna use the C# version change it to: 如果您要使用C#版本,请将其更改为:

AlphanumComparator : IComparer<string>

and

public int Compare(string x, string y)

Is it possible for you to pad your fields to the same length on the front with 0 ? 你有可能用0填充前面相同长度的字段吗? If so, then you can just use straight lexicographic sorting on the strings. 如果是这样,那么你可以在字符串上使用直接的词典排序。 Otherwise, there is no such method built in to the framework that does this automatically. 否则,框架内置的这种方法不会自动执行此操作。 You'll have to implement your own IComparer<string> if padding is not an option. 如果填充不是一个选项,则必须实现自己的IComparer<string>

Not really, though you may be able to use Regexes or Linq to avoid too much wheel-reinventing. 虽然你可能能够使用正则表达式或Linq避免过多的轮子重新发明,但事实并非如此。 Keep in mind it will cost you much the same computationally to use something built-in as to roll your own. 请记住,使用内置的东西来推动自己的计算会大大降低计算成本。

Try this: 试试这个:

List<string> myList = GetNumberStrings();

myList.Select(s=>s.Split('.')).ToArray().
   .Sort((a,b)=>RecursiveCompare(a,b))
   .Select(a=>a.Aggregate(new StringBuilder(),
      (s,sb)=>sb.Append(s).Append(".")).Remove(sb.Length-1, 1).ToString())
   .ToList();

...

public int RecursiveCompare(string[] a, string[] b)
{
    return RecursiveCompare(a,b,0)
}

public int RecursiveCompare(string[] a, string[] b, int index)
{
    return index == a.Length || index == b.Length 
        ? 0 
        : a[index] < b[index] 
            ? -1 
            : a[index] > b[index] 
                ? 1 
                : RecursiveCompare(a,b, index++);
}

Not the most compact, but it should work and you could use a y-combinator to make the comparison a lambda. 不是最紧凑的,但它应该工作,你可以使用y组合器使比较成为一个lambda。

Split each string by '.', iterate through the components and compare them numerically. 用'。'拆分每个字符串,遍历组件并以数字方式对它们进行比较。

This code also assumes that the number of components is signficant (a string '1.1.1' will be greater than '2.1'. This can be adjusted by altering the first if statement in the Compare method below. 此代码还假设组件的数量是显着的(字符串'1.1.1'将大于'2.1'。这可以通过在下面的Compare方法中更改第一个if语句来调整。

    int Compare(string a, string b)
    {
        string[] aParts = a.Split('.');
        string[] bParts = b.Split('.');

        /// if A has more components than B, it must be larger.
        if (aParts.Length != bParts.Length)
            return (aParts.Length > bParts.Length) ? 1 : -1;

        int result = 0;
        /// iterate through each numerical component

        for (int i = 0; i < aParts.Length; i++)
            if ( (result = int.Parse(aParts[i]).CompareTo(int.Parse(bParts[i]))) !=0 )
                return result;

        /// all components are equal.
        return 0;
    }



    public string[] sort()
    {
        /// initialize test data
        string l = "3.9.5.2.1.1\n"
        + "3.9.5.2.1.10\n"
        + "3.9.5.2.1.11\n"
        + "3.9.5.2.1.12\n"
        + "3.9.5.2.1.2\n"
        + "3.9.5.2.1.3\n"
        + "3.9.5.2.1.4\n";

        /// split the large string into lines
        string[] arr = l.Split(new char[] { '\n' },StringSplitOptions.RemoveEmptyEntries);
        /// create a list from the array
        List<string> strings = new List<string>(arr);
        /// sort using our custom sort routine
        strings.Sort(Compare);
        /// concatenate the list back to an array.
        return strings.ToArray();
    }

除了像Jon提到的那样实现你自己的IComparer之外,如果在数组上调用ToList(),你可以调用.Sort()方法并传入一个比较两个值的函数参数,如下所示: http:// msdn .microsoft.com / EN-US /库/ w56d4y5z.aspx

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM