String.Replace trường hợp bỏ qua


214

Tôi có một chuỗi gọi là "xin chào thế giới"

Tôi cần thay từ "thế giới" thành "csharp"

cho việc này tôi sử dụng:

string.Replace("World", "csharp");

nhưng kết quả là, tôi không nhận được chuỗi thay thế. Lý do là trường hợp nhạy cảm. Chuỗi ban đầu chứa "thế giới" trong khi tôi đang cố gắng thay thế "Thế giới".

Có cách nào để tránh trường hợp nhạy cảm này trong phương thức chuỗi.Replace không?



Câu trả lời:


309

Bạn có thể sử dụng Regex và thực hiện thay thế không phân biệt chữ hoa chữ thường:

class Program
{
    static void Main()
    {
        string input = "hello WoRlD";
        string result = 
           Regex.Replace(input, "world", "csharp", RegexOptions.IgnoreCase);
        Console.WriteLine(result); // prints "hello csharp"
    }
}

19
Không hoạt động với các yếu tố ngôn ngữ Regex , vì vậy đây không phải là phương pháp phổ quát. Câu trả lời của Steve B là chính xác.
AsValeO

1
Vì vậy, tốt hơn hết bạn không nên viết hello. world?hoặc bất cứ điều gì khác có chứa toán tử regex.
Sebastian Mach

Chỉ trong trường hợp bất kỳ ai không có xu hướng đọc thêm, đây là câu trả lời được chấp nhận vào năm 2011 và có số lượng phiếu bầu rất lớn. Điều này hoạt động tốt nếu bạn chỉ phải thay thế chữ và số. Tuy nhiên, nếu bạn phải thay thế bất kỳ ký tự dấu chấm câu nào, bạn có thể gặp rắc rối lớn. Câu trả lời của Oleg Zarevenny là vượt trội, nhưng chỉ có một số lượng nhỏ phiếu bầu vì nó đã được đăng vào năm 2017.
Tony Pulokas

115
var search = "world";
var replacement = "csharp";
string result = Regex.Replace(
    stringToLookInto,
    Regex.Escape(search), 
    replacement.Replace("$","$$"), 
    RegexOptions.IgnoreCase
);

Các Regex.Escape rất hữu ích nếu bạn dựa vào người dùng nhập vào mà có thể chứa yếu tố ngôn ngữ Regex

Cập nhật

Nhờ có ý kiến, bạn thực sự không phải thoát khỏi chuỗi thay thế.

Đây là một mẹo nhỏ kiểm tra mã :

using System;
using System.Text.RegularExpressions;           
public class Program
{
    public static void Main()
    {

        var tests = new[] {
            new { Input="abcdef", Search="abc", Replacement="xyz", Expected="xyzdef" },
            new { Input="ABCdef", Search="abc", Replacement="xyz", Expected="xyzdef" },
            new { Input="A*BCdef", Search="a*bc", Replacement="xyz", Expected="xyzdef" },
            new { Input="abcdef", Search="abc", Replacement="x*yz", Expected="x*yzdef" },       
            new { Input="abcdef", Search="abc", Replacement="$", Expected="$def" },
        };


        foreach(var test in tests){
            var result = ReplaceCaseInsensitive(test.Input, test.Search, test.Replacement);

            Console.WriteLine(
                "Success: {0}, Actual: {1}, {2}",
                result == test.Expected,
                result,
                test
            );

        }


    }

    private static string ReplaceCaseInsensitive(string input, string search, string replacement){
        string result = Regex.Replace(
            input,
            Regex.Escape(search), 
            replacement.Replace("$","$$"), 
            RegexOptions.IgnoreCase
        );
        return result;
    }
}

Đầu ra của nó là:

Success: True, Actual: xyzdef, { Input = abcdef, Search = abc, Replacement = xyz, Expected = xyzdef } 
Success: True, Actual: xyzdef, { Input = ABCdef, Search = abc, Replacement = xyz, Expected = xyzdef }
Success: True, Actual: xyzdef, { Input = A*BCdef, Search = a*bc, Replacement = xyz, Expected = xyzdef } 
Success: True, Actual: x*yzdef, { Input = abcdef, Search = abc, Replacement = x*yz, Expected = x*yzdef} 
Success: True, Actual: $def, { Input = abcdef, Search = abc, Replacement = $, Expected = $def }

