programing

C# 문자열의 여러 문자 바꾸기

telebox 2023. 10. 9. 22:29
반응형

C# 문자열의 여러 문자 바꾸기

줄을 교체하는 더 좋은 방법이 있습니까?

Replace가 문자 배열이나 문자열 배열을 포함하지 않는다는 것이 놀랍습니다.제가 제 확장자를 쓸 수 있을 것 같은데 다음을 더 잘 만들 수 있는 방법이 있는지 궁금했습니다.마지막 바꾸기는 문자가 아닌 문자열입니다.

myString.Replace(';', '\n').Replace(',', '\n').Replace('\r', '\n').Replace('\t', '\n').Replace(' ', '\n').Replace("\n\n", "\n");

대체 정규식을 사용할 수 있습니다.

s/[;,\t\r ]|[\n]{2}/\n/g
  • s/ 앞에 합니다를 합니다.
  • 사이의 .[그리고.]검색할 문자(순서에 상관없이)
  • ./ 및 합니다의 을 지정합니다.

영어로 다음과 같은 내용이 있습니다.

검색:;아니면,아니면\t아니면\r아니면 두 백)백\n다로 할 수 .\n"

수 있습니다. (C# 를 후 에서)System.Text.RegularExpressions)

Regex pattern = new Regex("[;,\t\r ]|[\n]{2}");
pattern.Replace(myString, "\n");

특별히 영리하다고 느껴서 Regex를 사용하고 싶지 않은 경우:

char[] separators = new char[]{' ',';',',','\r','\t','\n'};

string s = "this;is,\ra\t\n\n\ntest";
string[] temp = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
s = String.Join("\n", temp);

이것도 별 힘을 들이지 않고 연장 방식으로 포장할 수 있습니다.

편집:아니면 2분만 기다리면 어차피 내가 쓸게 :)

public static class ExtensionMethods
{
   public static string Replace(this string s, char[] separators, string newVal)
   {
       string[] temp;

       temp = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
       return String.Join( newVal, temp );
   }
}

그리고 voila...

char[] separators = new char[]{' ',';',',','\r','\t','\n'};
string s = "this;is,\ra\t\n\n\ntest";

s = s.Replace(separators, "\n");

다음과 같은 Linq의 Aggregate 함수를 사용할 수 있습니다.

string s = "the\nquick\tbrown\rdog,jumped;over the lazy fox.";
char[] chars = new char[] { ' ', ';', ',', '\r', '\t', '\n' };
string snew = chars.Aggregate(s, (c1, c2) => c1.Replace(c2, '\n'));

확장 방법은 다음과 같습니다.

public static string ReplaceAll(this string seed, char[] chars, char replacementCharacter)
{
    return chars.Aggregate(seed, (str, cItem) => str.Replace(cItem, replacementCharacter));
}

확장 메서드 사용 예:

string snew = s.ReplaceAll(chars, '\n');

이것이 가장 짧은 방법입니다.

myString = Regex.Replace(myString, @"[;,\t\r ]|[\n]{2}", "\n");

아, 공연 공포!답은 좀 구시대적이지만 그래도...

public static class StringUtils
{
    #region Private members

    [ThreadStatic]
    private static StringBuilder m_ReplaceSB;

    private static StringBuilder GetReplaceSB(int capacity)
    {
        var result = m_ReplaceSB;

        if (null == result)
        {
            result = new StringBuilder(capacity);
            m_ReplaceSB = result;
        }
        else
        {
            result.Clear();
            result.EnsureCapacity(capacity);
        }

        return result;
    }


    public static string ReplaceAny(this string s, char replaceWith, params char[] chars)
    {
        if (null == chars)
            return s;

        if (null == s)
            return null;

        StringBuilder sb = null;

        for (int i = 0, count = s.Length; i < count; i++)
        {
            var temp = s[i];
            var replace = false;

            for (int j = 0, cc = chars.Length; j < cc; j++)
                if (temp == chars[j])
                {
                    if (null == sb)
                    {
                        sb = GetReplaceSB(count);
                        if (i > 0)
                            sb.Append(s, 0, i);
                    }

                    replace = true;
                    break;
                }

            if (replace)
                sb.Append(replaceWith);
            else
                if (null != sb)
                    sb.Append(temp);
        }

        return null == sb ? s : sb.ToString();
    }
}

