2011-11-12 26 views
26

Tại sao không thể sử dụng ngôn ngữ thông thạo trên string?Cách tốt nhất để chuyển đổi IEnumerable <char> thành chuỗi?

Ví dụ: không có

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

phải là một cách tốt hơn để chuyển đổi IEnumerable<char>-string?

Dưới đây là một thử nghiệm tôi đã thực hiện:

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(); 
    } 
} 

Kết quả:

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%). 

Kết luận: Tôi nghi ngờ những gì tốt hơn để thích, tôi nghĩ rằng tôi sẽ đi trên TakeWhile đó là chỉ chạy chậm nhất trong lần chạy đầu tiên.

Dù sao, câu hỏi của tôi là nếu có cách nào để tối ưu hóa hiệu suất bằng cách đặt lại kết quả của hàm TakeWhile.

+1

Vui lòng giải thích ý bạn là "tốt nhất": Nhanh nhất? Đói đói nhất? Dễ hiểu nhất? – LukeH

+0

@LukeH Tôi đã đưa ra quyết định về những gì cần chọn: các cuộc thi đấu. Câu hỏi của tôi là nếu có một cách đẹp hơn 'chuỗi mới (x.TakeWhile (p) .ToArray)' – Shimmy

+2

@LukeH: Có thể muốn lấy lại giải pháp của bạn: Nó nhanh hơn tôi bằng một lề rất lớn – BrokenGlass

Trả lời

13

Giả sử rằng bạn đang tìm kiếm chủ yếu để thực hiện, sau đó một cái gì đó như thế này nên nhanh hơn đáng kể hơn so với bất kỳ ví dụ của bạn:

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

