Có một sự thay thế cho chuỗi. Đặt lại không phân biệt chữ hoa chữ thường?


306

Tôi cần tìm kiếm một chuỗi và thay thế tất cả các lần xuất hiện %FirstName%%PolicyAmount%bằng một giá trị được lấy từ cơ sở dữ liệu. Vấn đề là viết hoa của FirstName khác nhau. Điều đó ngăn cản tôi sử dụng String.Replace()phương pháp. Tôi đã thấy các trang web về chủ đề gợi ý

Regex.Replace(strInput, strToken, strReplaceWith, RegexOptions.IgnoreCase);

Tuy nhiên đối với một số lý do khi tôi cố gắng và thay thế %PolicyAmount%với $0, việc thay đổi không bao giờ diễn ra. Tôi giả định rằng nó có liên quan đến ký hiệu đô la là một nhân vật dành riêng trong regex.

Có phương pháp nào khác mà tôi có thể sử dụng không liên quan đến vệ sinh đầu vào để xử lý các ký tự đặc biệt regex không?


1
Nếu "$ 0" là biến sẽ không ảnh hưởng đến regex.
cfeduke

Câu trả lời:


132

Từ MSDN
$ 0 - "Thay thế chuỗi con cuối cùng khớp với số thứ tự nhóm (số thập phân)."

Trong .NET Biểu thức chính quy nhóm 0 luôn luôn là toàn bộ khớp. Đối với một chữ $ bạn cần phải

string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);

16
trong trường hợp cụ thể này thì tốt, nhưng trong trường hợp các chuỗi được nhập từ bên ngoài, người ta không thể chắc chắn rằng chúng không chứa các ký tự có nghĩa là một cái gì đó đặc biệt trong các biểu thức thông thường
Allanrbo

23
Bạn nên thoát các ký tự đặc biệt như thế này: chuỗi value = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), Regex.Escape ("$ 0"), RegexOptions.IgnoreCase);
Helge Klein

8
Vui lòng xem khi sử dụng Regex.Escape trong Regex.Replace. Bạn sẽ phải thoát tất cả ba chuỗi được thông qua và gọi Regex.Unescape về kết quả!
Holger Adam

4
Theo msdn: "Thoát ký tự được nhận dạng trong các mẫu biểu thức chính quy nhưng không phải trong các mẫu thay thế." ( msdn.microsoft.com/en-us/l
Library / 4edbef7e.aspx

1
Tốt nhất nên sử dụng: chuỗi value = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), "$ 0" .Replace ("$", "$$"), RegexOptions.IgnoreCase); như thay thế chỉ nhận ra dấu hiệu mol.
Skorek

295

Có vẻ như string.Replace nên có một quá tải mà cần một StringComparisonđối số. Vì nó không có, bạn có thể thử một cái gì đó như thế này:

public static string ReplaceString(string str, string oldValue, string newValue, StringComparison comparison)
{
    StringBuilder sb = new StringBuilder();

    int previousIndex = 0;
    int index = str.IndexOf(oldValue, comparison);
    while (index != -1)
    {
        sb.Append(str.Substring(previousIndex, index - previousIndex));
        sb.Append(newValue);
        index += oldValue.Length;

        previousIndex = index;
        index = str.IndexOf(oldValue, index, comparison);
    }
    sb.Append(str.Substring(previousIndex));

    return sb.ToString();
}

9
Đẹp. Tôi sẽ thay đổi ReplaceStringthành Replace.
AMissico

41
Đồng ý với ý kiến ​​trên. Điều này có thể được thực hiện thành một phương thức mở rộng với cùng tên phương thức. Chỉ cần bật nó trong một lớp tĩnh với chữ ký phương thức: chuỗi tĩnh công khai Thay thế (chuỗi này str, chuỗi oldValue, chuỗi newValue, so sánh StringComparison)
Mark Robinson

8
@Helge, nói chung, điều đó có thể tốt, nhưng tôi phải lấy các chuỗi tùy ý từ người dùng và không thể mạo hiểm đầu vào có ý nghĩa đối với regex. Tất nhiên, tôi đoán rằng tôi có thể viết một vòng lặp và đặt dấu gạch chéo ngược trước mỗi ký tự ... Vào thời điểm đó, tôi cũng có thể làm như trên (IMHO).
Jim

