【问题标题】:Sort numbers then strings C#排序数字,然后是字符串 C#
【发布时间】:2017-03-31 19:45:08
【问题描述】:

我想要这份清单

A
B
C
111
11
123
1
42
5

待排序

1
5
11
42
111
123
A
B
C

默认情况下,它像字符串一样对数字进行排序(所以,它是 1,11,111,123,42,5), 但我想对数字进行排序,而不是对不是数字的字符串进行排序。

有没有像上面那样排序的干净解决方案?

它是一个对象列表,对象有几个属性,其中一个是this字符串。

【问题讨论】:

  • 您的列表从什么类型开始?你已经尝试过什么?
  • 您的数字是表示为字符串还是整数?
  • list.GroupBy(s => int.TryParse(s, out var ignore)).OrderByDescending(g => g.Key).SelectMany(g => g.Key ? g.OrderBy(s => int.Parse(s)) : g.OrderBy(s => s));
  • 这在技术上可能可行,但我为下一个程序员感到抱歉,因为你必须弄清楚那行代码在做什么,哈哈

标签: c# list sorting


【解决方案1】:

这适用于大多数用例,但如果字符串以控制字符开头,可能会产生奇怪的结果,像“\tabc”这样的字符串将出现在整数之前:

list.OrderBy(x=>int.TryParse(x, out var dummy) ? dummy.ToString("D10") : x);

或者对于 7 之前的 C# 版本:

list.OrderBy(x=> { int dummy; return int.TryParse(x, out dummy) ? dummy.ToString("D10") : x;} );

【讨论】:

  • 你知道你可以做dummy.ToString("D10")而不是另一个解析。
  • @juharr 更新了不需要两次解析的答案,并添加了 C# 7.0 之前的版本。
  • 为什么使用D10?我不明白
  • @RoadRunner 我刚选了 10,因为他给出的数字不到 10 位。 .ToString("D10") 生成一个长度为 10 位的字符串,左侧填充 0,因此 1.ToString("D10") 创建一个字符串“0000000001”,然后可以对其进行排序。
【解决方案2】:

你想要的是Natural sort

我曾经为此写过一些代码:

public static class NaturalCompare
{
    public static int Compare(string first, string second, StringComparison comparison = StringComparison.Ordinal)
    {
        if (string.Compare(first, second, comparison) == 0)
        {
            return 0;
        }

        if (first == null)
        {
            return -1;
        }

        if (second == null)
        {
            return 1;
        }

        DateTime d1, d2;

        if (DateTime.TryParse(first, out d1) && DateTime.TryParse(second, out d2))
        {
            return d1.CompareTo(d2);
        }

        var pos1 = 0;
        var pos2 = 0;

        int result;
        do
        {
            bool isNum1, isNum2;

            var part1 = GetNext(first, ref pos1, out isNum1);
            var part2 = GetNext(second, ref pos2, out isNum2);

            if (isNum1 && isNum2)
            {
                result = long.Parse(part1).CompareTo(long.Parse(part2));
            }
            else
            {
                result = String.Compare(part1, part2, comparison);
            }
        } while (result == 0 && pos1 < first.Length && pos2 < second.Length);

        return result;
    }

    public static int CompareToNatural(this string first, string second, StringComparison comparison = StringComparison.Ordinal)
    {
        return Compare(first, second, comparison);
    }

    public static IOrderedEnumerable<TSource> OrderByNatural<TSource>(this IEnumerable<TSource> source, Func<TSource, string> keySelector)
    {
        return source.OrderBy(keySelector, new NatComparer());
    }

    public static IOrderedEnumerable<TSource> OrderByNaturalDescending<TSource>(this IEnumerable<TSource> source, Func<TSource, string> keySelector)
    {
        return source.OrderByDescending(keySelector, new NatComparer());
    }

    private sealed class NatComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return NaturalCompare.Compare(x, y);
        }
    }

    private static string GetNext(string s, ref int index, out bool isNumber)
    {
        if (index >= s.Length)
        {
            isNumber = false;
            return "";
        }

        isNumber = char.IsDigit(s[index]);

        var start = index;
        while (index < s.Length && char.IsDigit(s[index]) == isNumber)
        {
            index++;
        }
        return s.Substring(start, index - start);
    }
}

