当字符串为数字时,如何在考虑值的同时按字母顺序对字符串进行排序?

Posted

技术标签:

【中文标题】当字符串为数字时,如何在考虑值的同时按字母顺序对字符串进行排序?【英文标题】:How do I sort strings alphabetically while accounting for value when a string is numeric? 【发布时间】:2022-01-17 15:49:17 【问题描述】:

我正在尝试对一个字符串数字数组进行排序,我希望它们按数字排序。

问题是我无法将数字转换为整数

代码如下:

string[] things= new string[]  "105", "101", "102", "103", "90" ;

foreach (var thing in things.OrderBy(x => x))

    Console.WriteLine(thing);

输出

101, 102, 103, 105, 90

我想要:

90, 101, 102, 103, 105

编辑: 输出不能是090, 101, 102...

将代码示例更新为“things”而不是“sizes”。数组可以是这样的:

string[] things= new string[]  "paul", "bob", "lauren", "007", "90" ;

这意味着它需要按字母顺序和数字排序:

007, 90, bob, lauren, paul

【问题讨论】:

为什么不能将它们转换为 int? “sizes”可以是“name”之类的其他内容。代码示例只是简化了。 会有负数吗?它们都会是整数吗?整数的范围是多少? "things" 可以是任何类型的字符串。我希望将列表按逻辑排序给非计算机知识的人。负数应该在正数之前。就字符串长度而言,不会超过 100 个字符。 你想走多远? image10 应该在 image2 之后吗? January 应该在February 之前出现吗? 【参考方案1】:

将自定义比较器传递给 OrderBy。 Enumerable.OrderBy 会让你指定任何你喜欢的比较器。

这是一种方法:

void Main()

    string[] things = new string[]  "paul", "bob", "lauren", "007", "90", "101";

    foreach (var thing in things.OrderBy(x => x, new SemiNumericComparer()))
        
        Console.WriteLine(thing);
    



public class SemiNumericComparer: IComparer<string>

    /// <summary>
    /// Method to determine if a string is a number
    /// </summary>
    /// <param name="value">String to test</param>
    /// <returns>True if numeric</returns>
    public static bool IsNumeric(string value)
    
        return int.TryParse(value, out _);
    

    /// <inheritdoc />
    public int Compare(string s1, string s2)
    
        const int S1GreaterThanS2 = 1;
        const int S2GreaterThanS1 = -1;

        var IsNumeric1 = IsNumeric(s1);
        var IsNumeric2 = IsNumeric(s2);

        if (IsNumeric1 && IsNumeric2)
        
            var i1 = Convert.ToInt32(s1);
            var i2 = Convert.ToInt32(s2);

            if (i1 > i2)
            
                return S1GreaterThanS2;
            

            if (i1 < i2)
            
                return S2GreaterThanS1;
            

            return 0;
        

        if (IsNumeric1)
        
            return S2GreaterThanS1;
        

        if (IsNumeric2)
        
            return S1GreaterThanS2;
        

        return string.Compare(s1, s2, true, CultureInfo.InvariantCulture);
    

【讨论】:

对于给定的输入,这会产生与 Recursive 的答案相同的结果,其中涉及 PadLeft()。我假设您的输入实际上比此示例显示的更复杂,在这种情况下,自定义比较器是可行的方法。 干杯。该解决方案有效,并且似乎是一种易于阅读和干净的实施方式。 +1 向我展示您可以在 OrderBy 上使用 IComparer :) IsNumeric 方法不好,异常驱动的编码总是不好的。请改用int.TryParse。用一个大列表尝试你的代码,这将需要很长时间。 如果有帮助,我在这个版本中添加了一个扩展here,它增加了对单词排序的支持。对于我的需要,空格分割就足够了,我几乎不需要担心混合使用的单词(例如 test12 vs test3), @NeanDerThal 我敢肯定,如果您正在调试或正在访问 Exception 对象,那么在循环中处理大量异常只会很慢/很糟糕。【参考方案2】:

只需用零填充到相同的长度:

int maxlen = sizes.Max(x => x.Length);
var result = sizes.OrderBy(x => x.PadLeft(maxlen, '0'));

【讨论】:

+1 用于简单的解决方案,挑剔会(已经在编辑中完成,很好) 好主意,但下一个问题是我需要显示这些值,因此“90”需要是“90”,而不是“090” @sf:试试看,你可能会喜欢这个结果。请记住,订单键不是被订购的东西。如果我说按姓氏订购客户列表,那么我会得到一个客户列表,而不是姓氏列表。如果您说通过转换后的字符串对字符串列表进行排序,那么结果是原始字符串的有序列表,而不是转换后的字符串。 我必须添加“sizes = sizes.OrderBy(...)”才能完成这项工作。这是正常的还是应该编辑答案? @gorgabal:通常重新分配给sizes 也不起作用,因为结果是不同的类型。答案有点简略,因为第二行将结果显示为表达式,但读者可以用它来做点什么。我添加了另一个变量赋值以使其更加清晰。【参考方案3】:

值是一个字符串

List = List.OrderBy(c => c.Value.Length).ThenBy(c => c.Value).ToList();

作品

【讨论】:

这个答案是我最喜欢的。 谢谢,我刚刚发现退出了“ThenBy”方法。 这是最好的答案。它有效,是一个很好的学习方法。谢谢!! 但这会像这样混合字母字符串:"b", "ab", "101", "103", "bob", "abcd". 你的例子很糟糕,因为 new List "01", "2" .OrderBy(c => c.Length).ThenBy(c => c).ToList() ;以错误的顺序排序列表“2”,“01”,而不是作者想要的“01”,“2”。【参考方案4】:

还有,这个怎么样……

string[] sizes = new string[]  "105", "101", "102", "103", "90" ;

var size = from x in sizes
           orderby x.Length, x
           select x;

foreach (var p in size)

    Console.WriteLine(p);

【讨论】:

呵呵,我真的很喜欢这个——非常聪明。抱歉,如果我没有提供完整的初始数据集 这就像上面的 pad 选项只是更好的 IMO。 var size = sizes.OrderBy(x => x.Length).ThenBy(x => x); 但这会混合这样的字母字符串:"b", "ab", "101", "103", "bob", "abcd".【参考方案5】:

Windows StrCmpLogicalW 中有一个本机函数,它将在字符串中将数字作为数字而不是字母进行比较。制作一个调用该函数并将其用于比较的比较器很容易。