9
Trong khi kiểm tra đơn vị này, tôi gặp phải trường hợp nó sẽ không bao giờ quay lại khi nào oldValue == newValue == "".
Ishmael

10
Đây là lỗi; ReplaceString("œ", "oe", "", StringComparison.InvariantCulture)ném ArgumentOutOfRangeException.
Michael Liu

45

Loại một nhóm bối rối của câu trả lời, một phần vì tiêu đề của câu hỏi thực sự là nhiều hơn so với các câu hỏi cụ thể được yêu cầu. Sau khi đọc qua, tôi không chắc bất kỳ câu trả lời nào chỉ là một vài chỉnh sửa khi đồng hóa tất cả những thứ tốt ở đây, vì vậy tôi đoán rằng tôi sẽ cố gắng tổng hợp.

Đây là một phương pháp mở rộng mà tôi nghĩ rằng tránh được những cạm bẫy được đề cập ở đây và cung cấp giải pháp áp dụng rộng rãi nhất.

public static string ReplaceCaseInsensitiveFind(this string str, string findMe,
    string newValue)
{
    return Regex.Replace(str,
        Regex.Escape(findMe),
        Regex.Replace(newValue, "\\$[0-9]+", @"$$$0"),
        RegexOptions.IgnoreCase);
}

Vì thế...

Thật không may, nhận xét của @HA rằng bạn phải Escapecả ba đều không đúng . Giá trị ban đầu và newValuekhông cần phải có.

Lưu ý: Tuy nhiên, bạn phải thoát $s trong giá trị mới mà bạn đang chèn nếu chúng là một phần của điểm đánh dấu "giá trị bị bắt" . Do đó, ba ký hiệu đô la trong Regex.Replace bên trong Regex.Replace [sic]. Không có điều đó, một cái gì đó như thế này phá vỡ ...

"This is HIS fork, hIs spoon, hissssssss knife.".ReplaceCaseInsensitiveFind("his", @"he$0r")

Đây là lỗi:

An unhandled exception of type 'System.ArgumentException' occurred in System.dll

Additional information: parsing "The\hisr\ is\ he\HISr\ fork,\ he\hIsr\ spoon,\ he\hisrsssssss\ knife\." - Unrecognized escape sequence \h.

Nói cho bạn biết, tôi biết những người cảm thấy thoải mái với Regex cảm thấy như việc họ sử dụng sẽ tránh được các lỗi, nhưng tôi vẫn thường đánh hơi một phần chuỗi byte (nhưng chỉ sau khi đọc Spolsky trên bảng mã ) để chắc chắn bạn sẽ hiểu được bạn dành cho các trường hợp sử dụng quan trọng. Nhắc nhở tôi về Crockford về " những biểu hiện thường xuyên không an toàn " một chút. Chúng tôi thường xuyên viết các biểu thức cho phép những gì chúng tôi muốn (nếu chúng tôi may mắn), nhưng vô tình cho phép nhiều hơn trong (ví dụ: Có $10thực sự là một chuỗi "giá trị bắt giữ" hợp lệ trong regrecValue mới của tôi ở trên không?) Bởi vì chúng tôi không đủ chu đáo . Cả hai phương pháp đều có giá trị và cả hai đều khuyến khích các loại lỗi vô ý khác nhau. Nó thường dễ dàng đánh giá thấp sự phức tạp.

Lối $thoát kỳ lạ đó (và điều đó Regex.Escapekhông thoát khỏi các mẫu giá trị bị bắt $0như tôi mong đợi ở các giá trị thay thế) khiến tôi phát điên trong một thời gian. Lập trình là khó (c) 1842


32

Đây là một phương pháp mở rộng. Không chắc chắn nơi tôi tìm thấy nó.

public static class StringExtensions
{
    public static string Replace(this string originalString, string oldValue, string newValue, StringComparison comparisonType)
    {
        int startIndex = 0;
        while (true)
        {
            startIndex = originalString.IndexOf(oldValue, startIndex, comparisonType);
            if (startIndex == -1)
                break;

            originalString = originalString.Substring(0, startIndex) + newValue + originalString.Substring(startIndex + oldValue.Length);

            startIndex += newValue.Length;
        }

        return originalString;
    }

}

Bạn có thể cần phải xử lý các trường hợp chuỗi rỗng / null.
Vad