【讨论】:

    【解决方案3】:

    几个月前我写了这个IComparer 实现来处理这样的事情。我认为默认情况下它会执行您想要的操作,尽管它是为处理更复杂的情况而构建的,其中数字/字母组由也需要原子排序的分隔符分隔。您应该能够根据自己的需要进行调整。

    public class SemanticComparer : IComparer<string>
    {
        private static Regex _splitter = new Regex("\\W+");
    
        public int Compare(string x, string y)
        {
            string[] partsX = _splitter.Split(x);
            string[] partsY = _splitter.Split(y);
    
            int shortest = Math.Min(partsX.Length, partsY.Length);
    
            for (int index = 0; index < shortest; index++)
            {
                int intX, intY;
                int result;
    
                if (int.TryParse(partsX[index], out intX) && int.TryParse(partsY[index], out intY))
                {
                    result = intX.CompareTo(intY);
                }
                else
                {
                    result = string.Compare(partsX[index], partsY[index], StringComparison.Ordinal);
                }
    
                if (result != 0)
                {
                    return result;
                }
            }
    
            return 0;
        }
    }
    

    您可以像这样对列表进行排序:

    MyList.Sort(new SemanticComparer());
    

    【讨论】:

      【解决方案4】:

      您可以遍历所有值一次,然后使用 int.TryParse 将它们分成两个单独的列表:一个用于 int.TryParse 返回 true 的值(也称为数字),另一个用于返回的值false(非数字)。然后你可以分别对这两个列表进行排序,并在最后将它们的排序结果连接在一起。

      【讨论】:

        【解决方案5】:

        我尚未测试此代码的性能,但您可以使用比较器解决此问题

        public class ArrayItemComparer : IComparer<string>
        {
            public int Compare(string x, string y)
            {
                int xInt = 0, yInt = 0;
        
                bool parseX = int.TryParse(x, out xInt);
                bool parseY = int.TryParse(y, out yInt);
        
                if (parseX && parseY)
                {
                    return xInt.CompareTo(yInt);
                }
                else if (parseX)
                {
                    return -1;
                }
                else if (parseY)
                {
                    return 1;
                }
                else
                {
                    return x.CompareTo(y);
                }
            }
        }
        

        【讨论】:

          【解决方案6】:

          我为此创建了一个解决方案。我将list 分成两部分,然后排序和连接。请检查以下内容:

          public List<ListItem> getSortedList()
          {
              int dummy = 0;
          
              List<ListItem> list = new List<ListItem>();
              list.Add(new ListItem() { Item = "A" });
              list.Add(new ListItem() { Item = "B" });
              list.Add(new ListItem() { Item = "C" });
              list.Add(new ListItem() { Item = "111" });
              list.Add(new ListItem() { Item = "11" });
              list.Add(new ListItem() { Item = "123" });
              list.Add(new ListItem() { Item = "1" });
              list.Add(new ListItem() { Item = "42" });
              list.Add(new ListItem() { Item = "5" });
          
              var listNumber = list.Where(m => int.TryParse(m.Item, out dummy)).ToList().OrderBy(m => Convert.ToInt16(m.Item)).ToList();
              var listString = list.Where(m => !int.TryParse(m.Item, out dummy)).ToList().OrderBy(m => m.Item).ToList();
          
              var sortedList = listNumber.Concat(listString).ToList();
          
              return sortedList;
          }
          

          您可以在DotNetFiddle 中运行它。

          【讨论】:

            【解决方案7】:

            假设您从一组字符串开始,一个简单的比较器应该可以完成这项工作:

            public class Comparer : IComparer<string>
            {
                public int Compare(string a, string b) 
                {
                    int ia = 0;
                    int ib = 0;
                    var aIsInt = int.TryParse(a,out ia);
                    var bIsInt = int.TryParse(b,out ib);
                    if (aIsInt == bIsInt)
                    {
                        if (aIsInt)
                        {
                            return ia.CompareTo(ib);
                        }
                        else
                        {
                            return a.CompareTo(b);
                        }
                    }
                    return aIsInt ? -1 : 1;
                }
            }
            

            Here's a fiddle

            【讨论】:

              【解决方案8】:

              在“OrderBy”中使用 Regex.Replace 是一行(相当)简单的代码。请注意,数字“3”必须是等于或大于最长字符串的数字,因此其他任何人都可以根据需要增加。

              using System.Text.RegularExpressions;
              
              string[] yourStrings = new string[] { "A", "B", "C", "111", "11", "123", "1", "42", "5" };
              
              foreach (var item in yourStrings.OrderBy(x => Regex.Replace(x, @"\d+", i => 
              i.Value.PadLeft(3, '0'))))
              {
                  Response.Write(item + "\n");
              }
              

              【讨论】:

                猜你喜欢
                • 2021-12-20
                • 1970-01-01
                • 2023-01-12
                • 2016-12-24
                • 2021-11-28
                • 2022-01-01
                • 1970-01-01
                • 2019-09-02
                • 2011-02-17
                相关资源
                最近更新 更多