Thay thế nhiều phần tử chuỗi trong C #


86

Có cách nào tốt hơn để làm điều này ...

MyString.Trim().Replace("&", "and").Replace(",", "").Replace("  ", " ")
         .Replace(" ", "-").Replace("'", "").Replace("/", "").ToLower();

Tôi đã mở rộng lớp chuỗi để giữ cho nó thành một công việc nhưng có cách nào nhanh hơn không?

public static class StringExtension
{
    public static string clean(this string s)
    {
        return s.Replace("&", "and").Replace(",", "").Replace("  ", " ")
                .Replace(" ", "-").Replace("'", "").Replace(".", "")
                .Replace("eacute;", "é").ToLower();
    }
}

Chỉ để giải trí (và để dừng các tranh luận trong các bình luận) Tôi đã đưa ra một ý chính về điểm chuẩn cho các ví dụ khác nhau bên dưới.

https://gist.github.com/ChrisMcKee/5937656

Tùy chọn regex đạt điểm khủng khiếp; tùy chọn từ điển xuất hiện nhanh nhất; phiên bản dài hạn của trình thay thế stringbuilder nhanh hơn một chút so với phiên bản ngắn.


1
Dựa trên những gì bạn có trong điểm chuẩn của mình, có vẻ như phiên bản từ điển không thực hiện tất cả các thay thế mà tôi nghi ngờ là điều gì đang làm cho nó nhanh hơn các giải pháp StringBuilder.
cóc

1
@toad Chào từ năm 2009; Tôi đã thêm một bình luận bên dưới vào tháng Tư về sai lầm rõ ràng đó. Ý chính được cập nhật mặc dù tôi đã bỏ qua D. Phiên bản từ điển vẫn nhanh hơn.
Chris McKee


1
@TotZam ít nhất hãy kiểm tra ngày trước khi gắn cờ mọi thứ; đây là từ năm 2009 thats từ năm 2012
Chris McKee

Vì nhiều câu trả lời ở đây có vẻ liên quan đến hiệu suất, tôi tin rằng cần chỉ ra rằng câu trả lời của Andrej Adamanko có thể là nhanh nhất cho nhiều người thay thế; chắc chắn nhanh hơn chuỗi .Replace () đặc biệt là trên một chuỗi đầu vào lớn như đã nêu trong câu trả lời của mình.
27

Câu trả lời:


123

Nhanh hơn - không. Hiệu quả hơn - có, nếu bạn sẽ sử dụng StringBuilderlớp học. Với việc triển khai của bạn, mỗi thao tác sẽ tạo ra một bản sao của một chuỗi mà trong các trường hợp có thể làm giảm hiệu suất. Chuỗi là các đối tượng bất biến nên mỗi thao tác chỉ trả về một bản sao đã sửa đổi.

Nếu bạn mong đợi phương thức này sẽ được gọi chủ động trên bội Stringssố có độ dài đáng kể, có thể tốt hơn là "chuyển" việc triển khai của nó vào StringBuilderlớp. Với nó, bất kỳ sửa đổi nào được thực hiện trực tiếp trên phiên bản đó, vì vậy bạn không cần phải thực hiện các thao tác sao chép không cần thiết.

public static class StringExtention
{
    public static string clean(this string s)
    {
        StringBuilder sb = new StringBuilder (s);

        sb.Replace("&", "and");
        sb.Replace(",", "");
        sb.Replace("  ", " ");
        sb.Replace(" ", "-");
        sb.Replace("'", "");
        sb.Replace(".", "");
        sb.Replace("eacute;", "é");

        return sb.ToString().ToLower();
    }
}

2
Để làm rõ câu trả lời từ điển là nhanh nhất stackoverflow.com/a/1321366/52912
Chris McKee

