【问题标题】:Order string like numeric [duplicate]订单字符串,如数字 [重复]
【发布时间】:2019-08-20 10:11:21
【问题描述】:

我想对有时包含数值的字符串列表进行排序

我的清单是这样的:

  • 卧室1
  • 卧室2
  • 卧室 10
  • 浴室 1
  • 浴室 2
  • 浴室 10
  • 1 灯
  • 1 篇论文

如果我只使用 orderby: roomList.OrderBy(x => x.Name) 我得到了这份清单:

  • 1 灯
  • 1 篇论文
  • 浴室 1
  • 浴室 10
  • 浴室 2
  • 卧室1
  • 卧室 10
  • 卧室2

是否有可能得到这样的列表?

  • 1 灯
  • 1 篇论文
  • 浴室 1
  • 浴室 2
  • 浴室 10
  • 卧室1
  • 卧室2
  • 卧室 10

所有列表元素都不包含数字,列表大约有 1500 行长

我已尝试使用此代码,它适用于包含数字的元素,但不仅仅适用于字符串元素:

public class SemiNumericComparer : IComparer<string>
    {
        public int Compare(string s1, string s2)
        {
            if (IsNumeric(s1) && IsNumeric(s2))
            {
                if (Convert.ToInt32(s1) > Convert.ToInt32(s2)) return 1;
                if (Convert.ToInt32(s1) < Convert.ToInt32(s2)) return -1;
                if (Convert.ToInt32(s1) == Convert.ToInt32(s2)) return 0;
            }

            if (IsNumeric(s1) && !IsNumeric(s2))
                return -1;

            if (!IsNumeric(s1) && IsNumeric(s2))
                return 1;

            return string.Compare(s1, s2, true);
        }

        public static bool IsNumeric(object value)
        {
            try
            {
                int i = Convert.ToInt32(value.ToString());
                return true;
            }
            catch (FormatException)
            {
                return false;
            }
        }
    }

【问题讨论】:

  • 你想用“2 Light”和“10 Light”发生什么? “浴室蓝”呢?
  • 不确定这是否适合您,但您可以编辑字符串以使用前导零。 Bathroom 001Bathroom 002 等。或者,在运行时将数字部分转换为该格式,然后排序。
  • 两者都应该在“边缘情况”场景中有所不同。如果您有具体的问题,请在您的数据上尝试一个。但我很确定,如果它不是一个完全任意的规则,那么它要么是一个骗子,要么是一个内置函数。自从我们开始拥有计算机以来,人类一直在要求有序的清单。我希望开发人员在后天找到一个简单的解决方案。

标签: c# linq


【解决方案1】:

您正在寻找“自然排序”算法

例如: https://www.codeproject.com/Articles/22517/Natural-Sort-Comparer

【讨论】:

    【解决方案2】:
            [SuppressUnmanagedCodeSecurity]
            internal static class SafeNativeMethods
            {
                [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
                public static extern int StrCmpLogicalW(string p1, string p2);
            }
    
            public sealed class StringComparer : IComparer<string>
            {
                public int Compare(string a, string b)
                {
                    return SafeNativeMethods.StrCmpLogicalW(a, b);
                }
            }
    

    现在您可以在 OrderBy 上使用 StringComparer 了:

                List<string> str = new List<string>
                {
                    "Bedroom 1",
                    "Bedroom 2",
                    "Bedroom 10",
                    "Bathroom 1",
                    "Bathroom 2",
                    "Bathroom 10",
                    "1 Light",
                    "1 Paper"
                };
                str = str.OrderBy(x => x, new StringComparer()).ToList();
    

    【讨论】:

    • 这仅适用于 Windows 环境。此外,如果可以(很容易)避免,我不建议引用内核 dll。
    • 除了@discy 提到的内容之外,值得注意的是,StrCmpLogicalW 在不同的 Windows 版本上的行为可能不同(这可能是可取的,也可能不是可取的,具体取决于排序是否应与程序是否运行的 Windows 版本)。
    【解决方案3】:

    尝试以下自定义 IComparable :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    
    namespace ConsoleApplication107
    {
        class Program
        {
            static void Main(string[] args)
            {
                List<string> input = new List<string>() { "1 Light", "1 Paper", "Bathroom 1", "Bathroom 2", "Bathroom 10", "Bedroom 1", "Bedroom 2", "Bedroom 10" };
    
                List<string> results = input.Select(x => new { s = x, order = new Order(x) }).OrderBy(x => x.order).Select(x => x.s).ToList();
    
            }
        }
        public class Order : IComparable<Order>
        {
            List<string> split { get; set; }
    
            public Order(string line)
            {
                split = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            }
    
            public int CompareTo(Order other)
            {
    
                int min = Math.Min(split.Count, other.split.Count);
                int thisNumber = 0;
                int otherNumber = 0;
                for (int i = 0; i < min; i++)
                {
                    if (split[i] != other.split[i])
                    {
                        if ((int.TryParse(split[i], out thisNumber)))
                        {
                            if ((int.TryParse(other.split[i], out otherNumber)))
                            {
                                return thisNumber.CompareTo(otherNumber); //both numbers
                            }
                            else
                            {
                                return -1; // this is number other is string : this comes first
                            }
                        }
                        else
                        {
                            if ((int.TryParse(other.split[i], out otherNumber)))
                            {
                                return 1; //other is number this is string : other comes first
                            }
                            else
                            {
                                return split[i].CompareTo(other.split[i]);
                            }
                        }
                    }
                }
    
                return split.Count.CompareTo(other.split.Count);
    
            }
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2019-11-09
      • 1970-01-01
      • 1970-01-01
      • 2012-08-02
      • 2012-12-05
      • 1970-01-01
      • 1970-01-01
      • 2013-08-20
      • 2013-08-04
      相关资源
      最近更新 更多