2012-02-12 40 views
3

我正试图找到一种有效的方法来基于数组的每个字符串元素中的数值对字符串数组进行排序。我目前使用的Array.Sort(阵列,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个字符串的阵列需要一分多秒。我想知道是否有更有效的方法来实现这一点。例如,实现不同的排序算法或采用另一种方法,如使用字典和对值进行排序(值是字符串的数字部分)。有什么建议么?提前致谢!

+1

“使用字典并对值进行排序”听起来很有希望。你试过了吗? – 2012-02-12 18:04:51

+0

你可以尝试[基数排序](http://en.wikipedia.org/wiki/Radix_sort),它的目标是这种排序:“用整数键对数据进行排序,通过将键分成共享相同重要位置的个别数字和值“ – 2012-02-12 18:22:21

+1

@Luiggi门多萨:排序算法不是瓶颈。 – jason 2012-02-12 19:04:58

回答

2

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

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<T>上的T某些值死(在你的情况下,不匹配正则表达式,并且不能被解析到一个longstring S)。这通常是一个坏主意。

因此,使比较器的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); 
    } 
} 

现在,你的排序将是极快的,因为你已经停止了一遍又一遍解析相同的字符串。

+0

你的方法非常棒!我测试了10万个元素,排序过程仅需几秒钟!这以前需要一分多钟时间!现在需要时间的唯一部分是启动CustomString的数组,我使用'CustomString [] arrayOfCustomStrings = new CustomString [matchCollection.Count];'这需要大约12秒来创建数组,但在此之后,它会在一两秒内完成排序。只是为了记录,我不得不将'this.bar = Int64.Parse(match)'改为'this.bar = Int64.Parse(match.value);'这非常有帮助。谢谢杰森! – cake 2012-02-13 02:22:00

5

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

请注意,您现有的代码有一个bug:它会从不返回0,两个字符串比较相等。

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

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

(一次OrderBy项目拿到钥匙,然后比较键。)

+0

LINQ听起来不错,我会测试它。但是,我的目标是.NET 2.0。谢谢! – cake 2012-02-12 21:06:07

+0

@cake:您可以使用LINQBridge或类似的东西在.NET 2.0中使用LINQ to Objects。 (将来,如果你需要一个非常旧的框架版本,在这个问题中这样说会很有帮助。)你可以基本上做同样的事情:一次完成所有的键,然后排序和那些。 – 2012-02-12 21:21:48

+0

抱歉忘记提及所需版本。我选择了一个自定义的字符串类,它的速度与Jason建议的相比非常快。我不知道LinqBridge,但我一定会考虑它并进行比较。谢谢! – cake 2012-02-13 02:38:30

3

您现在正在执行Regexes O(n log n)次。

考虑以上所有字符串循环一次,提取数值和它添加到SortedDictionary<long, string>

这仅需要O(N)的注册表达的处决。其余的分类应该是可比的。

1

仅使用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); 
    } 
} 
+0

默认情况下,正则表达式被编译和缓存。这不应该有任何区别。 – 2012-02-12 18:26:16

+0

这不是瓶颈。 – jason 2012-02-12 18:27:46

+0

@HenkHolterman:OP使用'Regex.Match(s1,@“\ d + $”);'不会被编译。然而,当模式在构造函数中传递时,你指出正则表达式将被默认编译。 – 2012-02-12 18:48:38