2011-11-12 93 views
26

爲什麼不能在string上使用流利的語言?將IEnumerable <char>轉換爲字符串的最佳方法?

例如:

var x = "asdf1234"; 
var y = new string(x.TakeWhile(char.IsLetter).ToArray()); 

是不是有更好的辦法來IEnumerable<char>轉換爲string

這裏是我做了一個試驗:

class Program 
{ 
    static string input = "asdf1234"; 
    static void Main() 
    { 
    Console.WriteLine("1000 times:"); 
    RunTest(1000, input); 
    Console.WriteLine("10000 times:"); 
    RunTest(10000,input); 
    Console.WriteLine("100000 times:"); 
    RunTest(100000, input); 
    Console.WriteLine("100000 times:"); 
    RunTest(100000, "ffff57467"); 


    Console.ReadKey(); 

    } 

    static void RunTest(int times, string input) 
    { 

    Stopwatch sw = new Stopwatch(); 

    sw.Start(); 
    for (int i = 0; i < times; i++) 
    { 
     string output = new string(input.TakeWhile(char.IsLetter).ToArray()); 
    } 
    sw.Stop(); 
    var first = sw.ElapsedTicks; 

    sw.Restart(); 
    for (int i = 0; i < times; i++) 
    { 
     string output = Regex.Match(input, @"^[A-Z]+", 
     RegexOptions.IgnoreCase).Value; 
    } 
    sw.Stop(); 
    var second = sw.ElapsedTicks; 

    var regex = new Regex(@"^[A-Z]+", 
     RegexOptions.IgnoreCase); 
    sw.Restart(); 
    for (int i = 0; i < times; i++) 
    { 
     var output = regex.Match(input).Value; 
    } 
    sw.Stop(); 
    var third = sw.ElapsedTicks; 

    double percent = (first + second + third)/100; 
    double p1 = (first/percent)/ 100; 
    double p2 = (second/percent)/100; 
    double p3 = (third/percent )/100; 


    Console.WriteLine("TakeWhile took {0} ({1:P2}).,", first, p1); 
    Console.WriteLine("Regex took {0}, ({1:P2})." , second,p2); 
    Console.WriteLine("Preinstantiated Regex took {0}, ({1:P2}).", third,p3); 
    Console.WriteLine(); 
    } 
} 

結果:

1000 times: 
TakeWhile took 11217 (62.32%)., 
Regex took 5044, (28.02%). 
Preinstantiated Regex took 1741, (9.67%). 

10000 times: 
TakeWhile took 9210 (14.78%)., 
Regex took 32461, (52.10%). 
Preinstantiated Regex took 20669, (33.18%). 

100000 times: 
TakeWhile took 74945 (13.10%)., 
Regex took 324520, (56.70%). 
Preinstantiated Regex took 172913, (30.21%). 

100000 times: 
TakeWhile took 74511 (13.77%)., 
Regex took 297760, (55.03%). 
Preinstantiated Regex took 168911, (31.22%). 

結論:我懷疑什麼是最好的話,我想我會在TakeWhile去這是第一次運行時最慢的。

無論如何,我的問題是,是否有任何方法通過restringing TakeWhile函數的結果來優化性能。

+1

請解釋一下你的意思是「最好」:最快?最少的記憶飢餓?最容易理解? – LukeH

+0

@LukeH我已經決定要選擇什麼:緊固件。我的問題是,如果有比'新字符串(x.TakeWhile(p).ToArray)更好的方法'' – Shimmy

+2

@LukeH:可能想要取消刪除您的解決方案:它比我的速度快很多 – BrokenGlass

回答

13

假設你正在尋找主要的表現,那麼這樣的事情應該基本上快於你的任何實例:

string x = "asdf1234"; 
string y = x.LeadingLettersOnly(); 

// ... 

public static class StringExtensions 
{ 
    public static string LeadingLettersOnly(this string source) 
    { 
     if (source == null) 
      throw new ArgumentNullException("source"); 

     if (source.Length == 0) 
      return source; 

     char[] buffer = new char[source.Length]; 
     int bufferIndex = 0; 

     for (int sourceIndex = 0; sourceIndex < source.Length; sourceIndex++) 
     { 
      char c = source[sourceIndex]; 

      if (!char.IsLetter(c)) 
       break; 

      buffer[bufferIndex++] = c; 
     } 
     return new string(buffer, 0, bufferIndex); 
    } 
} 
+0