Hmmm, chỉ cần nhận thấy rằng bạn chỉ cần chữ cái từ đầu chuỗi, trong trường hợp đó tôi mong đợi [Câu trả lời của BrokenGlass] (http://stackoverflow.com/questions/8108313/best-way-to-convert-ienumerablechar -to-string/8108584 # 8108584) là nhanh nhất. (Một lần nữa, tôi đã không thực sự chuẩn để xác nhận.) – LukeH

+1

+1 Pre-phân bổ bộ đệm có lẽ là những gì làm cho điều này nhanh hơn, nhưng đây chỉ là một thử nghiệm giới hạn đoán cho thấy cách của nó nhanh hơn bằng cách sử dụng 'Substring()' – BrokenGlass

9

Bạn rất thường xuyên có thể làm tốt hơn hiệu suất-khôn ngoan. Nhưng những gì bạn mua? Trừ khi điều này thực sự là cổ chai cho ứng dụng của bạn và bạn đã đo nó là tôi sẽ dính vào phiên bản LINQ TakeWhile(): Đây là giải pháp dễ đọc nhất và có thể bảo trì, và đó là những gì được tính cho hầu hết tất cả các ứng dụng.

Nếu bạn thực sự đang tìm kiếm thực hiện nguyên bạn có thể làm việc chuyển đổi bằng tay - những điều sau đây là khoảng một yếu tố 4+ (tùy thuộc vào chiều dài chuỗi đầu vào) nhanh hơn TakeWhile() trong các thử nghiệm của tôi - nhưng tôi sẽ không sử dụng nó cá nhân trừ khi điều đó quan trọng:

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

+ 1. Và không có gì sai khi gói nó lên trong một phương thức trợ giúp của một loại nào đó để tái sử dụng. Một cái gì đó như 'source.LeadingLettersOnly()' sẽ dễ đọc hơn 'chuỗi mới (source.TakeWhile (char.IsLetter) .ToArray())', imo. – LukeH

+1

@ LukeH: Giải pháp của bạn nhanh hơn - vui lòng phục hồi! – BrokenGlass

+0

Chức năng này được cho là để so sánh một truy vấn tìm kiếm với một vài ký tự đầu tiên của chuỗi (100000), do đó hiệu suất là tất cả những gì quan trọng. – Shimmy

11

Tại sao không thể sử dụng ngôn ngữ thông thạo trên chuỗi?

Có thể. Bạn đã làm điều đó trong câu hỏi bản thân:

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

không có phải là một cách tốt hơn để chuyển đổi IEnumerable<char> chuỗi?

(giả định của tôi là :)

Khung không có một constructor như vậy bởi vì dây là không thay đổi, và bạn sẽ phải đi qua hai lần liệt kê theo thứ tự tiền phân bổ bộ nhớ cho chuỗi . Đây không phải lúc nào cũng là một tùy chọn, đặc biệt nếu đầu vào của bạn là một luồng.

Giải pháp duy nhất cho điều này là để đẩy tới mảng sao lưu hoặc StringBuilder trước tiên và phân bổ lại khi đầu vào tăng lên. Đối với một cái gì đó thấp cấp như một chuỗi, điều này có lẽ nên được coi là quá ẩn một cơ chế. Nó cũng sẽ đẩy vấn đề perf xuống lớp chuỗi bằng cách khuyến khích mọi người sử dụng một cơ chế mà không thể càng nhanh càng tốt.

Các sự cố này được giải quyết dễ dàng bằng cách yêu cầu người dùng sử dụng phương pháp mở rộng ToArray.

Như những người khác đã chỉ ra, bạn có thể đạt được những gì bạn muốn (perf mã biểu cảm) nếu bạn viết mã hỗ trợ và bọc mã hỗ trợ đó trong một phương pháp mở rộng để có giao diện rõ ràng.

+0

BTW, Điều tốt nhất để làm điều đó "thông thạo", tôi có được thêm vào thư viện tiện ích mở rộng của tôi một quá trình 'Tham gia' có' IEnumerable 'và trả về' chuỗi' hay không. – Shimmy

+6

Những người đi xuống ẩn danh không giúp gì được. Nêu rõ lý do của bạn và tôi sẽ giải quyết các mối quan tâm của bạn. –

31

Làm thế nào về vấn đề này để chuyển đổi IEnumerable<char>-string:

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

+1 Rất ngắn và không yêu cầu .ToArray() – Alex

+0

Tôi đoán rằng string.Concat sử dụng một StringBuilder trong nội bộ. Sẽ rất lạ nếu không. Vì vậy, giải pháp này cũng nên thực hiện thực sự tốt. –

+0

.Net 4.0 chỉ. Ngay cả khi bạn viết của riêng bạn .TakeWhile trong 3,5 sau đó string.Concat (IEnumerable ) không làm những gì bạn mong đợi. –

13

Tôi đã thực hiện này là chủ đề của another question nhưng ngày càng nhiều, mà đang trở thành một câu trả lời trực tiếp cho câu hỏi này.

tôi đã thực hiện một số thử nghiệm thực hiện 3 phương pháp đơn giản của chuyển đổi một IEnumerable<char> đến một string, các phương pháp đó là

mới chuỗi

return new string(charSequence.ToArray()); 

concat

return string.Concat(charSequence) 

StringBuilder

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

return sb.ToString(); 

Trong thử nghiệm của tôi, đó là chi tiết trong linked question, cho 1000000 lặp của "Some reasonably small test data" tôi nhận được kết quả như thế này,

1000000 lặp đi lặp lại của "Concat" đã 1597ms.

1000000 lần lặp của "chuỗi mới" mất 869ms.

1000000 lần lặp của "StringBuilder" mất 748ms.

Điều này cho thấy rằng không có lý do chính đáng để sử dụng string.Concat cho tác vụ này. Nếu bạn muốn đơn giản sử dụng phương pháp chuỗi mới và nếu muốn hiệu suất sử dụng StringBuilder.

Tôi sẽ báo trước xác nhận của mình, trong thực tế tất cả các phương thức này hoạt động tốt và điều này có thể là tối ưu hóa quá mức.

+0

Tôi muốn hy sinh 121 mili giây để sử dụng 'chuỗi mới' thay cho việc viết thêm ba dòng mã để sử dụng' StringBuilder'. #cleanCode. – RBT

4

trả lại chuỗi mới (foo.Select (x => x) .ToArray());

Các vấn đề liên quan