문자열은 불변의 문자 배열일 뿐입니다.

변형 가능하게 만들면 됩니다.

  • StringBuilder
  • 에 들어가다unsafe세계와 놀이 포인터 (그래도 dangerous)

문자 배열을 통해 최소한의 횟수만 반복해보세요참고.HashSet여기서는 루프 내부의 문자 시퀀스를 통과하지 않기 때문입니다.다를 할 수 .HashSetchar준 )array[256]).

StringBuilder 예제

public static void MultiReplace(this StringBuilder builder, 
    char[] toReplace, 
    char replacement)
{
    HashSet<char> set = new HashSet<char>(toReplace);
    for (int i = 0; i < builder.Length; ++i)
    {
        var currentCharacter = builder[i];
        if (set.Contains(currentCharacter))
        {
            builder[i] = replacement;
        }
    }
}

편집 - 최적화된 버전(ASCII에만 유효)

public static void MultiReplace(this StringBuilder builder, 
    char[] toReplace,
    char replacement)
{
    var set = new bool[256];
    foreach (var charToReplace in toReplace)
    {
        set[charToReplace] = true;
    }
    for (int i = 0; i < builder.Length; ++i)
    {
        var currentCharacter = builder[i];
        if (set[currentCharacter])
        {
            builder[i] = replacement;
        }
    }
}

그러면 그냥 이렇게 쓰면 됩니다.

var builder = new StringBuilder("my bad,url&slugs");
builder.MultiReplace(new []{' ', '&', ','}, '-');
var result = builder.ToString();

다음 문자열 확장 메서드를 간단히 작성하여 솔루션 어딘가에 배치할 수도 있습니다.

using System.Text;

public static class StringExtensions
{
    public static string ReplaceAll(this string original, string toBeReplaced, string newValue)
    {
        if (string.IsNullOrEmpty(original) || string.IsNullOrEmpty(toBeReplaced)) return original;
        if (newValue == null) newValue = string.Empty;
        StringBuilder sb = new StringBuilder();
        foreach (char ch in original)
        {
            if (toBeReplaced.IndexOf(ch) < 0) sb.Append(ch);
            else sb.Append(newValue);
        }
        return sb.ToString();
    }

    public static string ReplaceAll(this string original, string[] toBeReplaced, string newValue)
    {
        if (string.IsNullOrEmpty(original) || toBeReplaced == null || toBeReplaced.Length <= 0) return original;
        if (newValue == null) newValue = string.Empty;
        foreach (string str in toBeReplaced)
            if (!string.IsNullOrEmpty(str))
                original = original.Replace(str, newValue);
        return original;
    }
}


그들을 이렇게 부릅니다.

"ABCDE".ReplaceAll("ACE", "xy");

xyBxyDxy


그리고 이것은:

"ABCDEF".ReplaceAll(new string[] { "AB", "DE", "EF" }, "xy");

xyCxyF

RegEx를 사용합니다.대체, 다음과 같은 것입니다.

  string input = "This is   text with   far  too   much   " + 
                 "whitespace.";
  string pattern = "[;,]";
  string replacement = "\n";
  Regex rgx = new Regex(pattern);
  string result = rgx.Replace(input, replacement);

RegEx용MSDN 설명서에 대한 자세한 내용은 다음과 같습니다.바꾸기

정의된 문자열 문자 집합을 특정 문자로 바꾸기 위한 .NET Core 버전입니다.합니다를 합니다.Spanestring.Create방법.

