대소 문자를 구분하지 않는 문자열 대체 방법이 있습니까?


306

문자열을 검색하고 모든 발생 %FirstName%%PolicyAmount%데이터베이스에서 가져온 값으로 바꿔야 합니다. 문제는 FirstName의 대소 문자가 다양하다는 것입니다. 그 String.Replace()방법 을 사용하지 못하게합니다 . 제안하는 주제에 대한 웹 페이지를 보았습니다.

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

그러나 나는 시도하고 대체 어떤 이유 %PolicyAmount%와 함께 $0, 교체는 발생하지 않았다. 달러 기호가 정규식에서 예약 된 문자와 관련이 있다고 가정합니다.

정규식 특수 문자를 처리하기 위해 입력을 살균하지 않는 다른 방법이 있습니까?


1
"$ 0"이 들어가는 변수는 정규식에 전혀 영향을 미치지 않습니다.
cfeduke

답변:


132

MSDN
$ 0- "그룹 번호 번호 (10 진수)와 일치하는 마지막 하위 문자열을 대체합니다."

.NET 정규식에서 그룹 0은 항상 전체 일치합니다. 리터럴 $의 경우

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

16
이 특별한 경우에는 문제가 없지만 문자열을 외부에서 입력하는 경우 정규 표현식에서 특별한 의미가있는 문자를 포함하지 않을 수 있습니다.
Allanrbo

23
다음과 같은 특수 문자를 이스케이프해야합니다. 문자열 값 = Regex.Replace ( "% PolicyAmount %", Regex.Escape ( "% PolicyAmount %"), Regex.Escape ( "$ 0"), RegexOptions.IgnoreCase);
Helge Klein

8
Regex.Replace에서 Regex.Escape를 사용할 때주의하십시오. 전달 된 세 문자열을 모두 이스케이프하고 결과에서 Regex.Unescape를 호출해야합니다!
Holger Adam

4
msdn에 따르면 : "문자 이스케이프는 정규 표현식 패턴에서는 인식되지만 대체 패턴에서는 인식되지 않습니다." ( msdn.microsoft.com/en-us/library/4edbef7e.aspx )
Bronek

1
사용하는 것이 가장 좋습니다. 문자열 값 = Regex.Replace ( "% PolicyAmount %", Regex.Escape ( "% PolicyAmount %"), "$ 0".Replace ( "$", "$$"), RegexOptions.IgnoreCase); 교체는 달러 기호 만 인식합니다.
Skorek 2016 년

295

처럼 보인다 string.Replace 해야 걸리는 과부하가 StringComparison인수를. 그렇지 않기 때문에 다음과 같이 시도 할 수 있습니다.

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
좋은. 로 변경 ReplaceString합니다 Replace.
AMissico

41
위의 의견에 동의하십시오. 이것은 동일한 메소드 이름을 가진 확장 메소드로 만들 수 있습니다. 메소드 서명을 사용하여 정적 클래스에서 팝하십시오. public static string Replace (this String str, string oldValue, string newValue, StringComparison 비교)
Mark Robinson

8
@Helge는 일반적으로 괜찮을 수 있지만 사용자로부터 임의의 문자열을 가져와야하며 입력이 정규식에 의미가 있다고 위험 할 수는 없습니다. 물론, 나는 루프를 작성하고 각각의 모든 문자 앞에 백 슬래시를 넣을 수 있다고 생각합니다 ... 그 시점에서, 나는 위 (IMHO)를 할 수도 있습니다.
Jim

9
단위 테스트 중에 나는 언제 돌아올 수없는 경우에 부딪쳤다 oldValue == newValue == "".
Ishmael

10
이것은 버그입니다. ReplaceString("œ", "oe", "", StringComparison.InvariantCulture)던졌습니다 ArgumentOutOfRangeException.
Michael Liu

45

질문의 제목이 실제로 요청되는 특정 질문보다 훨씬 크기 때문에 혼란스러운 답변 그룹입니다 . 읽은 후, 나는 여기에 모든 좋은 것들을 동화시키는 것에 대한 몇 가지 편집 내용이 있는지 확실하지 않으므로 요약하려고합니다.

여기에 언급 된 함정을 피하고 가장 광범위하게 적용 가능한 솔루션을 제공하는 확장 방법이 있습니다.

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

그래서...

불행히도, 세 가지 모두에 대한 @HA의 의견 Escape은 정확하지 않습니다 . 초기 값이며 newValue반드시 그럴 필요는 없습니다.

