2009-05-05 30 views
7

Tôi có một phương pháp để thay thế mọi ký tự ngoại trừ những ký tự tôi chỉ định. Ví dụ,Inverse String.Replace - Cách làm nhanh hơn?

ReplaceNot("test. stop; or, not", ".;/\\".ToCharArray(), '*'); 

sẽ trở

 
"****.*****;***,****". 

Bây giờ, đây không phải là một thể hiện của tối ưu hóa quá sớm. Tôi gọi phương thức này khá nhiều lần trong một hoạt động mạng. Tôi thấy rằng trên chuỗi dài hơn, nó gây ra một số độ trễ, và loại bỏ nó đã giúp một chút. Bất kỳ trợ giúp để tăng tốc độ này sẽ được đánh giá cao.

public static string ReplaceNot(this string original, char[] pattern, char replacement) 
    {   
     int index = 0; 
     int old = -1; 

     StringBuilder sb = new StringBuilder(original.Length); 

     while ((index = original.IndexOfAny(pattern, index)) > -1) 
     { 
      sb.Append(new string(replacement, index - old - 1)); 
      sb.Append(original[index]); 
      old = index++; 
     } 

     if (original.Length - old > 1) 
     { 
      sb.Append(new string(replacement, original.Length - (old + 1))); 
     } 

     return sb.ToString(); 
    } 

Final # s. Tôi cũng đã thêm một trường hợp thử nghiệm cho chuỗi ký tự 3K, chạy ở 100K lần thay vì 1M để xem mức độ của mỗi thang này. Điều ngạc nhiên duy nhất là các biểu thức chính quy 'quy mô tốt hơn' hơn những người khác, nhưng nó không giúp đỡ vì nó là rất chậm để bắt đầu với:

 
User   Short * 1M Long * 100K  Scale 
John   319    2125   6.66 
Luke   360    2659   7.39 
Guffa   409    2827   6.91 
Mine   447    3372   7.54 
DirkGently  1094   9134   8.35 
Michael   1591   12785   8.04 
Peter   21106   94386   4.47 

Cập nhật: Tôi làm việc tạo ra các biểu thức chính quy cho phiên bản Phêrô một biến tĩnh, và đặt nó vào RegexOptions.Compiled phải công bằng:

 
User   Short * 1M  Long * 100K  Scale 
Peter   8997   74715   8.30 

Pastebin liên kết đến mã thử nghiệm của tôi, hãy sửa lại cho tôi nếu nó là sai: http://pastebin.com/f64f260ee

+0

Một nit: vui lòng thay đổi 'pattern.Contains (original [i]) == false? thay thế: bản gốc [i] 'thành' pattern.Contains (bản gốc [i])? gốc [i]: thay thế'. So sánh biểu thức bool thành true/false là không cần thiết và thường được coi là biểu mẫu không hợp lệ. –

+0

Tôi đã chạy thử nghiệm tốc độ cho tất cả các phiên bản này và bản chỉnh sửa mới nhất của bạn thực sự là chậm nhất trong số tất cả chúng, bạn sẽ nhận được kết quả nhanh hơn gấp 4 lần bằng cách nào? –

+0

@ john, tôi kiểm tra lại chúng, hy vọng tôi không làm gì đó như tải lại trang web trong khi chạy thử nghiệm :) – esac

Trả lời

6

Alright, trên một chuỗi ~ 60kB, điều này sẽ thực hiện nhanh hơn so với phiên bản của bạn khoảng 40%:

public static string ReplaceNot(this string original, char[] pattern, char replacement) 
{ 
    int index = 0; 

    StringBuilder sb = new StringBuilder(new string(replacement, original.Length)); 

    while ((index = original.IndexOfAny(pattern, index)) > -1) 
    { 
     sb[index] = original[index++]; 
    } 

    return sb.ToString(); 
} 

Bí quyết là để khởi tạo một chuỗi mới với tất cả các ký tự thay thế, vì hầu hết các ký tự sẽ được thay thế.

+0

+1 - Tốt. Tuy nhiên, tôi đảm bảo trong trường hợp này vì kết quả của chỉ mụC++ hoặc biểu thức chỉ mụC++ không được sử dụng, sẽ có sự khác biệt hoàn toàn về hiệu suất (theo cách bạn tăng chỉ mục). Tôi chắc chắn rằng JIT sẽ tạo ra cùng một mã chính xác. Tôi sẽ không ngạc nhiên nếu csc.exe tạo ra chính xác cùng một IL. –

+0

@Michael - có vẻ như bạn đã đúng, khi tôi thay đổi lại nó có vẻ tương tự, tôi không biết tại sao nó lại khác lần cuối cùng tôi thử nghiệm nó –

+0

Để xem IL có ildasm đi kèm với .NET Framework (hoặc có thể là SDK), hoặc bạn có thể làm những gì mà tất cả những đứa trẻ tuyệt vời làm và sử dụng .NET Reflector: http://www.red-gate.com/products/reflector/ –

0

nó sẽ là O (n). Bạn dường như đang thay thế tất cả các bảng chữ cái và khoảng trắng theo *, tại sao không chỉ kiểm tra nếu ký tự hiện tại là một bảng chữ cái/khoảng trắng và thay thế nó?

8

bạn không thể sử dụng Regex.Replace như vậy:

Regex regex = new Regex(@"[^.;/\\]"); 
string s = regex.Replace("test. stop; or, not", "*"); 
+0

