Usuń znaki z ciągu C #


150

Jak mogę usunąć znaki z ciągu? Na przykład:"My name @is ,Wan.;'; Wan" .

Chciałbym usunąć znaki '@', ',', '.', ';', '\''z tego ciągu, aby stał się"My name is Wan Wan"

Odpowiedzi:


177
var str = "My name @is ,Wan.;'; Wan";
var charsToRemove = new string[] { "@", ",", ".", ";", "'" };
foreach (var c in charsToRemove)
{
    str = str.Replace(c, string.Empty);
}

Ale mogę zasugerować inne podejście, jeśli chcesz usunąć wszystkie znaki niebędące literami

var str = "My name @is ,Wan.;'; Wan";
str = new string((from c in str
                  where char.IsWhiteSpace(c) || char.IsLetterOrDigit(c)
                  select c
       ).ToArray());

12
Można to również zrobić w ten sposób, str = new string (str.Where (x => char.IsWhiteSpace (x) || char.IsLetterOrDigit (x)). ToArray ());
Adnan Bhatti

1
Musiałem to sprawdzić, string.Empty nie tworzy łańcucha do porównania, więc jest bardziej wydajne niż "". ( stackoverflow.com/questions/151472/… )
Tom Cerul

6
Czy tylko ja otrzymuję „Argument 2: nie można przekonwertować z„ ciągu ”na„ znak ”” z ciągu.Empty?
OddDev

2
@OddDev powinieneś otrzymać ten błąd tylko wtedy, gdy twoja tablica, przez którą przechodzisz, jest listą znaków. Jeśli są to łańcuchy, to powinno działać
Newteq Developer

3
Należy również zauważyć, że aby funkcja „str.Replace” działała poprawnie, pierwszy parametr musi być ciągiem, jeśli chcesz użyć string.Empty jako drugiego parametru. Jeśli użyjesz znaku char (tj. „A”) jako pierwszego parametru, będziesz również potrzebować znaku jako drugiego. W przeciwnym razie zostanie wyświetlony błąd „Argument 2: nie można przekonwertować z„ ciągu ”na„ znak ”” wspomniany powyżej przez @OddDev
Leo

68

Prosty:

String.Join("", "My name @is ,Wan.;'; Wan".Split('@', ',' ,'.' ,';', '\''));

64

Brzmi jak idealna aplikacja dla RegEx - silnika zaprojektowanego do szybkiej manipulacji tekstem. W tym przypadku:

Regex.Replace("He\"ll,o Wo'r.ld", "[@,\\.\";'\\\\]", string.Empty)

3
Wydaje się, że byłoby to znacznie bardziej wydajne niż podejście oparte na iteratorze, zwłaszcza jeśli możesz użyć skompilowanego Regex;
Ade Miller,

To powinna być akceptowana odpowiedź, zwłaszcza że, jak powiedział @AdeMiller, będzie znacznie wydajniejsza.
Obsidian,

14
To nie jest szybsze niż pętla, to powszechne błędne przekonanie, że wyrażenia regularne są zawsze szybsze niż pętle. Regexy nie są magiczne, w ich rdzeniu muszą w pewnym momencie iterować po ciągu, aby wykonać swoje operacje, i mogą być znacznie wolniejsze z narzutami z samego wyrażenia regularnego. Naprawdę wyróżniają się, jeśli chodzi o niezwykle złożoną manipulację, w której potrzebne byłyby dziesiątki linii kodu i wiele pętli. Testując skompilowaną wersję tego wyrażenia regularnego względem prostej niezoptymalizowanej pętli 50000 razy, wyrażenie regularne jest 6X wolniejsze.
Tony Cheetham

A co z wydajnością pamięci? Czy wyrażenia regularne nie będą wydajniejsze w sensie alokacji nowych ciągów?
Marek

2
Być może pomyliłem się, twierdząc, że RegEx jest szybki. O ile nie było to centrum bardzo ścisłej pętli, a następnie innych względów, taka czytelność i łatwość konserwacji prawdopodobnie przeważą nad wydajnością w przypadku małej operacji, takiej jak ta.
John Melville,

21

Mniej specyficzne dla twojego pytania, możliwe jest usunięcie WSZYSTKICH znaków interpunkcyjnych z ciągu (z wyjątkiem spacji), wypisując na biało dopuszczalne znaki w wyrażeniu regularnym:

string dirty = "My name @is ,Wan.;'; Wan";

// only space, capital A-Z, lowercase a-z, and digits 0-9 are allowed in the string
string clean = Regex.Replace(dirty, "[^A-Za-z0-9 ]", "");

Zauważ, że po 9 jest spacja, aby nie usuwać spacji z zdania. Trzeci argument to pusty ciąg, który służy do zastąpienia dowolnego podłańcucha, który nie należy do wyrażenia regularnego.


19

Porównywanie różnych sugestii (a także porównywanie w kontekście zamienników jednoznakowych z różnymi rozmiarami i położeniami celu).

W tym konkretnym przypadku rozdzielenie celów i dołączenie do zamienników (w tym przypadku pustego ciągu) jest najszybsze co najmniej o współczynnik 3. Ostatecznie wydajność różni się w zależności od liczby zamian, w których znajdują się zastąpienia. źródło i rozmiar źródła. #ymmv

Wyniki

(pełne wyniki tutaj )

| Test                      | Compare | Elapsed                                                            |
|---------------------------|---------|--------------------------------------------------------------------|
| SplitJoin                 | 1.00x   | 29023 ticks elapsed (2.9023 ms) [in 10K reps, 0.00029023 ms per]   |
| Replace                   | 2.77x   | 80295 ticks elapsed (8.0295 ms) [in 10K reps, 0.00080295 ms per]   |
| RegexCompiled             | 5.27x   | 152869 ticks elapsed (15.2869 ms) [in 10K reps, 0.00152869 ms per] |
| LinqSplit                 | 5.43x   | 157580 ticks elapsed (15.758 ms) [in 10K reps, 0.0015758 ms per]   |
| Regex, Uncompiled         | 5.85x   | 169667 ticks elapsed (16.9667 ms) [in 10K reps, 0.00169667 ms per] |
| Regex                     | 6.81x   | 197551 ticks elapsed (19.7551 ms) [in 10K reps, 0.00197551 ms per] |
| RegexCompiled Insensitive | 7.33x   | 212789 ticks elapsed (21.2789 ms) [in 10K reps, 0.00212789 ms per] |
| Regex Insentive           | 7.52x   | 218164 ticks elapsed (21.8164 ms) [in 10K reps, 0.00218164 ms per] |

Wiązka testowa (LinqPad)

(uwaga: Perfi Vsrozszerzeniami czasu, które napisałem )

void test(string title, string sample, string target, string replacement) {
    var targets = target.ToCharArray();

    var tox = "[" + target + "]";
    var x = new Regex(tox);
    var xc = new Regex(tox, RegexOptions.Compiled);
    var xci = new Regex(tox, RegexOptions.Compiled | RegexOptions.IgnoreCase);

    // no, don't dump the results
    var p = new Perf/*<string>*/();
        p.Add(string.Join(" ", title, "Replace"), n => targets.Aggregate(sample, (res, curr) => res.Replace(new string(curr, 1), replacement)));
        p.Add(string.Join(" ", title, "SplitJoin"), n => String.Join(replacement, sample.Split(targets)));
        p.Add(string.Join(" ", title, "LinqSplit"), n => String.Concat(sample.Select(c => targets.Contains(c) ? replacement : new string(c, 1))));
        p.Add(string.Join(" ", title, "Regex"), n => Regex.Replace(sample, tox, replacement));
        p.Add(string.Join(" ", title, "Regex Insentive"), n => Regex.Replace(sample, tox, replacement, RegexOptions.IgnoreCase));
        p.Add(string.Join(" ", title, "Regex, Uncompiled"), n => x.Replace(sample, replacement));
        p.Add(string.Join(" ", title, "RegexCompiled"), n => xc.Replace(sample, replacement));
        p.Add(string.Join(" ", title, "RegexCompiled Insensitive"), n => xci.Replace(sample, replacement));

    var trunc = 40;
    var header = sample.Length > trunc ? sample.Substring(0, trunc) + "..." : sample;

    p.Vs(header);
}