public class StrCmpLogicalComparer : Comparer<string>

    [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
    private static extern int StrCmpLogicalW(string x, string y);

    public override int Compare(string x, string y)
    
        return StrCmpLogicalW(x, y);
    

它甚至适用于同时包含文本和数字的字符串。这是一个示例程序,它将显示默认排序和StrCmpLogicalW 排序之间的差异

class Program

    static void Main()
    
        List<string> items = new List<string>()
        
            "Example1.txt", "Example2.txt", "Example3.txt", "Example4.txt", "Example5.txt", "Example6.txt", "Example7.txt", "Example8.txt", "Example9.txt", "Example10.txt",
            "Example11.txt", "Example12.txt", "Example13.txt", "Example14.txt", "Example15.txt", "Example16.txt", "Example17.txt", "Example18.txt", "Example19.txt", "Example20.txt"
        ;

        items.Sort();

        foreach (var item in items)
        
            Console.WriteLine(item);
        

        Console.WriteLine();

        items.Sort(new StrCmpLogicalComparer());

        foreach (var item in items)
        
            Console.WriteLine(item);
        
        Console.ReadLine();
    

哪个输出

Example1.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example2.txt
Example20.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt

Example1.txt
Example2.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example20.txt

【讨论】:

我希望在 C# 中使用系统库更容易 这本来是完美的,但不幸的是它不能处理负数。 -1 0 10 2 排序为0 -1 2 10【参考方案6】:

试试这个

sizes.OrderBy(x => Convert.ToInt32(x)).ToList<string>();

注意: 当所有字符串都可以转换为 int 时,这将很有帮助.....

【讨论】:

这有点将字符串转换为int。 "sizes" 也可以是非数字 对于“LINQ to SQL”不要忘记前面的ToList() => sizes.ToList().OrderBy(x =&gt; Convert.ToInt32(x))【参考方案7】:

您说不能将数字转换为int,因为数组可以包含无法转换为int的元素,但是尝试没有坏处:

string[] things = new string[]  "105", "101", "102", "103", "90", "paul", "bob", "lauren", "007", "90" ;
Array.Sort(things, CompareThings);

foreach (var thing in things)
    Debug.WriteLine(thing);

然后像这样比较:

private static int CompareThings(string x, string y)

    int intX, intY;
    if (int.TryParse(x, out intX) && int.TryParse(y, out intY))
        return intX.CompareTo(intY);

    return x.CompareTo(y);

输出:007、90、90、101、102、103、105、鲍勃、劳伦、保罗

【讨论】:

顺便说一句,为了简单起见,我使用了 Array.Sort,但您可以在 IComparer 中使用相同的逻辑并使用 OrderBy。 这个解决方案似乎比使用 IComparer 更快(我的观点)。 15000 结果,我觉得这会产生大约第二个差异。【参考方案8】:

如果字符串中有一些数字,我想这会更好。 希望它会有所帮助。

PS:我不确定性能或复杂的字符串值,但它工作得很好,如下所示:

lorem ipsum lorem ipsum 1 lorem ipsum 2 lorem ipsum 3 ... lorem ipsum 20 lorem ipsum 21

public class SemiNumericComparer : IComparer<string>

    public int Compare(string s1, string s2)
    
        int s1r, s2r;
        var s1n = IsNumeric(s1, out s1r);
        var s2n = IsNumeric(s2, out s2r);

        if (s1n && s2n) return s1r - s2r;
        else if (s1n) return -1;
        else if (s2n) return 1;

        var num1 = Regex.Match(s1, @"\d+$");
        var num2 = Regex.Match(s2, @"\d+$");

        var onlyString1 = s1.Remove(num1.Index, num1.Length);
        var onlyString2 = s2.Remove(num2.Index, num2.Length);

        if (onlyString1 == onlyString2)
        
            if (num1.Success && num2.Success) return Convert.ToInt32(num1.Value) - Convert.ToInt32(num2.Value);
            else if (num1.Success) return 1;
            else if (num2.Success) return -1;
        

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

    public bool IsNumeric(string value, out int result)
    
        return int.TryParse(value, out result);
    

【讨论】:

正是我想要的。谢谢!【参考方案9】:

本网站讨论字母数字排序,并将按逻辑意义而非 ASCII 意义对数字进行排序。它还考虑了它周围的 alpha:

http://www.dotnetperls.com/alphanumeric-sorting

示例:

C:/TestB/333.jpg 11 C:/TestB/33.jpg 1 C:/TestA/111.jpg 111F C:/TestA/11.jpg 2 C:/TestA/1.jpg 111D 22 111Z C:/TestB/03.jpg
1 2 11 22 111D 111F 111Z C:/TestA/1.jpg C:/TestA/11.jpg C:/TestA/111.jpg C:/TestB/03.jpg C:/TestB/33.jpg C:/TestB/333.jpg

代码如下:

class Program

    static void Main(string[] args)
    
        var arr = new string[]
        
           "C:/TestB/333.jpg",
           "11",
           "C:/TestB/33.jpg",
           "1",
           "C:/TestA/111.jpg",
           "111F",
           "C:/TestA/11.jpg",
           "2",
           "C:/TestA/1.jpg",
           "111D",
           "22",
           "111Z",
           "C:/TestB/03.jpg"
        ;
        Array.Sort(arr, new AlphaNumericComparer());
        foreach(var e in arr) 
            Console.WriteLine(e);
        
    


public class AlphaNumericComparer : IComparer

    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;
        

        int len1 = s1.Length;
        int len2 = s2.Length;
        int marker1 = 0;
        int marker2 = 0;

        // Walk through two the strings with two markers.
        while (marker1 < len1 && marker2 < len2)
        
            char ch1 = s1[marker1];
            char ch2 = s2[marker2];

            // Some buffers we can build up characters in for each chunk.
            char[] space1 = new char[len1];
            int loc1 = 0;
            char[] space2 = new char[len2];
            int loc2 = 0;

            // Walk through all following characters that are digits or
            // characters in BOTH strings starting at the appropriate marker.
            // Collect char arrays.
            do
            
                space1[loc1++] = ch1;
                marker1++;

                if (marker1 < len1)
                
                    ch1 = s1[marker1];
                
                else
                
                    break;
                
             while (char.IsDigit(ch1) == char.IsDigit(space1[0]));

            do
            
                space2[loc2++] = ch2;
                marker2++;

                if (marker2 < len2)
                
                    ch2 = s2[marker2];
                
                else
                
                    break;
                
             while (char.IsDigit(ch2) == char.IsDigit(space2[0]));

            // If we have collected numbers, compare them numerically.
            // Otherwise, if we have strings, compare them alphabetically.
            string str1 = new string(space1);
            string str2 = new string(space2);

            int result;

            if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
            
                int thisNumericChunk = int.Parse(str1);
                int thatNumericChunk = int.Parse(str2);
                result = thisNumericChunk.CompareTo(thatNumericChunk);
            
            else
            
                result = str1.CompareTo(str2);
            

            if (result != 0)
            
                return result;
            
        
        return len1 - len2;
    

【讨论】:

【参考方案10】:

这似乎是一个奇怪的要求,值得一个奇怪的解决方案:

string[] sizes = new string[]  "105", "101", "102", "103", "90" ;

foreach (var size in sizes.OrderBy(x => 
    double sum = 0;
    int position = 0;
    foreach (char c in x.ToCharArray().Reverse()) 
        sum += (c - 48) * (int)(Math.Pow(10,position));
        position++;
    
    return sum;
))


    Console.WriteLine(size);

【讨论】:

我的意思当然是 0x30。此外,该数组仍然可以包含一个非数字字符串,解决方案将产生有趣的结果。 请注意,-48 与否绝对不会改变,我们可以直接使用 char 的整数值,所以如果它打扰您,请删除 -48... char值是0x30,如果转换成int,还是0x30,不是数字0。 唯一转换为整数的是从 Math.Pow 返回的双精度数 femaref 它是否为零无关紧要,十进制系统会处理它,如果你愿意,它可能是一个Đ,唯一重要的是数字在升序中字符集,小于 10【参考方案11】:

Jeff Paulsen 给出的答案是正确的,但 Comprarer 可以简化为:

public class SemiNumericComparer: IComparer<string>

    public int Compare(string s1, string s2)
    
        if (IsNumeric(s1) && IsNumeric(s2))
          return Convert.ToInt32(s1) - Convert.ToInt32(s2)

        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)
    
        int result;
        return Int32.TryParse(value, out result);
    