참고 : 그러나 "포착 된 값"마커 인 것의 일부인 경우$ 삽입중인 새 값 에서을 이스케이프해야합니다 . 따라서 Regex.Replace [sic] 안에있는 Regex.Replace의 3 달러 기호. 그것 없이는 이와 같은 것이 깨집니다.

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

오류는 다음과 같습니다.

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.

Regex에 익숙한 사람들은 사용이 오류를 피하는 것처럼 느껴지지만, 여전히 스니핑 문자열 (부분적 으로 인코딩에서 Spolsky 를 읽은 후에 만)에서 부분적으로 당신이 무엇을 얻는 지 확실히 알 수 있습니다. 중요한 사용 사례를위한 것입니다. Crockford가 " 안전하지 않은 정규식 "에 대해 조금 생각 나게합니다 . 너무 자주 우리는 우리가 원하는 것을 허용하는 $10정규 표현식을 작성 하지만 (우리가 운이 좋으면) 의도하지 않게 더 많은 것을 허용합니다 (예를 들어 , 위의 newValue 정규 표현식에서 실제로 유효한 "캡처 값"문자열입니까?) . 두 방법 모두 가치가 있으며, 두 가지 방법 모두 서로 다른 유형의 의도하지 않은 오류를 권장합니다. 복잡성을 과소 평가하는 것은 종종 쉬운 일입니다.

그 이상한 $탈출 (그리고 대체 가치에서 예상했던 Regex.Escape것과 같은 캡처 된 가치 패턴을 피하지 못했습니다 $0)은 잠시 동안 나를 화나게했습니다. 프로그래밍이 어렵다 (C) 1842


32

확장 방법은 다음과 같습니다. 어디서 찾았는지 모르겠습니다.

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

}

빈 / 널 문자열 사례를 처리해야 할 수도 있습니다.
Vad

2
이 솔루션의 여러 오류 : 1. originalString, oldValue 및 newValue가 널인지 확인하십시오. 2. orginalString을 되돌려주지 말고 (작동하지 않고 단순 유형은 참조로 전달되지 않음) 먼저 orginalValue 값을 새 문자열에 지정하고 수정 한 후 다시 제공하십시오.
RWC

31

가장 쉬운 방법은 .Net과 함께 제공되며 .Net 1.0부터 사용 된 Replace 메서드를 사용하는 것입니다.

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

이 방법을 사용하려면 Microsoft.VisualBasic 어셈블리에 대한 참조를 추가해야합니다. 이 어셈블리는 .Net 런타임의 표준 부분으로, 추가 다운로드가 아니거나 더 이상 사용되지 않는 것으로 표시됩니다.


4
효과가있다. Microsoft.VisualBasic 어셈블리에 대한 참조를 추가해야합니다.
CleverPatrick

이 방법을 사용할 때 문제가 발생했습니다 (행 시작 부분의 문자가 누락되었습니다). 여기에서 가장 인기있는 답변 C. Dragon 76은 예상대로 작동했습니다.
Jeremy Thompson

1
이것의 문제는 교체가 이루어지지 않아도 NEW 문자열을 반환한다는 것입니다. string.replace ()는 동일한 문자열에 대한 포인터를 반환합니다. 양식 편지 병합과 같은 작업을 수행하면 비효율적 일 수 있습니다.
Brain2000

4
Brain2000, 당신은 틀 렸습니다. .NET의 모든 문자열은 변경할 수 없습니다.
Der_Meister

Der_Meister는 당신이 말하는 것이 맞지만 Brain2000의 말을 잘못하지 않습니다.
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);
    }

더 나은 방법은 무엇입니까? 에 대해 무엇 stackoverflow.com/a/244933/206730 ? 더 나은 성능?
Kiquenet

8

cfeduke의 답변에서 영감을 얻어 IndexOf를 사용하여 문자열에서 이전 값을 찾은 다음 새 값으로 대체하는이 함수를 만들었습니다. 나는 이것을 수백만 행을 처리하는 SSIS 스크립트에서 사용했으며 정규식 방법은 이것보다 느립니다.

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입니다. 물론, 몇 줄의 코드를 더 사용하지만 $ 기능이 필요하지 않으면 정규식 대체보다 훨씬 효율적입니다.
ChrisG

6

에 확장 C. 드래곤 (76) 의 확장 과부하가 기본으로 자신의 코드를 만들어 '의 인기 대답 Replace하는 방법.

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

Jeff Reddy의 답변과 일부 최적화 및 검증을 기반으로합니다.

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

C. Dragon과 유사한 버전이지만 단일 교체 만 필요한 경우 :

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

1