2
Phương pháp này không thành công nếu thay thế = "! @ # $% ^ & * ()" Bạn nhận được "! @ \ # \ $% \ ^ & * ()" Thay thế.
Kcoder

2
Thứ hai Regex.Escapelà xấu, nó sẽ tiền tố các ký tự đặc biệt với dấu gạch chéo ngược. Có vẻ như cách tốt nhất là .Replace ("$", "$$"), đó là một sự ngu ngốc ( stackoverflow.com/a/10078353 ).
Daniel Tuppeny

1
@dannyTuppeny: bạn nói đúng ... Tôi đã cập nhật câu trả lời phù hợp
Steve B

54

Phương pháp 2.5X FASTERHIỆU QUẢ NHẤT so với các phương thức biểu thức thông thường khác:

/// <summary>
/// Returns a new string in which all occurrences of a specified string in the current instance are replaced with another 
/// specified string according the type of search to use for the specified string.
/// </summary>
/// <param name="str">The string performing the replace method.</param>
/// <param name="oldValue">The string to be replaced.</param>
/// <param name="newValue">The string replace all occurrences of <paramref name="oldValue"/>. 
/// If value is equal to <c>null</c>, than all occurrences of <paramref name="oldValue"/> will be removed from the <paramref name="str"/>.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules for the search.</param>
/// <returns>A string that is equivalent to the current string except that all instances of <paramref name="oldValue"/> are replaced with <paramref name="newValue"/>. 
/// If <paramref name="oldValue"/> is not found in the current instance, the method returns the current instance unchanged.</returns>
[DebuggerStepThrough]
public static string Replace(this string str,
    string oldValue, string @newValue,
    StringComparison comparisonType)
{

    // Check inputs.
    if (str == null)
    {
        // Same as original .NET C# string.Replace behavior.
        throw new ArgumentNullException(nameof(str));
    }
    if (str.Length == 0)
    {
        // Same as original .NET C# string.Replace behavior.
        return str;
    }
    if (oldValue == null)
    {
        // Same as original .NET C# string.Replace behavior.
        throw new ArgumentNullException(nameof(oldValue));
    }
    if (oldValue.Length == 0)
    {
        // Same as original .NET C# string.Replace behavior.
        throw new ArgumentException("String cannot be of zero length.");
    }


    //if (oldValue.Equals(newValue, comparisonType))
    //{
    //This condition has no sense
    //It will prevent method from replacesing: "Example", "ExAmPlE", "EXAMPLE" to "example"
    //return str;
    //}



    // Prepare string builder for storing the processed string.
    // Note: StringBuilder has a better performance than String by 30-40%.
    StringBuilder resultStringBuilder = new StringBuilder(str.Length);



    // Analyze the replacement: replace or remove.
    bool isReplacementNullOrEmpty = string.IsNullOrEmpty(@newValue);



    // Replace all values.
    const int valueNotFound = -1;
    int foundAt;
    int startSearchFromIndex = 0;
    while ((foundAt = str.IndexOf(oldValue, startSearchFromIndex, comparisonType)) != valueNotFound)
    {

        // Append all characters until the found replacement.
        int @charsUntilReplacment = foundAt - startSearchFromIndex;
        bool isNothingToAppend = @charsUntilReplacment == 0;
        if (!isNothingToAppend)
        {
            resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilReplacment);
        }



        // Process the replacement.
        if (!isReplacementNullOrEmpty)
        {
            resultStringBuilder.Append(@newValue);
        }


        // Prepare start index for the next search.
        // This needed to prevent infinite loop, otherwise method always start search 
        // from the start of the string. For example: if an oldValue == "EXAMPLE", newValue == "example"
        // and comparisonType == "any ignore case" will conquer to replacing:
        // "EXAMPLE" to "example" to "example" to "example" … infinite loop.
        startSearchFromIndex = foundAt + oldValue.Length;
        if (startSearchFromIndex == str.Length)
        {
            // It is end of the input string: no more space for the next search.
            // The input string ends with a value that has already been replaced. 
            // Therefore, the string builder with the result is complete and no further action is required.
            return resultStringBuilder.ToString();
        }
    }


    // Append the last part to the result.
    int @charsUntilStringEnd = str.Length - startSearchFromIndex;
    resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilStringEnd);


    return resultStringBuilder.ToString();

}