+1, Hãy đánh bại tôi! –

+0

Điều này cũng khiến tôi rên rỉ. Ngay sau khi bạn có nhiều hơn một vài cuộc gọi đến .IndexOf * hoặc .Substring phương pháp (hoặc có thể ít hiệu quả (?) "Chuỗi mới (thay thế, chỉ số - cũ - 1)"), bạn có thể có thể thực hiện bất cứ điều gì bạn đang cố gắng làm với một regex tương đối đơn giản, trong một vài dòng mã. – gregmac

+0

Và regex sẽ nhanh hơn chạy qua chuỗi một lần? Tôi luôn luôn mặc dù regex-es là không tốt khi hiệu suất là một vấn đề! – dirkgently

4

Tôi không biết nếu điều này sẽ được bất kỳ nhanh hơn, nhưng nó tránh newing lên dây chỉ để họ có thể được nối vào xây dựng chuỗi, mà có thể giúp:

public static string ReplaceNot(this string original, char[] pattern, char replacement) 
    { 
     StringBuilder sb = new StringBuilder(original.Length); 

     foreach (char ch in original) { 
      if (Array.IndexOf(pattern, ch) >= 0) { 
       sb.Append(ch); 
      } 
      else { 
       sb.Append(replacement); 
      } 
     } 

     return sb.ToString(); 
    } 

Nếu số lượng ký tự trong pattern sẽ có kích thước bất kỳ (mà tôi đoán nó thường sẽ không), nó có thể trả tiền để sắp xếp nó và thực hiện một Array.BinarySearch() thay vì Array.indexOf().

Để chuyển đổi đơn giản như vậy, tôi muốn đặt cược rằng nó sẽ không có vấn đề gì nhanh hơn cả regex nữa.

Ngoài ra, vì bộ ký tự của bạn trong pattern có thể thường xuất phát từ chuỗi (ít nhất đó là trải nghiệm chung của tôi với loại API này), tại sao bạn không có chữ ký phương thức là:

public static string ReplaceNot(this string original, string pattern, char replacement) 

hoặc tốt hơn, có tình trạng quá tải nơi pattern có thể là char[] hoặc string?

+0

Vâng - sau khi xem kết quả tính thời gian của bạn ' m một chút ngạc nhiên tại mức giá vé rẻ như thế nào so với ban đầu của bạn ... Tôi đoán rằng bằng cách sử dụng String.IndexOfAny() là xa, tốt hơn nhiều so với gọi Array.IndexOf nhiều lần trên mỗi nhân vật. –

2

StringBuilder có quá tải phải mất một ký tự và một số, do đó bạn không phải tạo chuỗi trung gian để thêm vào StringBuilder. Tôi có được cải thiện khoảng 20% ​​bằng cách thay thế này:

sb.Append(new string(replacement, index - old - 1)); 

với:

sb.Append(replacement, index - old - 1); 

và điều này:

sb.Append(new string(replacement, original.Length - (old + 1))); 

với:

sb.Append(replacement, original.Length - (old + 1)); 

(Tôi đã thử nghiệm mã mà bạn nói nhanh gấp bốn lần và tôi tìm thấy chậm hơn khoảng 15 lần ...)

+0

Vì vậy, những gì tôi nghĩ là câu hỏi thú vị bây giờ là: đây là nhanh hơn so với thói quen của John Rasch mà lạc quan tải StringBuilder với char thay thế hoặc Rasch nhanh hơn? Tôi có thể nhìn thấy nó đi một trong hai cách (và tôi nghi ngờ nó phụ thuộc dữ liệu). –

+0

@Michael: số cập nhật sẽ đến sau một vài. – esac

+0

@esac - cảm ơn ... Tôi thực sự quan tâm, và tôi rất vui vì có người lười biếng hơn tôi quanh đây để tìm hiểu! –

4

Đây là phiên bản khác dành cho bạn. Thử nghiệm của tôi cho thấy hiệu suất của nó khá tốt.

public static string ReplaceNot(
    this string original, char[] pattern, char replacement) 
{ 
    char[] buffer = new char[original.Length]; 

    for (int i = 0; i < buffer.Length; i++) 
    { 
     bool replace = true; 

     for (int j = 0; j < pattern.Length; j++) 
     { 
      if (original[i] == pattern[j]) 
      { 
       replace = false; 
       break; 
      } 
     } 

     buffer[i] = replace ? replacement : original[i]; 
    } 

    return new string(buffer); 
} 
+0

rất đẹp, mặc dù tỷ lệ của bạn là tắt một chút, bạn đang rất gần để đánh bại các trình hàng đầu :) tôi đã cập nhật thời gian chính. – esac

+0

Thú vị - thuật toán-khôn ngoan này tương tự như bài viết của tôi. Sự khác biệt chính là một vòng lặp rõ ràng thay vì gọi Array.IndexOf() và sử dụng một char [] để xây dựng kết quả thay vì một StringBuffer(). Nhưng thực hiện này nhanh hơn 4-5x ... –

+0

@esac, Dường như các điểm chuẩn thay đổi đáng kể tùy thuộc vào máy/khung mà chúng đang chạy trên: Trên PC cũ của tôi (P4 2.6GHz, 1.5GB, XP32) phiên bản của tôi nhanh gấp hai lần John; Trên máy tính mới hơn của tôi (Core2Duo 2.66GHz, 4GB, Vista64), phiên bản của John nhanh hơn gấp đôi so với tôi! – LukeH