정규 표현식 대체를 실행하는 또 다른 옵션은 다음과 같습니다. 일치하는 문자열에 위치가 포함되어있는 사람은 많지 않습니다.

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

왜 MatchNo를 곱하는지 설명해 주시겠습니까?
Aheho

oldValue와 newValue 사이의 길이에 차이가 있으면 값을 바꾸면 문자열이 길어 지거나 짧아집니다. match.Index는 문자열 내 원래 위치를 나타내며 교체로 인해 해당 위치 이동을 조정해야합니다. 다른 방법은 오른쪽에서 왼쪽으로 제거 / 삽입을 실행하는 것입니다.
Brandon

나는 그것을 얻는다. 이것이 "오프셋"변수입니다. 내가 이해하지 못하는 것은 왜 당신이 matchNo를 곱하는지입니다. 내 직감은 문자열 내에서 일치 위치가 이전 발생의 실제 수와 관련이 없음을 알려줍니다.
Aheho

신경 쓰지 마 지금 알겠다 발생 횟수에 따라 오프셋을 조정해야합니다. 교체해야 할 때마다 2 개의 문자가 손실되는 경우 매개 변수를 remove 메소드로 계산할 때이를 고려해야합니다.
Aheho

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

3
작동하지 않습니다. $는 토큰에 없습니다. strReplace With 문자열에 있습니다.
Aheho

9
그리고 당신은 그것을 적응시킬 수 없습니까?
Joel Coehoorn

18
이 사이트는 정답을위한 저장소입니다. 거의 정확한 답변이 아닙니다.
Aheho

0

정규식 방법이 작동해야합니다. 그러나 수행 할 수있는 작업은 데이터베이스의 소문자 문자열, 소문자의 % variables %를 사용한 다음 데이터베이스의 소문자 문자열에서 위치와 길이를 찾는 것입니다. 문자열의 위치는 소문자이므로 변경되지 않습니다.

그런 다음 역순으로 돌아가는 루프를 사용하여 (나중에 포인트가 이동하는 곳의 실행 횟수를 유지하지 않아도되는 경우 더 쉬움) 데이터베이스에서 소문자가 아닌 문자열을 % variables % 위치에서 제거하십시오. 길이를 바꾸고 교체 값을 삽입하십시오.


반대로, 찾은 위치를 데이터베이스에서 역으로 순회하지 않고 가장 짧은 곳에서 가장 짧은 곳으로 반대로 처리한다는 의미입니다.
cfeduke

당신은 또는 당신은 그냥 정규식을 사용할 수 있습니다 :)
Ray

0

(모두가 총에 맞기 때문에). 다음은 내 버전입니다 (널 체크 및 올바른 입력 및 교체 이스케이프 포함) ** 인터넷 및 기타 버전에서 영감을 얻었습니다.

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

용법:

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

0

내가 사건을 제기하면 원한다면 조각을 찢을 수 있습니다.

Regex는이 문제에 대한 답이 아닙니다. 너무 느리고 메모리가 부족하여 상대적으로 말하십시오.

StringBuilder는 문자열 맹 글링보다 훨씬 좋습니다.

이것이 보충하는 확장 방법이기 때문에 string.Replace나는 그것이 작동하는 방식과 일치하는 것이 중요하다고 생각합니다. 따라서 동일한 인수 문제에 대해 예외를 던지는 것이 교체하지 않으면 원래 문자열을 반환하는 것과 마찬가지로 중요합니다.

StringComparison 매개 변수를 갖는 것은 좋은 생각이 아니라고 생각합니다. 나는 그것을 시도했지만 michael-liu가 처음 언급 한 테스트 사례에 문제가 있음을 보여주었습니다.

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

IndexOf가 일치하는 동안 소스 문자열 (1)의 일치 길이와 oldValue.Length (2)가 일치하지 않습니다. oldValue.Length가 현재 일치 위치에 추가되었을 때 다른 솔루션에서 IndexOutOfRange가 발생하여 나타납니다. 어쨌든 정규식은 사례와 일치하지 않으므로 솔루션에 대해서만 실용적인 솔루션을 사용 StringComparison.OrdinalIgnoreCase했습니다.

내 코드는 다른 답변과 비슷하지만 내 트위스트는을 만드는 데 어려움을 겪기 전에 일치하는 것을 찾는 것 StringBuilder입니다. 아무것도 발견되지 않으면 잠재적으로 큰 할당을 피할 수 있습니다. 그런 다음 코드 do{...}whilewhile{...}

다른 답변에 대해 광범위한 테스트를 수행했으며 이는 부분적으로 빠르며 약간 적은 메모리를 사용했습니다.

    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();
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.