2
Lỗi đa dạng trong giải pháp này: 1. Kiểm tra gốcString, oldValue và newValue xem có null không. 2. Không trả lại orginalString (không hoạt động, các loại đơn giản không được chuyển qua tham chiếu), nhưng trước tiên gán giá trị của orginalValue cho một chuỗi mới và sửa đổi nó và trả lại.
RWC

31

Có vẻ phương pháp đơn giản nhất là sử dụng phương thức Thay thế đi kèm với .Net và đã có từ .Net 1.0:

string res = Microsoft.VisualBasic.Strings.Replace(res, 
                                   "%PolicyAmount%", 
                                   "$0", 
                                   Compare: Microsoft.VisualBasic.CompareMethod.Text);

Để sử dụng phương pháp này, bạn phải thêm một Tham chiếu đến tập hợp Microsoft.VisualBasic. Tập hợp này là một phần tiêu chuẩn của thời gian chạy .Net, nó không phải là một bản tải xuống bổ sung hoặc được đánh dấu là lỗi thời.


4
Nó hoạt động. Bạn cần thêm một tham chiếu đến hội đồng Microsoft.VisualBasic.
CleverPatrick

Điều kỳ lạ là phương pháp này có một số vấn đề khi tôi sử dụng nó (các ký tự ở đầu dòng bị mất). Câu trả lời phổ biến nhất ở đây từ C. Dragon 76làm việc như mong đợi.
Jeremy Thompson

1
Vấn đề với điều này là nó trả về một chuỗi MỚI ngay cả khi việc thay thế không được thực hiện, trong đó chuỗi.replace () trả về một con trỏ tới cùng một chuỗi. Có thể không hiệu quả nếu bạn đang làm một cái gì đó như hợp nhất mẫu thư.
Brain2000

4
Brain2000, bạn đã sai. Tất cả các chuỗi trong .NET là bất biến.
Der_Meister

Der_Meister, trong khi những gì bạn nói là chính xác, điều đó không làm cho những gì Brain2000 nói sai.
Simon Hewitt

11
    /// <summary>
    /// A case insenstive replace function.
    /// </summary>
    /// <param name="originalString">The string to examine.(HayStack)</param>
    /// <param name="oldValue">The value to replace.(Needle)</param>
    /// <param name="newValue">The new value to be inserted</param>
    /// <returns>A string</returns>
    public static string CaseInsenstiveReplace(string originalString, string oldValue, string newValue)
    {
        Regex regEx = new Regex(oldValue,
           RegexOptions.IgnoreCase | RegexOptions.Multiline);
        return regEx.Replace(originalString, newValue);
    }

Đó là cách tốt hơn? Điều gì về stackoverflow.com/a/244933/206730 ? hiệu suất tốt hơn?
Kiquenet

8

Lấy cảm hứng từ câu trả lời của cfeduke, tôi đã tạo hàm này sử dụng IndexOf để tìm giá trị cũ trong chuỗi và sau đó thay thế nó bằng giá trị mới. Tôi đã sử dụng điều này trong tập lệnh SSIS xử lý hàng triệu hàng và phương thức regex chậm hơn phương thức này.

public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue)
{
    int prevPos = 0;
    string retval = str;
    // find the first occurence of oldValue
    int pos = retval.IndexOf(oldValue, StringComparison.InvariantCultureIgnoreCase);

    while (pos > -1)
    {
        // remove oldValue from the string
        retval = retval.Remove(pos, oldValue.Length);

        // insert newValue in it's place
        retval = retval.Insert(pos, newValue);

        // check if oldValue is found further down
        prevPos = pos + newValue.Length;
        pos = retval.IndexOf(oldValue, prevPos, StringComparison.InvariantCultureIgnoreCase);
    }

    return retval;
}

+1 vì không sử dụng regex khi không cần thiết. Chắc chắn, bạn sử dụng một vài dòng mã, nhưng nó hiệu quả hơn nhiều so với thay thế dựa trên regex trừ khi bạn cần chức năng $.
ChrisG

6

Mở rộng câu trả lời phổ biến của C. Dragon 76 bằng cách biến mã của anh ta thành một phần mở rộng làm quá tải Replacephương thức mặc định .

public static class StringExtensions
{
    public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
    {
        StringBuilder sb = new StringBuilder();

        int previousIndex = 0;
        int index = str.IndexOf(oldValue, comparison);
        while (index != -1)
        {
            sb.Append(str.Substring(previousIndex, index - previousIndex));
            sb.Append(newValue);
            index += oldValue.Length;

            previousIndex = index;
            index = str.IndexOf(oldValue, index, comparison);
        }
        sb.Append(str.Substring(previousIndex));
        return sb.ToString();
     }
}