void Main()
{
    // also see /programming/7411438/remove-characters-from-c-sharp-string

    "Control".Perf(n => { var s = "*"; });


    var text = "My name @is ,Wan.;'; Wan";
    var clean = new[] { '@', ',', '.', ';', '\'' };

    test("stackoverflow", text, string.Concat(clean), string.Empty);


    var target = "o";
    var f = "x";
    var replacement = "1";

    var fillers = new Dictionary<string, string> {
        { "short", new String(f[0], 10) },
        { "med", new String(f[0], 300) },
        { "long", new String(f[0], 1000) },
        { "huge", new String(f[0], 10000) }
    };

    var formats = new Dictionary<string, string> {
        { "start", "{0}{1}{1}" },
        { "middle", "{1}{0}{1}" },
        { "end", "{1}{1}{0}" }
    };

    foreach(var filler in fillers)
    foreach(var format in formats) {
        var title = string.Join("-", filler.Key, format.Key);
        var sample = string.Format(format.Value, target, filler.Value);

        test(title, sample, target, replacement);
    }
}

1
Wreszcie kilka liczb! Dobra robota @drzaus!
Marek



6

Kolejne proste rozwiązanie:

var forbiddenChars = @"@,.;'".ToCharArray();
var dirty = "My name @is ,Wan.;'; Wan";
var clean = new string(dirty.Where(c => !forbiddenChars.Contains(c)).ToArray());


4

Łańcuch jest po prostu tablicą znaków, więc użyj Linq, aby wykonać zamianę (podobnie jak Albin powyżej, z wyjątkiem tego, że używa linq zawiera instrukcję do wykonania zamiany):

var resultString = new string(
        (from ch in "My name @is ,Wan.;'; Wan"
         where ! @"@,.;\'".Contains(ch)
         select ch).ToArray());

Pierwszy ciąg to łańcuch, który ma zastąpić znaki, a drugi to prosty łańcuch zawierający znaki


Rozwiązanie Albina Linq jest prawdopodobnie lepsze, chyba że istnieją dodatkowe znaki, które chcesz odfiltrować (nie zakryte białymi znakami, literami i cyframi).
alistair

3

Równie dobrze mogę to wyrzucić tutaj.

Utwórz rozszerzenie, aby usunąć znaki z ciągu:

public static string RemoveChars(this string input, params char[] chars)
{
    var sb = new StringBuilder();
    for (int i = 0; i < input.Length; i++)
    {
        if (!chars.Contains(input[i]))
            sb.Append(input[i]);
    }
    return sb.ToString();
}

I to jest użyteczne w następujący sposób:

string str = "My name @is ,Wan.;'; Wan";
string cleanedUpString = str.RemoveChars('@', ',', '.', ';', '\'');

Lub tak po prostu:

string str = "My name @is ,Wan.;'; Wan".RemoveChars('@', ',', '.', ';', '\'');

Jest to najlepsze rozwiązanie, ponieważ zapewnia najmniejszą liczbę alokacji pamięci. Ustawiłbym również długość oryginalnego ciągu jako początkową pojemność konstruktora ciągów, na przykład: new StringBuilder (input.Length) w tym celu, aby mieć jak najmniejszą liczbę alokacji pamięci.
treaschf

3

Wydaje się, że najkrótszym sposobem jest połączenie LINQ i string.Concat:

var input = @"My name @is ,Wan.;'; Wan";
var chrs = new[] {'@', ',', '.', ';', '\''};
var result = string.Concat(input.Where(c => !chrs.Contains(c)));
// => result = "My name is Wan Wan" 

Zobacz demo C # . Zauważ, że string.Concatjest to skrót dostring.Join("", ...) .

Zauważ, że użycie wyrażenia regularnego do usunięcia poszczególnych znanych znaków jest nadal możliwe do dynamicznego budowania, chociaż uważa się, że wyrażenie regularne jest wolniejsze. Jednak tutaj jest sposób na zbudowanie takiego dynamicznego wyrażenia regularnego (gdzie wszystko, czego potrzebujesz, to klasa postaci):

var pattern = $"[{Regex.Escape(new string(chrs))}]+";
var result = Regex.Replace(input, pattern, string.Empty);

Zobacz inne demo C # . Regex będzie wyglądać [@,\.;']+(jednej pasującej lub więcej ( +) kolejnych wystąpień @, ,, ., ;lub 'znaków), gdzie kropka nie musi być uciekł, ale Regex.Escapebędzie konieczne, aby uniknąć inne znaki, które muszą być ocalałem, jak \, ^, ]lub -, których pozycja wewnątrz klasy postaci, której nie możesz przewidzieć.



3

