2012-02-12 68 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