Lưu ý: bỏ qua trường hợp == StringComparison.OrdinalIgnoreCaselàm tham số cho StringComparison comparisonType. Đó là cách nhanh nhất, không phân biệt chữ hoa chữ thường để thay thế tất cả các giá trị.


Ưu điểm của phương pháp này:

  • Hiệu suất CPU và BỘ NHỚ cao;
  • Đây là giải pháp nhanh nhất, nhanh hơn 2,5 lần so với các phương pháp khác với các biểu thức chính quy (bằng chứng cuối cùng);
  • Thích hợp để loại bỏ các phần từ chuỗi đầu vào (được đặt newValuethành null), được tối ưu hóa cho việc này;
  • Tương tự như hành vi .NET C # ban đầu string.Replace, các trường hợp ngoại lệ tương tự;
  • Nhận xét tốt, dễ hiểu;
  • Đơn giản hơn - không có biểu thức chính quy. Biểu thức thông thường luôn chậm hơn vì tính linh hoạt của chúng (thậm chí được biên dịch);
  • Phương pháp này được thử nghiệm tốt và không có lỗ hổng ẩn như vòng lặp vô hạn trong các giải pháp của người khác, thậm chí được đánh giá cao:

@AsValeO: Không hoạt động với các yếu tố ngôn ngữ Regex, vì vậy đây không phải là phương pháp phổ quát

@Mike Stillion: Có một vấn đề với mã này. Nếu văn bản trong mới là một siêu văn bản của văn bản cũ, điều này có thể tạo ra một vòng lặp vô tận.


Chống điểm chuẩn : giải pháp này nhanh hơn 2,59 lần so với regex từ @Steve B., mã:

// Results:
// 1/2. Regular expression solution: 4486 milliseconds
// 2/2. Current solution: 1727 milliseconds — 2.59X times FASTER! than regex!

// Notes: the test was started 5 times, the result is an average; release build.

const int benchmarkIterations = 1000000;
const string sourceString = "aaaaddsdsdsdsdsd";
const string oldValue = "D";
const string newValue = "Fod";
long totalLenght = 0;

Stopwatch regexStopwatch = Stopwatch.StartNew();
string tempString1;
for (int i = 0; i < benchmarkIterations; i++)
{
    tempString1 = sourceString;
    tempString1 = ReplaceCaseInsensitive(tempString1, oldValue, newValue);

    totalLenght = totalLenght + tempString1.Length;
}
regexStopwatch.Stop();



Stopwatch currentSolutionStopwatch = Stopwatch.StartNew();
string tempString2;
for (int i = 0; i < benchmarkIterations; i++)
{
    tempString2 = sourceString;
    tempString2 = tempString2.Replace(oldValue, newValue,
        StringComparison.OrdinalIgnoreCase);

    totalLenght = totalLenght + tempString2.Length;
}
currentSolutionStopwatch.Stop();

Ý tưởng ban đầu - @ Darky711; cảm ơn @MinerR vì StringBuilder.


5
Tôi cá là bạn có thể làm điều này nhanh hơn nữa bằng cách sử dụng StringBuilder chứ không phải là chuỗi.
MineR

1
@MineR Bạn nói đúng, ban đầu tôi chỉ cập nhật giải pháp @ Darky711 mà không có vòng lặp vô hạn, vì vậy tôi đã sử dụng String. Tuy nhiên, StringBuilderthực sự nhanh hơn 30-40% so với String. Tôi đã cập nhật giải pháp. Cảm ơn;)
Oleg Zarevennyi

