【问题标题】:How can I use C# to sort values numerically?如何使用 C# 对数值进行排序?
【发布时间】:2010-12-08 16:41:38
【问题描述】:

我有一个字符串,其中包含用句点分隔的数字。当我排序时,它看起来像这样,因为它是一个字符串:(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

等等

我希望它像这样排序:(按数字顺序)

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

我知道我可以:

  1. 使用拆分函数获取单个数字
  2. 将值放入对象中
  3. 对对象进行排序

如果复制现有功能,我更愿意避免所有这些工作。 .net 框架中的方法是否已经这样做了?

【问题讨论】:

  • 你能更清楚你想要什么吗?你得到的排序和你想要的排序有什么区别?
  • 他希望它们按照给出的示例按升序排列,所以 10 跟在 9 之后,而不是跟在 1 之后。
  • 我认为 Tarzan 有一个按字符顺序排列的数据列表,并希望它按数字顺序排序。
  • 看到他没有费心回应过多的回应,我不会问他拆分字符串有什么问题。这不是“额外的工作”,运行时间为 O(N)。

标签: c# .net string sorting numbers


【解决方案1】:

这是我的工作解决方案,它还处理格式不正确的字符串(例如包含文本)。

这个想法是获取两个字符串中的第一个数字并比较这些数字。如果它们匹配,请继续下一个数字。如果他们不这样做,我们就有了赢家。如果这些数字根本不是数字,则对尚未比较的部分进行字符串比较。

通过改变确定下一个数字的方式,很容易使比较器完全兼容自然排序顺序。

看那个..刚刚找到this question

比较器:

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;
    }
}

用法:

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);
    }
}

输出:

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

【讨论】:

  • 可以说我们有以下数字 10 10.01 2 3 1.1 1.2 1 我得到错误的输出:1.1 1.2 1 2 3 10 10.01 你能帮我吗?!
  • 这项工作几乎完美。但非十进制数会出现在十进制数之后。例如:4.01、4.10、4
【解决方案2】:

不,我不相信框架中有任何东西可以自动执行此操作。您可以编写自己的 IComparer&lt;string&gt; 实现,它进行任何拆分,而是迭代两个字符串,只比较所需的数量(即只解析每个字符串的第一个数字,然后继续如有必要等),但我怀疑这将是相当繁琐的。例如,它还需要假设“1.2.3.4.5”与“1.3”相比如何(即,值包含不同数量的数字)。

【讨论】:

  • 我的解决方案完全符合您的解释。
【解决方案3】:

由于您要对字符串进行的比较与在 .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
            }
        }

然后您可以在 OrderBy 和 Sort 等方法中使用比较器

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

lst.Sort(new MyStringComparer());

这将为您提供所需的结果。如果不是,那么只需调整比较器。

【讨论】:

  • 没有 lambda?有点令人失望。不过,这是最有价值的解决方案。 +1
  • OP 希望避免字符串拆分。
  • 这必须以一种或另一种方式完成。他还可以使用 numString.Where(char.IsDigit) 删除 '.' (点)从字符串中,然后按字符比较字符串。
  • 嗯.. 关键是必须先解析字符串,然后才能进行比较。有些使用Split,有些使用Substring 和IndexOf。但无论如何都必须进行解析。
【解决方案4】:

您正在寻找的是自然排序顺序和Jeff Atwood bloged about it and has links to implementations in different languages。 .NET Framework 不包含实现。

【讨论】:

    【解决方案5】:

    您是否可以在前面使用0 将字段填充到相同的长度?如果是这样,那么您可以对字符串使用直接的字典排序。否则,框架中没有内置的这种方法可以自动执行此操作。如果不能选择填充,则必须实现自己的 IComparer&lt;string&gt;

    【讨论】:

      【解决方案6】:

      并非如此,尽管您可以使用正则表达式或 Linq 来避免过多的轮子重新发明。请记住,使用内置的东西与滚动你自己的东西在计算上的成本是一样的。

      试试这个:

      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++);
      }
      

      不是最紧凑的,但它应该可以工作,您可以使用 y-combinator 将比较变成 lambda。

      【讨论】:

      • 看起来相当昂贵,我很难理解你在那个大的 select 语句中所做的只是排序。
      【解决方案7】:

      用'.'分割每个字符串,遍历组件并进行数值比较。

      此代码还假定组件的数量很大(字符串 '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();
          }
      

      【讨论】:

      • OP 希望避免字符串拆分。
      • 啊。 =) OP 需要重新评估他的优先级。字符串拆分是涉及成本最低的操作。
      • 它帮助我完成了 80% 的排序谢谢 :)
      【解决方案8】:

      您可以使用 David Koelle 的 AlphanumComparator Alphanum natural sort algorithm

      代码:

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

      如果您要使用 C# 版本,请将其更改为:

      AlphanumComparator : IComparer<string>
      

      public int Compare(string x, string y)
      

      【讨论】:

        【解决方案9】:

        除了像 Jon 提到的那样实现自己的 IComparer 之外,如果您在数组上调用 ToList(),则可以调用 .Sort() 方法并传入一个比较两个值的函数参数,如下所示:@987654321 @

        【讨论】:

        • 我提到了它作为替代。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-10
        • 1970-01-01
        • 2011-10-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多