3

Dựa trên câu trả lời của Jeff Reddy, với một số tối ưu hóa và xác nhận:

public static string Replace(string str, string oldValue, string newValue, StringComparison comparison)
{
    if (oldValue == null)
        throw new ArgumentNullException("oldValue");
    if (oldValue.Length == 0)
        throw new ArgumentException("String cannot be of zero length.", "oldValue");

    StringBuilder sb = null;

    int startIndex = 0;
    int foundIndex = str.IndexOf(oldValue, comparison);
    while (foundIndex != -1)
    {
        if (sb == null)
            sb = new StringBuilder(str.Length + (newValue != null ? Math.Max(0, 5 * (newValue.Length - oldValue.Length)) : 0));
        sb.Append(str, startIndex, foundIndex - startIndex);
        sb.Append(newValue);

        startIndex = foundIndex + oldValue.Length;
        foundIndex = str.IndexOf(oldValue, startIndex, comparison);
    }

    if (startIndex == 0)
        return str;
    sb.Append(str, startIndex, str.Length - startIndex);
    return sb.ToString();
}

2

một phiên bản tương tự như C. Dragon's, nhưng nếu bạn chỉ cần một thay thế duy nhất:

int n = myText.IndexOf(oldValue, System.StringComparison.InvariantCultureIgnoreCase);
if (n >= 0)
{
    myText = myText.Substring(0, n)
        + newValue
        + myText.Substring(n + oldValue.Length);
}

1

Đây là một tùy chọn khác để thực hiện thay thế Regex, vì dường như không có nhiều người chú ý đến các trận đấu có chứa vị trí trong chuỗi:

    public static string ReplaceCaseInsensative( this string s, string oldValue, string newValue ) {
        var sb = new StringBuilder(s);
        int offset = oldValue.Length - newValue.Length;
        int matchNo = 0;
        foreach (Match match in Regex.Matches(s, Regex.Escape(oldValue), RegexOptions.IgnoreCase))
        {
            sb.Remove(match.Index - (offset * matchNo), match.Length).Insert(match.Index - (offset * matchNo), newValue);
            matchNo++;
        }
        return sb.ToString();
    }

Bạn có thể giải thích lý do tại sao bạn nhân với MatchNo không?
Aheho

Nếu có sự khác biệt về độ dài giữa oldValue và newValue, chuỗi sẽ dài hơn hoặc ngắn hơn khi bạn thay thế các giá trị. match.Index đề cập đến vị trí ban đầu trong chuỗi, chúng ta cần điều chỉnh cho chuyển động vị trí đó do sự thay thế của chúng ta. Một cách tiếp cận khác là thực hiện Xóa / Chèn từ phải sang trái.
Brandon

Tôi hiểu rồi Đó là biến "offset" dùng để làm gì. Điều tôi không hiểu là tại sao bạn lại nhân với matchNo. Trực giác của tôi cho tôi biết rằng vị trí của một trận đấu trong một chuỗi sẽ không liên quan đến số lần xuất hiện thực tế trước đó.
Aheho

Không sao, tôi hiểu rồi. Giá trị bù cần được chia tỷ lệ dựa trên số lần xuất hiện. Nếu bạn mất 2 ký tự mỗi lần bạn cần thay thế, bạn cần tính đến điều đó khi tính toán các tham số cho phương thức xóa
Aheho

0
Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);

3
Điều này không hoạt động. $ Không có trong mã thông báo. Đó là trong strReplace Với chuỗi.
Aheho

9
Và bạn không thể thích ứng nó cho điều đó?
Joel Coehoorn

18
Trang web này được coi là một kho lưu trữ cho câu trả lời chính xác. Không phải câu trả lời gần như đúng.
Aheho

0

Phương thức biểu thức chính quy nên hoạt động. Tuy nhiên, những gì bạn cũng có thể làm là viết thường chuỗi ký tự từ cơ sở dữ liệu, viết thường các biến %% bạn có, sau đó xác định vị trí và độ dài trong chuỗi ký tự thấp hơn từ cơ sở dữ liệu. Hãy nhớ rằng, các vị trí trong một chuỗi không thay đổi chỉ vì vỏ thấp hơn.