嗯,只是注意到,你只需要從字符串開頭的字母,在這種情況下,我期望[BrokenGlass的答案](http://stackoverflow.com/questions/8108313/best-way-to-convert-ienumerablechar字符串/ 8108584#8108584)是最快的。 (再次,我沒有真正的基準確認。) – LukeH

+1

+1預先分配緩衝區可能會讓這個速度更快,但這只是一個猜測 - 有限的測試比使用'Substring()'快了一點, – BrokenGlass

9

你可以經常做的更好的性能,明智的。但是,這會給你帶來什麼?除非這確實是您應用程序的瓶頸,並且您已經測量了它,否則我會堅持使用Linq TakeWhile()版本:這是最易讀和可維護的解決方案,這對於大多數應用程序來說都是非常重要的。

如果你真的正在尋找原始性能,你可以做手工轉換 - 以下是各地的一個因素4+(取決於輸入字符串的長度)在我的測試速度比TakeWhile() - 但我不會用它個人,除非它是至關重要的:

int j = 0; 
for (; j < input.Length; j++) 
{ 
    if (!char.IsLetter(input[j])) 
     break; 
} 
string output = input.Substring(0, j); 
+3

+ 1。把這個包裝在某種輔助方法中用於重複使用是沒有問題的。像'source.LeadingLettersOnly()'這樣的東西比'new string(source.TakeWhile(char.IsLetter).ToArray())',imo更具有可讀性。 – LukeH

+1

@LukeH:您的解決方案速度更快 - 請取消刪除! – BrokenGlass

+0

該函數應該將搜索查詢與幾千(100000)個字符串的第一個字符進行比較,因此,性能是最重要的。 – Shimmy

11

爲什麼不是有可能在字符串中使用流暢的語言?

這是可能的。你們這樣做是在問題本身:

var y = new string(x.TakeWhile(char.IsLetter).ToArray()); 

是不是有更好的辦法來IEnumerable<char>轉換爲字符串?

(我的假設是:)

框架並沒有這樣的構造,因爲字符串是不可變的,你不得不兩次遍歷枚舉,以預分配內存的字符串。這並不總是一個選項,特別是如果你的輸入是一個流。

唯一的解決方法是首先推入後備陣列或StringBuilder,並在輸入增長時重新分配。對於像字符串這樣低級的東西,這可能應該被認爲是太隱藏了一種機制。它還會通過鼓勵人們使用一種機制來儘可能快地將性能問題推到字符串類中。

這些問題通過要求用戶使用ToArray擴展方法很容易解決。

正如其他人所指出的,如果您編寫支持代碼,並將該支持代碼包裝在擴展方法中以獲得乾淨的界面,則可以實現您想要的功能(執行富有表現力的代碼)。

+0

順便說一句,最好的事情做到「流利」,是我添加到我的擴展庫一個'加入'重載,需要'IEnumerable '並返回字符串。 – Shimmy

+6

匿名downvoters沒有任何幫助。陳述你的理由,我會解決你的擔憂。 –

31

這個怎麼樣IEnumerable<char>string轉換:

string.Concat(x.TakeWhile(char.IsLetter)); 
+3

+1非常短,並不需要.ToArray() – Alex

+0

我想那個string.Concat在內部使用StringBuilder。如果沒有,會很奇怪。所以這個解決方案也應該表現的很好。 –

+0

.Net 4.0只。即使你編寫自己的.TakeWhile在3.5然後string.Concat(IEnumerable )不會做你的期望。 –

13

我做了這樣的another question但越來越多的主題,那就是成爲一個直接的回答這個問題。

我已經做了轉換的IEnumerable<char>string的3種簡單的方法的一些性能測試,這些方法都是

新的字符串

return new string(charSequence.ToArray()); 

的毗連

return string.Concat(charSequence) 

的StringBuilder

var sb = new StringBuilder(); 
foreach (var c in charSequence) 
{ 
    sb.Append(c); 
} 

return sb.ToString(); 

在我的測試中,在linked question中詳細說明,對於"Some reasonably small test data"我得到這樣的結果1000000迭代,

百萬「Concat」迭代耗時1597ms。

1000000次迭代的「新字符串」花了869ms。

「StringBuilder」的1000000次迭代耗時748ms。

這表明我沒有很好的理由使用string.Concat來完成此任務。如果你想簡單使用新字符串的方法,如果要性能使用StringBuilder

我會告訴我的斷言,實際上所有這些方法都可以正常工作,而且這可能都是過度優化。

+0

我想犧牲121毫秒來使用'新字符串'來代替編寫三個額外的代碼行來使用'StringBuilder'。 #cleanCode。 – RBT

4

返回新字符串(foo.Select(x => x).ToArray());

相關問題