3
Trong điểm chuẩn của bạn trên gist.github.com/ChrisMcKee/5937656 , bài kiểm tra từ điển không hoàn thành: nó không thực hiện tất cả các thay thế và "" thay thế "", không phải "". Không thực hiện tất cả các thay thế có thể là lý do tại sao nó nhanh nhất trong điểm chuẩn. Việc thay thế regex cũng không hoàn tất. Nhưng quan trọng nhất là chuỗi TestData của bạn rất ngắn. Giống như các trạng thái câu trả lời được chấp nhận, chuỗi phải có độ dài đáng kể để StringBuilder có lợi thế. Bạn có thể vui lòng lặp lại điểm chuẩn với các chuỗi 10kB, 100kB và 1MB không?
Leif

Đó là một điểm tốt; vì nó đã được sử dụng để làm sạch url vì vậy các thử nghiệm ở 100kb - 1mb sẽ là không thực tế. Tôi sẽ cập nhật điểm chuẩn để nó sử dụng toàn bộ, đó là một sai lầm.
Chris McKee

Để có hiệu suất tốt nhất, hãy lặp lại các ký tự và tự thay thế chúng. Tuy nhiên, điều đó có thể tẻ nhạt nếu bạn có nhiều chuỗi ký tự đơn lẻ (thấy chúng bắt buộc bạn phải so sánh nhiều ký tự cùng một lúc, trong khi việc thay thế chúng đòi hỏi phải phân bổ thêm bộ nhớ và di chuyển phần còn lại của chuỗi).
Chayim Friedman

13

điều này sẽ hiệu quả hơn:

public static class StringExtension
{
    public static string clean(this string s)
    {
        return new StringBuilder(s)
              .Replace("&", "and")
              .Replace(",", "")
              .Replace("  ", " ")
              .Replace(" ", "-")
              .Replace("'", "")
              .Replace(".", "")
              .Replace("eacute;", "é")
              .ToString()
              .ToLower();
    }
}

Thực sự khó đọc. Tôi chắc rằng bạn biết nó làm gì nhưng một Junior Dev sẽ vò đầu bứt tai với những gì thực sự diễn ra. Tôi đồng ý- Tôi cũng luôn tìm kiếm bàn tay thiếu sót của việc viết một cái gì đó- Nhưng nó chỉ vì sự hài lòng của riêng tôi. Những người khác phát hoảng trước đống hỗn độn.
Piotr Kula

3
Điều này thực sự chậm hơn. BenchmarkOverhead ... 13ms StringClean-user151323 ... 2843ms StringClean-TheVillageIdiot ... 2921ms Thay đổi khi chạy lại nhưng câu trả lời thắng gist.github.com/anonymous/5937596
Chris McKee,

12

Nếu bạn chỉ đơn giản là theo đuổi một giải pháp tốt và không cần tiết kiệm vài nano giây, vậy còn một số đường LINQ thì sao?

var input = "test1test2test3";
var replacements = new Dictionary<string, string> { { "1", "*" }, { "2", "_" }, { "3", "&" } };

var output = replacements.Aggregate(input, (current, replacement) => current.Replace(replacement.Key, replacement.Value));

Tương tự như ví dụ C trong Gist (nếu bạn nhìn ở trên nó báo cáo kết quả xấu xí LINQ là trong các bình luận)
Chris McKee

1
Thật thú vị khi bạn xác định một trạng thái chức năng là "Xấu hơn" so với một trạng thái thủ tục.
TimS

sẽ không tranh luận về nó; sở thích đơn thuần của nó. Như bạn nói, linq chỉ đơn giản là đường cú pháp; và như tôi đã nói, tôi đã đặt phần tương đương phía trên mã :)
Chris McKee

11

Có lẽ dễ đọc hơn một chút?

    public static class StringExtension {

        private static Dictionary<string, string> _replacements = new Dictionary<string, string>();

        static StringExtension() {
            _replacements["&"] = "and";
            _replacements[","] = "";
            _replacements["  "] = " ";
            // etc...
        }

        public static string clean(this string s) {
            foreach (string to_replace in _replacements.Keys) {
                s = s.Replace(to_replace, _replacements[to_replace]);
            }
            return s;
        }
    }

Cũng thêm đề xuất của New In Town về StringBuilder ...