Oto metoda, którą napisałem, która ma nieco inne podejście. Zamiast określać znaki do usunięcia, mówię mojej metodzie, które znaki chcę zachować - spowoduje to usunięcie wszystkich innych znaków.

W przykładzie OP chce zachować tylko znaki alfabetu i spacje. Oto jak wyglądałoby wywołanie mojej metody ( C # demo ):

var str = "My name @is ,Wan.;'; Wan";

// "My name is Wan Wan"
var result = RemoveExcept(str, alphas: true, spaces: true);

Oto moja metoda:

/// <summary>
/// Returns a copy of the original string containing only the set of whitelisted characters.
/// </summary>
/// <param name="value">The string that will be copied and scrubbed.</param>
/// <param name="alphas">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="numerics">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="dashes">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="underlines">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="spaces">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="periods">If true, all decimal characters (".") will be preserved; otherwise, they will be removed.</param>
public static string RemoveExcept(string value, bool alphas = false, bool numerics = false, bool dashes = false, bool underlines = false, bool spaces = false, bool periods = false) {
    if (string.IsNullOrWhiteSpace(value)) return value;
    if (new[] { alphas, numerics, dashes, underlines, spaces, periods }.All(x => x == false)) return value;

    var whitelistChars = new HashSet<char>(string.Concat(
        alphas ? "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" : "",
        numerics ? "0123456789" : "",
        dashes ? "-" : "",
        underlines ? "_" : "",
        periods ? "." : "",
        spaces ? " " : ""
    ).ToCharArray());

    var scrubbedValue = value.Aggregate(new StringBuilder(), (sb, @char) => {
        if (whitelistChars.Contains(@char)) sb.Append(@char);
        return sb;
    }).ToString();

    return scrubbedValue;
}

Świetna odpowiedź!
edtheprogrammerguy

Bardzo dobrze! ciąg liczbowy ma 0 dwukrotnie.
John Kurtz

@JohnKurtz Niezły chwyt - już go nie ma.
Mass Dot Net

2

Wiele dobrych odpowiedzi tutaj, oto mój dodatek wraz z kilkoma testami jednostkowymi, które mogą pomóc w testowaniu poprawności, moje rozwiązanie jest podobne do powyższego @ Rianne, ale używa ISet, aby zapewnić czas wyszukiwania O (1) dla znaków zastępczych (a także podobny do rozwiązania Linq @Albin Sunnanbo).

    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Returns a string with the specified characters removed.
    /// </summary>
    /// <param name="source">The string to filter.</param>
    /// <param name="removeCharacters">The characters to remove.</param>
    /// <returns>A new <see cref="System.String"/> with the specified characters removed.</returns>
    public static string Remove(this string source, IEnumerable<char> removeCharacters)
    {
        if (source == null)
        {
            throw new  ArgumentNullException("source");
        }

        if (removeCharacters == null)
        {
            throw new ArgumentNullException("removeCharacters");
        }

        // First see if we were given a collection that supports ISet
        ISet<char> replaceChars = removeCharacters as ISet<char>;

        if (replaceChars == null)
        {
            replaceChars = new HashSet<char>(removeCharacters);
        }

        IEnumerable<char> filtered = source.Where(currentChar => !replaceChars.Contains(currentChar));

        return new string(filtered.ToArray());
    }

Testy NUnit (2.6+) tutaj

using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;

[TestFixture]
public class StringExtensionMethodsTests
{
    [TestCaseSource(typeof(StringExtensionMethodsTests_Remove_Tests))]
    public void Remove(string targetString, IEnumerable<char> removeCharacters, string expected)
    {
        string actual = StringExtensionMethods.Remove(targetString, removeCharacters);

        Assert.That(actual, Is.EqualTo(expected));
    }

    [TestCaseSource(typeof(StringExtensionMethodsTests_Remove_ParameterValidation_Tests))]
    public void Remove_ParameterValidation(string targetString, IEnumerable<char> removeCharacters)
    {
        Assert.Throws<ArgumentNullException>(() => StringExtensionMethods.Remove(targetString, removeCharacters));
    }
}

internal class StringExtensionMethodsTests_Remove_Tests : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        yield return new TestCaseData("My name @is ,Wan.;'; Wan", new char[] { '@', ',', '.', ';', '\'' }, "My name is Wan Wan").SetName("StringUsingCharArray");
        yield return new TestCaseData("My name @is ,Wan.;'; Wan", new HashSet<char> { '@', ',', '.', ';', '\'' }, "My name is Wan Wan").SetName("StringUsingISetCollection");
        yield return new TestCaseData(string.Empty, new char[1], string.Empty).SetName("EmptyStringNoReplacementCharactersYieldsEmptyString");
        yield return new TestCaseData(string.Empty, new char[] { 'A', 'B', 'C' }, string.Empty).SetName("EmptyStringReplacementCharsYieldsEmptyString");
        yield return new TestCaseData("No replacement characters", new char[1], "No replacement characters").SetName("StringNoReplacementCharactersYieldsString");
        yield return new TestCaseData("No characters will be replaced", new char[] { 'Z' }, "No characters will be replaced").SetName("StringNonExistantReplacementCharactersYieldsString");
        yield return new TestCaseData("AaBbCc", new char[] { 'a', 'C' }, "ABbc").SetName("CaseSensitivityReplacements");
        yield return new TestCaseData("ABC", new char[] { 'A', 'B', 'C' }, string.Empty).SetName("AllCharactersRemoved");
        yield return new TestCaseData("AABBBBBBCC", new char[] { 'A', 'B', 'C' }, string.Empty).SetName("AllCharactersRemovedMultiple");
        yield return new TestCaseData("Test That They Didn't Attempt To Use .Except() which returns distinct characters", new char[] { '(', ')' }, "Test That They Didn't Attempt To Use .Except which returns distinct characters").SetName("ValidateTheStringIsNotJustDistinctCharacters");
    }
}