Sau đó, sử dụng một vòng lặp đi ngược lại (dễ dàng hơn, nếu không, bạn sẽ phải tiếp tục đếm số điểm mà các điểm sau di chuyển đến) xóa khỏi chuỗi không có giá trị thấp hơn khỏi cơ sở dữ liệu% biến% theo vị trí của chúng và chiều dài và chèn các giá trị thay thế.


Ngược lại, tôi có nghĩa là xử lý các vị trí tìm thấy ngược lại từ xa nhất đến ngắn nhất, không đi qua chuỗi từ cơ sở dữ liệu theo chiều ngược lại.
cfeduke

Bạn có thể, hoặc bạn chỉ có thể sử dụng Regex :)
Ray

0

(Vì mọi người đang chụp ảnh này). Đây là phiên bản của tôi (với kiểm tra null và thoát chính xác và thoát thay thế) ** Lấy cảm hứng từ khắp nơi trên internet và các phiên bản khác:

using System;
using System.Text.RegularExpressions;

public static class MyExtensions {
    public static string ReplaceIgnoreCase(this string search, string find, string replace) {
        return Regex.Replace(search ?? "", Regex.Escape(find ?? ""), (replace ?? "").Replace("$", "$$"), RegexOptions.IgnoreCase);          
    }
}

Sử dụng:

var result = "This is a test".ReplaceIgnoreCase("IS", "was");

0

Hãy để tôi làm cho trường hợp của tôi và sau đó bạn có thể xé tôi thành mảnh vụn nếu bạn muốn.

Regex không phải là câu trả lời cho vấn đề này - quá chậm và bộ nhớ đói, nói một cách tương đối.

StringBuilder tốt hơn nhiều so với xâu chuỗi.

Vì đây sẽ là một phương thức mở rộng để bổ sung string.Replace, tôi tin rằng điều quan trọng là phải khớp với cách thức hoạt động - do đó, việc đưa ra các ngoại lệ cho cùng một vấn đề đối số cũng quan trọng như trả về chuỗi gốc nếu không thực hiện thay thế.

Tôi tin rằng có một tham số StringComparison không phải là một ý tưởng tốt. Tôi đã thử nhưng trường hợp thử nghiệm ban đầu được đề cập bởi michael-liu cho thấy một vấn đề: -

[TestCase("œ", "oe", "", StringComparison.InvariantCultureIgnoreCase, Result = "")]

Trong khi IndexOf sẽ khớp, có sự không khớp giữa độ dài của trận đấu trong chuỗi nguồn (1) và oldValue.Lipse (2). Điều này thể hiện bằng cách gây ra IndexOutOfRange trong một số giải pháp khác khi oldValue.Lipse được thêm vào vị trí khớp hiện tại và tôi không thể tìm ra cách nào khác. Regex không khớp với trường hợp nào, vì vậy tôi đã sử dụng giải pháp thực tế là chỉ sử dụngStringComparison.OrdinalIgnoreCase cho giải pháp của mình.

Mã của tôi tương tự như các câu trả lời khác nhưng điều khó khăn của tôi là tôi tìm kiếm một trận đấu trước khi gặp rắc rối khi tạo một StringBuilder. Nếu không tìm thấy thì phân bổ có khả năng lớn sẽ tránh được. Mã sau đó trở thành một do{...}whilechứ không phải là mộtwhile{...}

Tôi đã thực hiện một số thử nghiệm mở rộng đối với các Câu trả lời khác và điều này xuất hiện nhanh hơn một chút và sử dụng ít bộ nhớ hơn.

    public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue)
    {
        if (str == null) throw new ArgumentNullException(nameof(str));
        if (oldValue == null) throw new ArgumentNullException(nameof(oldValue));
        if (oldValue.Length == 0) throw new ArgumentException("String cannot be of zero length.", nameof(oldValue));

        var position = str.IndexOf(oldValue, 0, StringComparison.OrdinalIgnoreCase);
        if (position == -1) return str;

        var sb = new StringBuilder(str.Length);

        var lastPosition = 0;

        do
        {
            sb.Append(str, lastPosition, position - lastPosition);

            sb.Append(newValue);

        } while ((position = str.IndexOf(oldValue, lastPosition = position + oldValue.Length, StringComparison.OrdinalIgnoreCase)) != -1);

        sb.Append(str, lastPosition, str.Length - lastPosition);

        return sb.ToString();
    }
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.