2
Cách tiếp cận thú vị. Có lẽ là tốt hơn (tốt hơn của tôi :)) khi hiệu suất quan trọng. Điển hình là một phương thức để thêm vào một thư viện mã dùng chung.
Steve B

2
Việc sử dụng các biểu thức 'nameof' làm cho điều này chỉ hợp lệ cho C # 6.0 trở lên. Nếu bạn đang ở VS2013, bạn có thể sử dụng nó bằng cách xóa các toán hạng trong các ngoại lệ.
LanchPad

Đối với nhận xét "// if (oldValue.Equals (newValue, soType))" thay thế so sánhType với StringComparison.Ordinal?
Roger Willcocks

31

Phần mở rộng làm cho cuộc sống của chúng ta dễ dàng hơn:

static public class StringExtensions
{
    static public string ReplaceInsensitive(this string str, string from, string to)
    {
        str = Regex.Replace(str, from, to, RegexOptions.IgnoreCase);
        return str;
    }
}

10
Và thoát ra làm cho cuộc sống của chúng ta ít lỗi hơn :-) trả lại Regex.Replace (đầu vào, Regex.Escape (tìm kiếm), thay thế.Replace ("$", "$$"), RegexOptions.IgnoreCase);
Vman

29

Rất nhiều đề xuất sử dụng Regex. Làm thế nào về phương pháp mở rộng mà không có nó:

public static string Replace(this string str, string old, string @new, StringComparison comparison)
{
    @new = @new ?? "";
    if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(old) || old.Equals(@new, comparison))
        return str;
    int foundAt = 0;
    while ((foundAt = str.IndexOf(old, foundAt, comparison)) != -1)
    {
        str = str.Remove(foundAt, old.Length).Insert(foundAt, @new);
        foundAt += @new.Length;
    }
    return str;
}

Lưu ý rằng đối số so sánh không được sử dụng để thay thế thực tế (nó luôn không phân biệt chữ hoa chữ thường)
Bolo

2
Có một vấn đề với mã này. Nếu văn bản trong mới là một siêu văn bản của văn bản , điều này có thể tạo ra một vòng lặp vô tận. Khi mới được chèn tại FoundAt , giá trị của FoundAt cần được nâng cao theo độ dài của mới .
Mike Stillion

comparisontham số nên được sử dụng IndexOf, thay vìStringComparison.CurrentCultureIgnoreCase
Maxence

@Bolo Tôi đã chỉnh sửa nó để sử dụng đối số so sánh (có thể mất một chút để được xem xét ngang hàng).
bradlis7

2
Tôi cũng tách biệt điều kiện này để trả về chuỗi mới : if(old.Equals(@new, comparison)) return @new;, vì chuỗi mới có thể khác nhau ở chữ hoa / chữ thường.
sɐunıɔ qɐp

13

Bạn có thể sử dụng không gian tên Microsoft.VisualBasic để tìm hàm trợ giúp này:

Replace(sourceString, "replacethis", "withthis", , , CompareMethod.Text)

Tôi tự hào về câu trả lời của mình cho đến khi tôi thấy đây là câu trả lời tốt hơn vì nó được tích hợp sẵn. Ví dụ: String.Replace ("TeStInG123", "t", "z", 1, -1, so sánhMethod.Text) trả về " zeSzInG123 "
Bolo

Cảnh báo, String.Replace trả về null nếu chuỗi đang tìm kiếm là một chuỗi rỗng.
Mafu Josh

1
Trong .Net 4.7.2, bạn cần thêm một tham chiếu đến Microsoft.VisualBasic để làm việc này. Trong .Net Core, lớp Microsoft.VisualBasic.Strings (dù sao trong Phiên bản 10.3.0) không xuất hiện để thực hiện chức năng Thay thế. Điều này cũng hoạt động trong Powershell nếu bạn Add-Class -AssuggingName Microsoft.VisualBasic trước.
Giáo sư Von Lemongargle

6

( Đã chỉnh sửa: không biết về vấn đề 'liên kết trần trụi', xin lỗi về điều đó)