internal class StringExtensionMethodsTests_Remove_ParameterValidation_Tests : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        yield return new TestCaseData(null, null);
        yield return new TestCaseData("valid string", null);
        yield return new TestCaseData(null, new char[1]);
    }
}

2

Jest to potężna metoda, której zwykle używam w tym samym przypadku:

private string Normalize(string text)
{
        return string.Join("",
            from ch in text
            where char.IsLetterOrDigit(ch) || char.IsWhiteSpace(ch)
            select ch);
}

Cieszyć się...


1

Old School in place copy / tomp:

  private static string RemoveDirtyCharsFromString(string in_string)
     {
        int index = 0;
        int removed = 0;

        byte[] in_array = Encoding.UTF8.GetBytes(in_string);

        foreach (byte element in in_array)
        {
           if ((element == ' ') ||
               (element == '-') ||
               (element == ':'))
           {
              removed++;
           }
           else
           {
              in_array[index] = element;
              index++;
           }
        }

        Array.Resize<byte>(ref in_array, (in_array.Length - removed));
        return(System.Text.Encoding.UTF8.GetString(in_array, 0, in_array.Length));
     }

Nie jestem pewien co do wydajności innych metod (tj. Narzut wszystkich wywołań funkcji i wystąpień, które występują jako efekt uboczny w wykonywaniu C #).


1

Robię to metodą rozszerzającą, a przy tablicy ciągów myślę, że string[]jest bardziej przydatna niż char[]dlatego, że char może być również ciągiem:

public static class Helper
{
    public static string RemoverStrs(this string str, string[] removeStrs)
    {
        foreach (var removeStr in removeStrs)
            str = str.Replace(removeStr, "");
        return str;
    }
}

wtedy możesz go używać wszędzie:

string myname = "My name @is ,Wan.;'; Wan";
string result = myname.RemoveStrs(new[]{ "@", ",", ".", ";", "\\"});

1

Musiałem usunąć znaki specjalne z pliku XML. Oto jak to zrobiłem. char.ToString () jest bohaterem w tym kodzie.

string item = "<item type="line" />"
char DC4 = (char)0x14;
string fixed = item.Replace(DC4.ToString(), string.Empty);

1
new[] { ',', '.', ';', '\'', '@' }
.Aggregate("My name @is ,Wan.;'; Wan", (s, c) => s.Replace(c.ToString(), string.Empty)); 

1

Biorąc dane dotyczące wydajności z @drzaus, oto metoda rozszerzenia korzystająca z najszybszego algorytmu.

public static class StringEx
{
    public static string RemoveCharacters(this string s, params char[] unwantedCharacters) 
        => s == null ? null : string.Join(string.Empty, s.Split(unwantedCharacters));
}

Stosowanie

var name = "edward woodward!";
var removeDs = name.RemoveCharacters('d', '!');
Assert.Equal("ewar woowar", removeDs); // old joke
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.