이 아이디어는 교체 배열을 준비하는 것이므로 각 문자열 문자에 대한 실제 비교 작업이 필요하지 않습니다.따라서 교체 프로세스는 상태 기계가 작동하는 방식을 상기시킵니다.합니다를 oldChar ^ newChar여기서 (XOR'ed) 가치를 평가하면 다음과 같은 이점이 있습니다.

  • :ch ^ ch = 0되지 않은 항목을 -
  • 는 XOR'ing 에서 수 .ch ^ repl[ch]:
    • ch ^ 0 = ch되지 않은 -
    • ch ^ (ch ^ newChar) = newChar ar-

따라서 초기화 시 교체 어레이가 제로-에이드(zero-ed) 상태인지 확인하는 것만이 유일한 요구 사항입니다.저희가 사용할 것입니다.ArrayPool<char>다 을 피하기 .ReplaceAll메소드라고 합니다.합니다에 할 수 보장합니다.Array.Clear드 입니다ReplaceAll방법. 만 해당)을 교체 배열(정확한 항목만 해당)을 삭제한 후 풀로 반환합니다.

public static class StringExtensions
{
    private static readonly ArrayPool<char> _replacementPool = ArrayPool<char>.Create();

    public static string ReplaceAll(this string str, char newChar, params char[] oldChars)
    {
        // If nothing to do, return the original string.
        if (string.IsNullOrEmpty(str) ||
            oldChars is null ||
            oldChars.Length == 0)
        {
            return str;
        }

        // If only one character needs to be replaced,
        // use the more efficient `string.Replace`.
        if (oldChars.Length == 1)
        {
            return str.Replace(oldChars[0], newChar);
        }

        // Get a replacement array from the pool.
        var replacements = _replacementPool.Rent(char.MaxValue + 1);

        try
        {
            // Intialize the replacement array in the way that
            // all elements represent `oldChar ^ newChar`.
            foreach (var oldCh in oldChars)
            {
                replacements[oldCh] = (char)(newChar ^ oldCh);
            }

            // Create a string with replaced characters.
            return string.Create(str.Length, (str, replacements), (dst, args) =>
            {
                var repl = args.replacements;

                foreach (var ch in args.str)
                {
                    dst[0] = (char)(repl[ch] ^ ch);
                    dst = dst.Slice(1);
                }
            });
        }
        finally
        {
            // Clear the replacement array.
            foreach (var oldCh in oldChars)
            {
                replacements[oldCh] = char.MinValue;
            }

            // Return the replacement array back to the pool.
            _replacementPool.Return(replacements);
        }
    }
}

이 질문이 아주 오래된 질문이라는 것을 알지만, 더 효율적인 두 가지 옵션을 제공하고자 합니다.

우선 폴 월스가 올린 확장 방법은 좋지만 스트링 데이터 타입과 같지만 스트링 값을 두 번 이상 변경할 상황에 맞게 특별히 만들어진 스트링빌더 클래스를 사용하면 더욱 효율적으로 만들 수 있습니다.StringBuilder를 사용한 확장 방법으로 만든 버전은 다음과 같습니다.

public static string ReplaceChars(this string s, char[] separators, char newVal)
{
    StringBuilder sb = new StringBuilder(s);
    foreach (var c in separators) { sb.Replace(c, newVal); }
    return sb.ToString();
}

저는 이 작업을 100,000번 실행했고 StringBuilder를 사용한 경우가 81ms였던 것에 비해 73ms가 걸렸습니다.따라서 많은 작업을 실행하거나 큰 문자열을 사용하지 않는 한 일반적으로 그 차이는 무시할 수 있습니다.

두 번째로 사용할 수 있는 1 라이너 루프는 다음과 같습니다.

foreach (char c in separators) { s = s.Replace(c, '\n'); }

저는 개인적으로 이게 최선의 선택이라고 생각합니다.효율성이 높고 확장 방법을 쓸 필요가 없습니다.제가 테스트한 결과 이것은 단지 63ms 만에 100k 반복을 실행하여 가장 효율적이었습니다.상황에 맞는 예는 다음과 같습니다.

string s = "this;is,\ra\t\n\n\ntest";
char[] separators = new char[] { ' ', ';', ',', '\r', '\t', '\n' };
foreach (char c in separators) { s = s.Replace(c, '\n'); }

