Zastąp wiele znaków w ciągu C #


178

Czy istnieje lepszy sposób na zastąpienie ciągów?

Dziwię się, że funkcja Replace nie przyjmuje tablicy znaków ani tablicy ciągów. Wydaje mi się, że mógłbym napisać własne rozszerzenie, ale byłem ciekaw, czy istnieje lepszy sposób na wykonanie następujących czynności? Zauważ, że ostatnia zamiana jest łańcuchem, a nie znakiem.

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

Odpowiedzi:


206

Możesz użyć zastępującego wyrażenia regularnego.

s/[;,\t\r ]|[\n]{2}/\n/g
  • s/ na początku oznacza poszukiwanie
  • Znaki między [i ]to znaki do wyszukania (w dowolnej kolejności)
  • Drugi /oddziela wyszukiwany tekst i tekst zastępujący

W języku angielskim brzmi to:

„Wyszukaj ;lub ,lub \tlub \rlub (spacja) lub dokładnie dwa kolejne \ni zamień je na \n

W C # możesz wykonać następujące czynności: (po zaimportowaniu System.Text.RegularExpressions)

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

2
\ti \rsą zawarte w \s. Więc twoje wyrażenie regularne jest równoważne [;,\s].
NullUserException,

3
W \srzeczywistości jest to równoważne z [ \f\n\r\t\v]załączeniem pewnych rzeczy, których nie było w pierwotnym pytaniu. Dodatkowo pierwotne pytanie dotyczy tego, Replace("\n\n", "\n")którego wyrażenie regularne nie obsługuje.
NullUserException

11
Proszę wziąć pod uwagę, że dla prostych operacji zamiany, które nie są konfigurowalne przez użytkownika, użycie wyrażeń regularnych nie jest optymalne, ponieważ jest bardzo wolne w porównaniu do zwykłych operacji na łańcuchach, zgodnie z pierwszym artykułem dotyczącym testów porównawczych, które znalazłem podczas wyszukiwania "c # regex performance replace" to około 13 razy wolniej.
zbyt

Ach regex, hieroglify mocy! Jedynym problemem, jaki tu widzę, jest czytelność wyrażeń regularnych przez człowieka; wielu nie chce ich zrozumieć. Niedawno dodałem poniżej rozwiązanie dla tych, którzy szukają mniej złożonej alternatywy.
sɐunıɔ ןɐ qɐp

Jak więc piszemy, jeśli chcemy zastąpić wiele znaków wieloma znakami?
Habip Oğuz

114

Jeśli czujesz się szczególnie mądry i nie chcesz używać 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);

Niewielkim wysiłkiem można by również opakować to w metodę rozszerzającą.

Edycja: Lub po prostu poczekaj 2 minuty, a mimo to napiszę to :)

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

I voila ...

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

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

Bardzo mało wydajna pamięć, szczególnie w przypadku większych ciągów.
MarcinJuraszek

@MarcinJuraszek Lol ... To chyba pierwszy raz, kiedy słyszałem, jak ktoś twierdzi, że wbudowane metody łańcuchowe są mniej wydajne w pamięci niż wyrażenia regularne.
Paul Walls

10
Masz rację. Powinienem był zmierzyć, zanim to opublikowałem. Uruchamiam benchmark i jestem Regex.Replaceponad 8 razy wolniejszy niż wiele string.Replacepołączeń z rzędu. i 4x wolniej niż Split+ Join. Zobacz gist.github.com/MarcinJuraszek/c1437d925548561ba210a1c6ed144452
MarcinJuraszek

1
Niezłe rozwiązanie! tylko mały dodatek. Niestety, to nie zadziała, jeśli chcesz, aby również zostały zastąpione pierwsze znaki. Załóżmy, że chcesz zamienić znak „t” w przykładowym ciągu. Metoda Split po prostu usunie to „t” z pierwszego słowa „this”, ponieważ jest to EmptyEntry. Jeśli użyjesz StringSplitOptions.None zamiast RemoveEmptyEntries, Split pozostawi wpis, a metoda Join doda zamiast tego znak separatora. Mam nadzieję, że to pomoże
Pierre

58

Możesz użyć funkcji Aggregate Linq:

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

Oto metoda rozszerzenia:

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

Przykład użycia metody rozszerzenia:

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

21

To jest najkrótsza droga:

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

1
Ta jedna linijka pomaga również, gdy potrzebujesz tego w inicjalizatorach.
Guney Ozsan

8

Och, horror wydajności! Odpowiedź jest nieco przestarzała, ale nadal ...

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

7

Łańcuchy są po prostu niezmiennymi tablicami znaków

Musisz tylko uczynić go zmiennym:

  • albo za pomocą StringBuilder
  • idź w unsafeświat i baw się wskazówkami (choć niebezpieczne)

i spróbuj powtórzyć tablicę znaków najmniej razy. Zwróć uwagę na to HashSet, ponieważ pozwala uniknąć przechodzenia przez sekwencję znaków wewnątrz pętli. Jeśli potrzebujesz jeszcze szybszego wyszukiwania, możesz zastąpić HashSetwyszukiwanie zoptymalizowane dla char(na podstawie array[256]).

Przykład z 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;
        }
    }
}

Edycja - wersja zoptymalizowana

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

Następnie po prostu użyj tego w ten sposób:

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

Pamiętaj, że ciągi wchar_tznaków znajdują się w .net, zastępujesz tylko podzbiór wszystkich możliwych znaków (i będziesz potrzebować 65536 znaków bools, aby to zoptymalizować ...)
gog

3

Możesz także po prostu napisać te metody rozszerzające ciągi i umieścić je gdzieś w swoim rozwiązaniu:

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


Nazwij ich tak:

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

xyBxyDxy


I to:

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

xyCxyF



1

Pod względem wydajności może to nie być najlepsze rozwiązanie, ale działa.

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

1
string ToBeReplaceCharacters = @"~()@#$%&amp;+,'&quot;&lt;&gt;|;\/*?";
string fileName = "filename;with<bad:separators?";

foreach (var RepChar in ToBeReplaceCharacters)
{
    fileName = fileName.Replace(RepChar.ToString(), "");
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.