Удалить символы из строки C #


150

Как я могу удалить символы из строки? Например: "My name @is ,Wan.;'; Wan".

Я хотел бы удалить символы '@', ',', '.', ';', '\''из этой строки, чтобы она стала"My name is Wan Wan"

Ответы:


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

Но я могу предложить другой подход, если вы хотите удалить все не буквенные символы

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
Это также можно сделать следующим образом: str = новая строка (str.Where (x => char.IsWhiteSpace (x) || char.IsLetterOrDigit (x)). ToArray ());
— Аднан Бхатти,

1
Мне пришлось искать это, string.Empty не создает строку для сравнения, поэтому она более эффективна, чем "". ( stackoverflow.com/questions/151472/… )
— Том Черул,

6
Я единственный, кто получает «Аргумент 2: не может преобразовать из« строки »в« char »» ом string.Empty?
— OddDev

2
@OddDev вы должны получить эту ошибку, только если ваш массив, который вы просматриваете, представляет собой список символов. Если это строки, это должно сработать
— Newteq Developer

3
Также обратите внимание, что для правильной работы функции str.Replace первый параметр должен быть строкой, если вы хотите использовать string.Empty в качестве второго параметра. Если в качестве первого параметра вы используете символ (то есть «a»), вам также понадобится символ как второй. В противном случае вы получите ошибку «Аргумент 2: невозможно преобразовать из« string »в« char »», упомянутую @OddDev выше
— Лев


64

Звучит как идеальное приложение для RegEx - движка, предназначенного для быстрой обработки текста. В таком случае:

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

3
Похоже, это было бы гораздо более эффективно, чем подход, основанный на итераторах, особенно если вы можете использовать скомпилированный Regex;
— Аде Миллер

Это должен быть принятый ответ, особенно потому, что, как сказал @AdeMiller, он будет гораздо более эффективным.
— Обсидиан

14
Это не быстрее, чем цикл, это распространенное заблуждение, что регулярные выражения всегда быстрее, чем циклы. Регулярные выражения не волшебны, по своей сути они должны в какой-то момент перебирать строку для выполнения своих операций, и они могут быть намного медленнее с накладными расходами от самого регулярного выражения. Они действительно превосходны, когда дело доходит до чрезвычайно сложных манипуляций, где понадобятся десятки строк кода и несколько циклов. Тестируя скомпилированную версию этого регулярного выражения на простом неоптимизированном цикле 50000 раз, регулярное выражение в 6 раз медленнее.
— Тони Читам,

Как насчет эффективности памяти? Не будут ли регулярные выражения более эффективными в смысле размещения новых строк?
— Марек

2
Возможно, я ошибся, когда утверждал, что RegEx быстр. Если это не было в центре очень узкого цикла, тогда другие соображения, такие удобочитаемость и ремонтопригодность, вероятно, будут доминировать над производительностью для такой маленькой операции, как эта.
— Джон Мелвилл

21

Менее специфичный для вашего вопроса, можно удалить ВСЕ знаки препинания из строки (кроме пробела), перечислив допустимые символы в регулярном выражении белым цветом:

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 ]", "");

Обратите внимание, что после 9 есть пробел, чтобы не удалять пробелы в предложении. Третий аргумент - пустая строка, которая служит для замены любой подстроки, которая не принадлежит регулярному выражению.


19

Сравнение различных предложений (а также сравнение в контексте односимвольных замен с различными размерами и позициями цели).

В этом конкретном случае разделение по целям и объединение замен (в данном случае пустая строка) является самым быстрым, по крайней мере, в 3 раза. В конечном счете, производительность различается в зависимости от количества замен, где замены находятся в источник и размер источника. #ymmv

Полученные результаты

(полные результаты здесь )

| 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] |

Test Harness (LinqPad)

(примечание: Perfи Vsявляются расширениями времени, которые я написал )

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
Напоследок несколько цифр! Хорошая работа @drzaus!
— Марек



6

Еще одно простое решение:

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


4

Строка - это просто массив символов, поэтому для замены используйте Linq (аналогично Albin выше, за исключением того, что для замены используется оператор linq includes):

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

Первая строка - это строка для замены символов, а вторая - простая строка, содержащая символы


Решение от Albin Linq, вероятно, лучше, если нет дополнительных символов, которые вы хотите отфильтровать (без пробелов, букв и цифр).
— Алистер

3

Я мог бы также выбросить это здесь.

Сделайте расширение для удаления символов из строки:

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

И это можно использовать так:

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

Или просто так:

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

Это лучшее решение, так как оно выделяет наименьшее количество памяти. Я также установил бы длину исходной строки в качестве начальной емкости построителя строк, например: new StringBuilder (input.Length) для этой цели, чтобы иметь наименьшее количество выделений памяти.
— treaschf

3

Кажется, что самый короткий путь - это объединить LINQ и 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" 

Смотрите демонстрацию C # . Обратите внимание, что string.Concatэто ярлык string.Join("", ...).

Обратите внимание, что использование регулярного выражения для удаления отдельных известных символов все еще возможно для динамического построения, хотя считается, что регулярное выражение медленнее. Однако вот способ построить такое динамическое регулярное выражение (где все, что вам нужно, это класс символов):

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

Смотрите еще одну демонстрацию C # . Регулярное выражение будет выглядеть [@,\.;']+( в соответствии с одной или более ( +) последовательными появлениями @, ,, ., ;или 'символы) , где точка не должна быть экранирована, но Regex.Escapeбудет необходимо , чтобы избежать других символов , которые должны быть экранированы, как \,^ , ]или -положение которых внутри класса персонажа вы не можете предсказать.



3

Вот метод, который я написал, который использует немного другой подход. Вместо указания символов для удаления я сообщаю своему методу, какие символы я хочу сохранить - он удалит все остальные символы.

В примере с ОП он хочет сохранить только алфавитные символы и пробелы. Вот как будет выглядеть вызов моего метода ( C # demo ):

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

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

Вот мой метод:

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

Отличный ответ!
— edtheprogrammerguy

Очень хорошо! строка чисел имеет 0 дважды.
— Джон Курц

@JohnKurtz Хороший улов - он исчез.
— Mass Dot Net

2

Здесь много хороших ответов, вот мое дополнение, а также несколько модульных тестов, которые можно использовать для проверки правильности, мое решение аналогично описанному выше @ Rianne, но использует ISet, чтобы обеспечить O (1) время поиска для символов замены (а также похож на решение @Albin Sunnanbo's Linq).

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

Тесты NUnit (2.6+) здесь

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

Это мощный метод, который я обычно использую в том же случае:

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

Наслаждаться...


1

Старая школа в месте копирования / топать:

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

Не уверен в эффективности по сравнению с другими методами (т. Е. Накладными расходами на все вызовы функций и экземпляры, которые происходят как побочный эффект при выполнении C #).


1

Я делаю это методом расширения и со строковым массивом, я думаю, string[]это более полезно, чем char[]потому, что char также может быть строкой:

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

тогда вы можете использовать его где угодно:

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

1

Мне нужно было удалить специальные символы из файла XML. Вот как я это сделал. char.ToString () является героем в этом коде.

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

Если взять показатели производительности из @drzaus, то вот метод расширения, который использует самый быстрый алгоритм.

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

использование

var name = "edward woodward!";
var removeDs = name.RemoveCharacters('d', '!');
Assert.Equal("ewar woowar", removeDs); // old joke
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.