이 예제의 첫 두 줄에 대해 Paul Walls에게 공을 돌립니다.

성능 면에서는 이 방법이 최선의 해결책은 아닐 수 있지만 효과는 있습니다.

var str = "filename:with&bad$separators.txt";
char[] charArray = new char[] { '#', '%', '&', '{', '}', '\\', '<', '>', '*', '?', '/', ' ', '$', '!', '\'', '"', ':', '@' };
foreach (var singleChar in charArray)
{
   str = str.Replace(singleChar, '_');
}
string ToBeReplaceCharacters = @"~()@#$%&amp;+,'&quot;&lt;&gt;|;\/*?";
string fileName = "filename;with<bad:separators?";

foreach (var RepChar in ToBeReplaceCharacters)
{
    fileName = fileName.Replace(RepChar.ToString(), "");
}

저는 또한 그 문제를 만지작거렸고, 이곳의 대부분의 해결책이 매우 느리다는 것을 발견했습니다.가장 빠른 방법은 실제로 dodgy_coder가 게시한 LINQ + Aggregate 방법이었습니다.

하지만 오래된 캐릭터의 수에 따라 메모리 할당이 상당히 무거울 수도 있다고 생각했습니다.그래서 이걸 들고 나왔습니다.

여기서는 현재 스레드에 대한 이전 문자의 캐시된 대체 맵을 안전하게 할당하는 것이 좋습니다.그 외에는 나중에 입력의 문자 배열을 사용하여 작업하는 것이 다시 문자열로 반환됩니다.반면 문자 배열은 가능한 적게 수정됩니다.

[ThreadStatic]
private static bool[] replaceMap;
public static string Replace(this string input, char[] oldChars, char newChar)
{
    if (input == null) throw new ArgumentNullException(nameof(input));
    if (oldChars == null) throw new ArgumentNullException(nameof(oldChars));
    if (oldChars.Length == 1) return input.Replace(oldChars[0], newChar);
    if (oldChars.Length == 0) return input;

    replaceMap = replaceMap ?? new bool[char.MaxValue + 1];
    foreach (var oldChar in oldChars)
    {
        replaceMap[oldChar] = true;
    }

    try
    {
        var count = input.Length;
        var output = input.ToCharArray();
        for (var i = 0; i < count; i++)
        {
            if (replaceMap[input[i]])
            {
                output[i] = newChar;
            }
        }

        return new string(output);
    }
    finally
    {
        foreach (var oldChar in oldChars)
        {
            replaceMap[oldChar] = false;
        }
    }
}

저는 실제 입력 문자열이 작동하려면 많아야 두 번의 할당이 필요합니다. AStringBuilder어떤 이유에서인지는 몰라도 제가 훨씬 느리다는 걸 알게 됐어요.그리고 그것은 LINQ 변형보다 2배나 빠릅니다.

"바꾸기" 없음(Linq만 해당):

    string myString = ";,\r\t \n\n=1;;2,,3\r\r4\t\t5  6\n\n\n\n7=";
    char NoRepeat = '\n';
    string ByeBye = ";,\r\t ";
    string myResult = myString.ToCharArray().Where(t => !"STOP-OUTSIDER".Contains(t))
                 .Select(t => "" + ( ByeBye.Contains(t) ? '\n' : t))
                  .Aggregate((all, next) => (
                      next == "" + NoRepeat && all.Substring(all.Length - 1) == "" + NoRepeat
                      ? all : all  + next ) );

