【问题标题】:Increasing performance in custom sorting of a string array提高字符串数组自定义排序的性能
【发布时间】:2012-03-04 07:17:26
【问题描述】:

我试图找到一种有效的方法来根据数组的每个字符串元素中的数值对字符串数组进行排序。我目前正在使用 Array.Sort(array, customComparer) 静态方法(快速排序),我的自定义比较器类(按降序排序)是:

class StringComparer : IComparer<string>
{
    public int Compare(string a, string b)
    {
        string s1 = a;
        string s2 = b;

        Match matchA = Regex.Match(s1, @"\d+$");
        Match matchB = Regex.Match(s2, @"\d+$");

        long numberA = long.Parse(matchA.Value);
        long numberB = long.Parse(matchB.Value);

        if (numberB - numberA < 0)
        {
            return -1;
        }
        else 
        {
            return 1;
        }
    }
}

这很好用,但有时排序需要花费太多时间,在 2.4Ghz 处理器上,一个包含 100 000 个字符串的数组需要花费超过一分钟的时间。我想知道是否有更有效的方法来完成同样的事情。例如,实现不同的排序算法或采用另一种方法,例如使用字典并对值进行排序(该值是字符串的数字部分)。有什么建议?提前致谢!

【问题讨论】:

  • “使用字典并按值排序”听起来很有希望。你试过了吗?
  • 你可以试试Radix Sort,它针对这种排序:“通过按具有相同有效位置和值的单个数字对键进行分组,使用整数键对数据进行排序”
  • @Luiggi Mendoza:排序算法不是瓶颈。

标签: c# arrays performance algorithm sorting


【解决方案1】:

首先,您不必要地一遍又一遍地解析相同的字符串(都与正则表达式匹配,然后解析匹配项)。相反,将您拥有的内容封装到自定义类型中,这样您只需解析一次。

public class FooString {
    private readonly string foo;
    private readonly long bar;

    public FooString(string foo) {
        this.foo = foo;
        Match match = Regex.Match(foo, @"\d+$");
        this.bar = Int64.Parse(match.Value);
    }

    public string Foo { get { return this.foo; } }
    public long Bar { get { return this.bar; } }
}

我什至会在这个类中添加一个Contract.Requires,说明foo 必须满足正则表达式。

其次,您有一个IComparer&lt;T&gt;,它在T 的某些值上死掉(在您的情况下,strings 与正则表达式不匹配并且无法解析为long)。这通常是个坏主意。

所以,为FooString 做比较器:

public FooStringComparer : IComparer<FooString> {
    public int Compare(FooString a, FooString b) {
        Contract.Requires(a != null);
        Contract.Requires(b != null);
        return a.Bar.CompareTo(b.Bar);
    }
}

现在,您的排序将非常快,因为您已经停止了一遍又一遍地解析相同的字符串。

【讨论】:

  • 你的方法很棒!我用 100 000 个元素进行了测试,排序过程只用了几秒钟!这过去需要一分钟多的时间!现在唯一需要时间的部分是启动 CustomStrings 数组,我使用 'CustomString[] arrayOfCustomStrings = new CustomString[matchCollection.Count];'这需要大约 12 秒来创建数组,但之后它会在一两秒内完成排序。只是为了记录,我不得不将 'this.bar = Int64.Parse(match)' 更改为 'this.bar = Int64.Parse(match.value);'这很有帮助。谢谢杰森!
【解决方案2】:

您现在正在执行正则表达式 O(n log n) 次。

考虑对所有字符串循环一次,提取数值并将其添加到SortedDictionary&lt;long, string&gt;

这只需要 O(n) 次 Reg 表达式的执行。其余的排序应该是可比的。

【讨论】:

    【解决方案3】:

    使用Compiled 选项仅创建一次Regex。这将提高速度。

    class StringComparer : IComparer<string>
    {
        private static Regex _regex = new Regex(@"\d+$", RegexOptions.Compiled);
    
        public int Compare(string a, string b)
        {
            long numberA = Int64.Parse(_regex.Match(a).Value);
            long numberB = Int64.Parse(_regex.Match(b).Value);
            return numberA.CompareTo(numberB);
        }
    }
    

    【讨论】:

    【解决方案4】:

    您正在解析每次比较的值。我建议你解析一次,得到一个字符串/长对,排序,然后提取字符串部分。

    请注意,您现有的代码有一个错误:对于两个比较相等的字符串,它将永远返回 0。

    这是使用 LINQ 的另一种方法(不是就地排序,但很简单。)

    var sorted = unsorted.OrderBy(x => long.Parse(Regex.Match(x, @"\d+$").Value));
                         .ToList();
    

    OrderBy 进行一次获取密钥,然后比较密钥。)

    【讨论】:

    • LINQ 听起来不错,我会测试一下。但是,我的目标是 .NET 2.0。谢谢!
    • @cake:您可以使用 LINQBridge 或类似的东西来使用 .NET 2.0 中的 LINQ to Objects。 (将来,如果您需要一个非常旧版本的框架,那么在问题中这样说会很有帮助。)您基本上可以做同样的事情:一次计算所有键,然后排序和那些。
    • 很抱歉忘记提及所需的版本。我选择了一个自定义字符串类,它按照 Jason 的建议进行比较非常快。我不知道 LinqBridge,但我一定会研究它并进行比较。谢谢!
    • 刚刚尝试过 LinqBridge,效果很好,但是解析和正则表达式仍然太多,所以代码仍然很慢。谢谢你提到 LinqBridge,我在别的地方用过。
    • @cake:好吧,每个值只会进行一次解析和正则表达式......所以它不应该比接受的答案慢。
    猜你喜欢
    • 2020-05-29
    • 1970-01-01
    • 2019-08-30
    • 1970-01-01
    • 2019-10-06
    • 2013-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多