5
Nó sẽ dễ đọc hơn như thế này:private static Dictionary<string, string> _replacements = new Dictionary<string, string>() { {"&", "and"}, {",", ""}, {" ", " "} /* etc */ };
ANeves nghĩ SE là ác

2
hoặc tất nhiên ... từ điển chỉ đọc tĩnh riêng tư <string, string> Replacements = new Dictionary <string, string> () {{"&", "và"}, {",", ""}, {"", " " } /* Vân vân */ }; public static string Clean (this string s) {return Replacements.Keys.Aggregate (s, (current, toReplace) => current.Replace (toReplace, Replacements [toReplace])); }
Chris McKee

2
-1: Sử dụng Từ điển không tạo ra bất kỳ điều gì ở đây. Chỉ cần sử dụng a List<Tuple<string,string>>. Điều này cũng làm thay đổi thứ tự thay thế được thực hiện VÀ không nhanh như ví dụ s.Replace("a").Replace("b").Replace("c"). Đừng sử dụng cái này!
Thomas

6

Có một thứ có thể được tối ưu hóa trong các giải pháp được đề xuất. Có nhiều lệnh gọi để Replace()làm cho mã thực hiện nhiều lần chuyển trên cùng một chuỗi. Với các chuỗi rất dài, các giải pháp có thể chậm do dung lượng bộ nhớ cache của CPU bị thiếu. Có thể là một người nên xem xét thay thế nhiều chuỗi trong một lần vượt qua .


1
Rất nhiều câu trả lời có vẻ lo lắng về hiệu suất, trong trường hợp này thì đây là tốt nhất. Và nó đơn giản vì nó chỉ là một quá tải được ghi lại bằng tài liệu của String.Replace nơi bạn trả về một giá trị mong đợi dựa trên kết quả khớp, trong ví dụ này, sử dụng từ điển để khớp chúng với nhau. Nên đơn giản để hiểu.
27

4

Một tùy chọn khác sử dụng linq là

[TestMethod]
public void Test()
{
  var input = "it's worth a lot of money, if you can find a buyer.";
  var expected = "its worth a lot of money if you can find a buyer";
  var removeList = new string[] { ".", ",", "'" };
  var result = input;

  removeList.ToList().ForEach(o => result = result.Replace(o, string.Empty));

  Assert.AreEqual(expected, result);
}

Bạn có thể khai báo var removeList = new List<string> { /*...*/ };sau đó chỉ cần gọi removeList.ForEach( /*...*/ );và đơn giản hóa mã của bạn. Cũng lưu ý rằng nó không trả lời đầy đủ câu hỏi vì tất cả các chuỗi được tìm thấy đều được thay thế bằng String.Empty.
Tok '

2

Tôi đang làm điều gì đó tương tự, nhưng trong trường hợp của tôi, tôi đang thực hiện tuần tự hóa / De-serialization, vì vậy tôi cần có thể đi cả hai hướng. Tôi thấy việc sử dụng chuỗi [] [] hoạt động gần giống với từ điển, bao gồm cả việc khởi tạo, nhưng bạn cũng có thể đi theo hướng khác, trả các chuỗi thay thế về giá trị ban đầu của chúng, điều mà từ điển thực sự không được thiết lập để làm.

Chỉnh sửa: Bạn có thể sử dụng Dictionary<Key,List<Values>>để nhận được kết quả giống như chuỗi [] []


-1
string input = "it's worth a lot of money, if you can find a buyer.";
for (dynamic i = 0, repl = new string[,] { { "'", "''" }, { "money", "$" }, { "find", "locate" } }; i < repl.Length / 2; i++) {
    input = input.Replace(repl[i, 0], repl[i, 1]);
}

2
Bạn nên cân nhắc thêm ngữ cảnh vào câu trả lời của mình. Giống như một lời giải thích ngắn gọn về những gì nó đang làm Và, nếu có liên quan, tại sao bạn viết nó theo cách bạn đã làm.
Neil
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.