Lấy từ đây :

string myString = "find Me and replace ME";
string strReplace = "me";
myString = Regex.Replace(myString, "me", strReplace, RegexOptions.IgnoreCase);

Có vẻ như bạn không phải là người đầu tiên phàn nàn về việc thiếu chuỗi không nhạy cảm trường hợp.Replace.


5

Đã sửa đổi câu trả lời của @ Darky711 để sử dụng loại so sánh được thông qua và khớp với khung thay thế nhận xét đặt tên và xml càng sát càng tốt.

/// <summary>
/// Returns a new string in which all occurrences of a specified string in the current instance are replaced with another specified string.
/// </summary>
/// <param name="str">The string performing the replace method.</param>
/// <param name="oldValue">The string to be replaced.</param>
/// <param name="newValue">The string replace all occurrances of oldValue.</param>
/// <param name="comparisonType">Type of the comparison.</param>
/// <returns></returns>
public static string Replace(this string str, string oldValue, string @newValue, StringComparison comparisonType)
{
    @newValue = @newValue ?? string.Empty;
    if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(oldValue) || oldValue.Equals(@newValue, comparisonType))
    {
        return str;
    }
    int foundAt;
    while ((foundAt = str.IndexOf(oldValue, 0, comparisonType)) != -1)
    {
        str = str.Remove(foundAt, oldValue.Length).Insert(foundAt, @newValue);
    }
    return str;
}

2

Tôi đã viết phương pháp mở rộng:

public static string ReplaceIgnoreCase(this string source, string oldVale, string newVale)
    {
        if (source.IsNullOrEmpty() || oldVale.IsNullOrEmpty())
            return source;

        var stringBuilder = new StringBuilder();
        string result = source;

        int index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);

        while (index >= 0)
        {
            if (index > 0)
                stringBuilder.Append(result.Substring(0, index));

            if (newVale.IsNullOrEmpty().IsNot())
                stringBuilder.Append(newVale);

            stringBuilder.Append(result.Substring(index + oldVale.Length));

            result = stringBuilder.ToString();

            index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);
        }

        return result;
    }

Tôi sử dụng hai phương thức mở rộng bổ sung cho phương thức mở rộng trước đó:

    public static bool IsNullOrEmpty(this string value)
    {
        return string.IsNullOrEmpty(value);
    }

    public static bool IsNot(this bool val)
    {
        return val == false;
    }

2
Nâng cao. Nhưng IsNotviệc sử dụng tiện ích mở rộng quá nghiêm trọng :)
nawfal

Thất vọng, điều này không hoạt động trong tất cả các tình huống. Tôi đã chuyển một cái tên nổi bật và nó xuất hiện cho đến khi chuỗi dài một triệu ký tự và sau đó hết bộ nhớ
Bbb

Giải pháp thay thế được đưa ra dưới đây đã khắc phục sự cố của tôi
Bbb

Tôi thực sự thích.IsNot
ttugates

1

Mở rộng câu trả lời của PetrucioRegex.Escape trên chuỗi tìm kiếm và thoát khỏi nhóm phù hợp như được đề xuất trong câu trả lời của Steve B (và một số thay đổi nhỏ theo sở thích của tôi):

public static class StringExtensions
{
    public static string ReplaceIgnoreCase(this string str, string from, string to)
    {
        return Regex.Replace(str, Regex.Escape(from), to.Replace("$", "$$"), RegexOptions.IgnoreCase);
    }
}

Điều này sẽ tạo ra kết quả mong đợi sau đây:

Console.WriteLine("(heLLo) wOrld".ReplaceIgnoreCase("(hello) world", "Hi $1 Universe")); // Hi $1 Universe
Console.WriteLine("heLLo wOrld".ReplaceIgnoreCase("(hello) world", "Hi $1 Universe"));   // heLLo wOrld

Tuy nhiên, không thực hiện các lối thoát, bạn sẽ nhận được những điều sau đây, đây không phải là hành vi được mong đợi từ một String.Replacetrường hợp không phân biệt chữ hoa chữ thường:

Console.WriteLine("(heLLo) wOrld".ReplaceIgnoreCase_NoEscaping("(hello) world", "Hi $1 Universe")); // (heLLo) wOrld
Console.WriteLine("heLLo wOrld".ReplaceIgnoreCase_NoEscaping("(hello) world", "Hi $1 Universe"));   // Hi heLLo Universe

1

Điều này không hiệu quả: Tôi không thể chụp ảnh bất cứ điều gì khác nhanh hơn hoặc dễ dàng hơn.

public static class ExtensionMethodsString
{
    public static string Replace(this String thisString, string oldValue, string newValue, StringComparison stringComparison)
    {
        string working = thisString;
        int index = working.IndexOf(oldValue, stringComparison);
        while (index != -1)
        {
            working = working.Remove(index, oldValue.Length);
            working = working.Insert(index, newValue);
            index = index + newValue.Length;
            index = working.IndexOf(oldValue, index, stringComparison);
        }
        return working;
    }
}

Tôi không biết nếu nó nhanh hơn nhưng ngắn gọn, không sử dụng regex và các vấn đề tiềm ẩn và sử dụng StringComparison tích hợp.
fvlinden

0

Chức năng dưới đây là loại bỏ tất cả các từ khớp như (này) khỏi bộ chuỗi. Tác giả Ravikant Sonare.

private static void myfun()
{
    string mystring = "thiTHISThiss This THIS THis tThishiThiss. Box";
    var regex = new Regex("this", RegexOptions.IgnoreCase);
    mystring = regex.Replace(mystring, "");
    string[] str = mystring.Split(' ');
    for (int i = 0; i < str.Length; i++)
    {
        if (regex.IsMatch(str[i].ToString()))
        {
            mystring = mystring.Replace(str[i].ToString(), string.Empty);

        }
    }
    Console.WriteLine(mystring);
}

Hàm này được thay thế tất cả các chuỗi từ bộ chuỗi ... bởi Ravikant Sonare,
Ravikant Sonare

0

Sử dụng giải pháp @Georgy Batalov tôi gặp vấn đề khi sử dụng ví dụ sau

chuỗi gốc = "blah, DC = bleh, DC = blih, DC = bloh, DC = com"; chuỗi thay thế = gốc.ReplaceIgnoreCase (", DC =", ".")

Dưới đây là cách tôi viết lại phần mở rộng của anh ấy

public static string ReplaceIgnoreCase(this string source, string oldVale, 
string newVale)
    {
        if (source.IsNullOrEmpty() || oldVale.IsNullOrEmpty())
            return source;

        var stringBuilder = new StringBuilder();
        string result = source;

        int index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);
        bool initialRun = true;

        while (index >= 0)
        {
            string substr = result.Substring(0, index);
            substr = substr + newVale;
            result = result.Remove(0, index);
            result = result.Remove(0, oldVale.Length);

            stringBuilder.Append(substr);

            index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);
        }

        if (result.Length > 0)
        {
            stringBuilder.Append(result);
        }

        return stringBuilder.ToString();
    }

0

dưới đây là sự thay thế để thay thế chuỗi bỏ qua trường hợp ký tự

String thisString = "hello world"; 
String replaceString = "World";

//thisString.Replace("World", "csharp"); 
//below is the alternative to replace string ignoring character case

int start = StringUtils.indexOfIgnoreCase(thisString,replaceString);
String searchKey = thisString.substring(start, start+replaceString.length());
thisString= thisString.replaceAll(searchKey ,replaceString );
System.out.println(thisString);

//prints hello World

0

Bạn cũng có thể thử Regexlớp học.

var regex = new Regex( "camel", RegexOptions.IgnoreCase ); var newSentence = regex.Replace( sentence, "horse" );


-3

Tôi thích điều này - "Hello World" .ToLower (). Thay thế ("thế giới", "csharp");


1
Điều này sẽ viết thường mọi thứ, ngay cả những từ không được thay thế.
JJJ

Rõ ràng, bạn chỉ có thể sử dụng điều này nếu bạn không bận tâm về vụ án.
Thống chế
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.