这是有效的,因为唯一检查Comparer 的结果是结果是否更大、更小或等于零。可以简单地从另一个值中减去值,而不必处理返回值。

此外,IsNumeric 方法不必使用try 块,并且可以从TryParse 中受益。

对于那些不确定的人: 此比较器将对值进行排序,以便始终将非数字值附加到列表的末尾。如果一开始就想要它们,则必须交换第二个和第三个 if 块。

【讨论】:

由于调用 TryParse 方法可能有一些开销,我会先将 s1 和 s2 的 isNumeric 值存储到布尔值中,然后对它们进行比较。这样他们就不会被多次评估。【参考方案12】:
public class NaturalSort: IComparer<string>

          [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
          public static extern int StrCmpLogicalW(string x, string y);

          public int Compare(string x, string y)
          
                 return StrCmpLogicalW(x, y);
          

arr = arr.OrderBy(x => x, new NaturalSort()).ToArray();

我需要它的原因是要在文件名以数字开头的目录中归档:

public static FileInfo[] GetFiles(string path)

  return new DirectoryInfo(path).GetFiles()
                                .OrderBy(x => x.Name, new NaturalSort())
                                .ToArray();

【讨论】:

【参考方案13】:

试试这个:

string[] things= new string[]  "105", "101", "102", "103", "90" ;

int tmpNumber;

foreach (var thing in (things.Where(xx => int.TryParse(xx, out tmpNumber)).OrderBy(xx =>     int.Parse(xx))).Concat(things.Where(xx => !int.TryParse(xx, out tmpNumber)).OrderBy(xx => xx)))

    Console.WriteLine(thing);

【讨论】:

【参考方案14】:

扩展 Jeff Paulsen 的答案。我想确保字符串中有多少个数字或字符组无关紧要:

public class SemiNumericComparer : IComparer<string>

    public int Compare(string s1, string s2)
    
        if (int.TryParse(s1, out var i1) && int.TryParse(s2, out var i2))
        
            if (i1 > i2)
            
                return 1;
            

            if (i1 < i2)
            
                return -1;
            

            if (i1 == i2)
            
                return 0;
            
        

        var text1 = SplitCharsAndNums(s1);
        var text2 = SplitCharsAndNums(s2);

        if (text1.Length > 1 && text2.Length > 1)
        

            for (var i = 0; i < Math.Max(text1.Length, text2.Length); i++)
            

                if (text1[i] != null && text2[i] != null)
                
                    var pos = Compare(text1[i], text2[i]);
                    if (pos != 0)
                    
                        return pos;
                    
                
                else
                
                    //text1[i] is null there for the string is shorter and comes before a longer string.
                    if (text1[i] == null)
                    
                        return -1;
                    
                    if (text2[i] == null)
                    
                        return 1;
                    
                
            
        

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

    private string[] SplitCharsAndNums(string text)
    
        var sb = new StringBuilder();
        for (var i = 0; i < text.Length - 1; i++)
        
            if ((!char.IsDigit(text[i]) && char.IsDigit(text[i + 1])) ||
                (char.IsDigit(text[i]) && !char.IsDigit(text[i + 1])))
            
                sb.Append(text[i]);
                sb.Append(" ");
            
            else
            
                sb.Append(text[i]);
            
        

        sb.Append(text[text.Length - 1]);

        return sb.ToString().Split(' ');
    

在修改它以处理文件名之后,我还从 SO Page 中获取了 SplitCharsAndNums。

【讨论】:

当您想要对看起来像“10A、11A、James、Mike、Amanada”的项目进行排序时,上述方法有效【参考方案15】:

短 IComparer 类的示例。

    如果两个字符串参数都可以转换为整数,那么参数 解析为整数并进行比较 如果只有一个参数可以转换为整数,那么整数就是 优先(具有较低的值)并插入到字符串之前。 如果没有一个参数可以转换为整数,那么普通 使用字符串比较。

代码:

public class CompareIntegerStrings : IComparer<string>
    
        public int Compare(string x, string y)
        
            if (int.TryParse(x, out int xOut) && int.TryParse(y, out int yOut))
                return xOut.CompareTo(yOut);
            else if (int.TryParse(x, out _))
                return -1;
            else if (int.TryParse(y, out _))
                return 1;
            else
                return x.CompareTo(y);
    
        
    

在这个例子中

List<string> intStrings = new List<string>  "01","0022","abba", "11", "deep purple", "02", ;
List<string> orderedIntStrings = intStrings.OrderBy(i=>i,new CompareIntegerStrings()).ToList();

有序列表 orderedIntString 是 "01","02","11","0022","abba","deep Purple"。

【讨论】:

【参考方案16】:
Try this out..  



  string[] things = new string[]  "paul", "bob", "lauren", "007", "90", "-10" ;

        List<int> num = new List<int>();
        List<string> str = new List<string>();
        for (int i = 0; i < things.Count(); i++)
        

            int result;
            if (int.TryParse(things[i], out result))
            
                num.Add(result);
            
            else
            
                str.Add(things[i]);
            


        

现在对列表进行排序并将它们合并回来...

        var strsort = from s in str
                      orderby s.Length
                      select s;

        var numsort = from n in num
                     orderby n
                     select n;

        for (int i = 0; i < things.Count(); i++)
        

         if(i < numsort.Count())
             things[i] = numsort.ElementAt(i).ToString();
             else
             things[i] = strsort.ElementAt(i - numsort.Count());               
               

我只是试图在这个有趣的问题上做出贡献......

【讨论】:

【参考方案17】:

我的首选解决方案(如果所有字符串都是数字):

// Order by numerical order: (Assertion: all things are numeric strings only) 
foreach (var thing in things.OrderBy(int.Parse))

    Console.Writeline(thing);

【讨论】:

【参考方案18】:
public class Test

    public void TestMethod()
    
        List<string> buyersList = new List<string>()  "5", "10", "1", "str", "3", "string" ;
        List<string> soretedBuyersList = null;

        soretedBuyersList = new List<string>(SortedList(buyersList));
    

    public List<string> SortedList(List<string> unsoredList)
    
        return unsoredList.OrderBy(o => o, new SortNumericComparer()).ToList();
    


   public class SortNumericComparer : IComparer<string>

    public int Compare(string x, string y)
    
        int xInt = 0;
        int yInt = 0;
        int result = -1;

        if (!int.TryParse(x, out xInt))
        
            result = 1;
        

        if(int.TryParse(y, out yInt))
        
            if(result == -1)
            
                result = xInt - yInt;
            
        
        else if(result == 1)
        
             result = string.Compare(x, y, true);
        

        return result;
    

【讨论】:

你能解释你的代码吗?仅代码的答案可能会被删除。 Jeff Paulsen 的帖子帮助我实施 IComparer 来解决我的疼痛问题。 .【参考方案19】:

使用 Regex.Replace 如此简单而高效。请注意,数字“3”必须是等于或大于最长字符串的数字,因此对于其他任何人,请根据需要增加。

using System.Text.RegularExpressions;

string[] things = new string[]  "105", "101", "102", "103", "90" ;

foreach (var thing in things.OrderBy(x => Regex.Replace(x, @"\d+", i => 
i.Value.PadLeft(3, '0'))))

    Console.WriteLine(thing);

【讨论】:

【参考方案20】:

尽管这是一个老问题,但我想给出一个解决方案:

string[] things= new string[]  "105", "101", "102", "103", "90" ;

foreach (var thing in things.OrderBy(x => Int32.Parse(x) )

    Console.WriteLine(thing);

哇,很简单吧? :D

【讨论】:

【参考方案21】:
namespace X

    public class Utils
    
        public class StrCmpLogicalComparer : IComparer<Projects.Sample>
        
            [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
            private static extern int StrCmpLogicalW(string x, string y);


            public int Compare(Projects.Sample x, Projects.Sample y)
            
                string[] ls1 = x.sample_name.Split("_");
                string[] ls2 = y.sample_name.Split("_");
                string s1 = ls1[0];
                string s2 = ls2[0];
                return StrCmpLogicalW(s1, s2);
            
        

    

【讨论】:

以上是关于当字符串为数字时,如何在考虑值的同时按字母顺序对字符串进行排序?的主要内容,如果未能解决你的问题,请参考以下文章

如何按字典顺序对 ArrayList 进行排序?

用单词和数字按字母顺序排列字符串

ID 按字母顺序而不是数字顺序

编程实践Golang 字符串数组排序

如何通过删除名称开头的数字按字母顺序对数据进行排序[重复]

除数字外,如何按字母顺序对对象数组进行排序?