나만의 솔루션을 구축하고 여기서 사용하는 솔루션을 살펴보니 복잡한 코드를 사용하지 않고 대부분의 파라미터에 대해 일반적으로 효율적인 답변을 활용했습니다.

  1. 다른 방법이 더 적합한 경우를 기준으로 합니다.교체할 문자가 없으면 원래 문자열을 반환합니다.한 개만 있는 경우 Replace 방법을 사용합니다.
  2. StringBuilder를 사용하고 용량을 원래 문자열 길이로 초기화합니다.결국, 새로 만들어지는 문자열은 문자만 교체된다면 원래 문자열의 길이와 같습니다.이렇게 하면 새 문자열에 메모리 할당이 1개만 사용됩니다.
  3. 'char' 길이가 작거나 클 수 있다고 가정하면 성능에 영향을 미칩니다.해시셋은 큰 컬렉션이 더 나은 반면 작은 컬렉션은 그렇지 않습니다.이것은 하이브리드 사전의 거의 완벽한 사용 사례입니다.컬렉션이 너무 커지면 해시 기반 룩업으로 전환합니다.하지만 우리는 사전의 가치를 신경쓰지 않기 때문에 그냥 "true"로 설정했습니다.
  4. StringBuilder 절에 다른 방법을 사용하면 불필요한 메모리 할당을 방지할 수 있습니다.문자열일 경우 기본 케이스를 선택하지 않는 한 StringBuilder를 인스턴스화하지 마십시오.이미 StringBuilder인 경우 교체를 수행하고 StringBuilder 자체를 반환합니다(다른 StringBuilder 메서드와 같이).Append하다).
  5. 교체용 차를 먼저 넣고, 마지막에 확인할 차를 넣었습니다.이런 식으로, 내가 그들을 이용할 수 있는params추가 문자열을 쉽게 전달할 수 있는 키워드입니다.하지만 다른 주문을 원하신다면 이렇게 하지 않으셔도 됩니다.
namespace Test.Extensions
{
    public static class StringExtensions
    {
        public static string ReplaceAll(this string str, char replacementCharacter, params char[] chars)
        {
            if (chars.Length == 0)
                return str;

            if (chars.Length == 1)
                return str.Replace(chars[0], replacementCharacter);

            StringBuilder sb = new StringBuilder(str.Length);

            var searcher = new HybridDictionary(chars.Length);
            for (int i = 0; i < chars.Length; i++)
                searcher[chars[i]] = true;

            foreach (var c in str)
            {
                if (searcher.Contains(c))
                    sb.Append(replacementCharacter);
                else
                    sb.Append(c);
            }

            return sb.ToString();
        }

        public static StringBuilder ReplaceAll(this StringBuilder sb, char replacementCharacter, params char[] chars)
        {
            if (chars.Length == 0)
                return sb;

            if (chars.Length == 1)
                return sb.Replace(chars[0], replacementCharacter);

            var searcher = new HybridDictionary(chars.Length);
            for (int i = 0; i < chars.Length; i++)
                searcher[chars[i]] = true;

            for (int i = 0; i < sb.Length; i++)
            {
                var val = sb[i];
                if (searcher.Contains(val))
                    sb[i] = replacementCharacter;
            }

            return sb;
        }
    }
}

단일 또는 다중 값의 단일 패스 교체

public static class StringExtensions
{
    public static string ReplaceTokens(this string input, Dictionary<char, string> tokenReplacement)
    {
        if (string.IsNullOrEmpty(input) ||
            tokenReplacement is null ||
            tokenReplacement.Count == 0)
        {
            return input;
        }
        if (tokenReplacement.Count == 1)
        {
            return input.Replace(tokenReplacement.Keys.First().ToString(), tokenReplacement.Values.First());
        }
        StringBuilder output = new StringBuilder(input.Length);
        foreach (char c in input)
        {
            if (tokenReplacement.TryGetValue(c, out string token))
            {
                output.Append(token);
            }
            else
            {
                output.Append(c);
            }
        }

        return output.ToString();
    }
    public static string ReplaceTokens(this string input, char[] tokenReplacement, string replaceWith)
    {
        if (string.IsNullOrEmpty(input) ||
            tokenReplacement is null ||
            tokenReplacement.Length == 0)
        {
            return input;
        }

        if (tokenReplacement.Length == 1)
        {
            return input.Replace(tokenReplacement[0].ToString(), replaceWith ?? String.Empty);
        }
        var splits = input.Split(tokenReplacement);
        return string.Join(replaceWith ?? String.Empty, splits);
    }
}

언급URL : https://stackoverflow.com/questions/7265315/replace-multiple-characters-in-a-c-sharp-string

반응형