Заменить несколько символов в строке C #


179

Есть ли лучший способ заменить строки?

Я удивлен, что Replace не принимает массив символов или массив строк. Я думаю, что я мог бы написать свое собственное расширение, но мне было любопытно, есть ли лучший способ сделать следующее? Обратите внимание, что последняя замена - это строка, а не символ.

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

Ответы:


207

Вы можете использовать регулярное выражение замены.

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

2
\tи \rвключены в \s. Таким образом, ваше регулярное выражение эквивалентно [;,\s].
NullUserException

3
И \sна самом деле это эквивалентно тому, [ \f\n\r\t\v]что вы включаете туда некоторые вещи, которых не было в первоначальном вопросе. Кроме того, оригинальный вопрос спрашивает, с Replace("\n\n", "\n")чем не справляется ваше регулярное выражение.
NullUserException

11
Обратите внимание, что для простых операций замены, которые не могут быть изменены пользователем, использование регулярных выражений не является оптимальным, поскольку оно очень медленное по сравнению с обычными строковыми операциями, согласно первой статье о тестировании, которую я обнаружил при поиске "c # regex performance replace", это примерно 13 раз медленнее.
тоже

Ах, регулярное выражение, иероглифы власти! Единственная проблема, которую я вижу здесь, это удобочитаемость регулярных выражений; многие отказываются понимать их. Я недавно добавил решение ниже для тех, кто ищет менее сложную альтернативу.
sɐunıɔ ןɐ qɐp

Так как же написать, если мы хотим заменить несколько символов несколькими?
Хабип Огуз

114

Если вы чувствуете себя особенно умным и не хотите использовать 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 );
   }
}

И вуаля ...

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

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

Очень мало памяти, особенно для больших строк.
MarcinJuraszek

@MarcinJuraszek Lol ... Я, наверное, впервые слышал, чтобы кто-то утверждал, что встроенные строковые методы менее эффективны по памяти, чем регулярные выражения.
Пол Уоллс

10
Ты прав. Я должен был измерить, прежде чем я отправил это. Я запускаю бенчмарк и Regex.Replaceв 8 раз медленнее, чем несколько string.Replaceзвонков подряд. и в 4 раза медленнее, чем Split+ Join. См. Gist.github.com/MarcinJuraszek/c1437d925548561ba210a1c6ed144452
MarcinJuraszek

1
Отличное решение! просто небольшой аддон. К сожалению, это не сработает, если вы хотите заменить первый символ (ы). Скажем, вы хотите заменить символ 't' в строке примера. Метод Split просто удалит этот 't' из первого слова 'this', потому что это EmptyEntry. Если вы используете StringSplitOptions.None вместо RemoveEmptyEntries, Split оставит запись, а метод Join добавит символ разделителя. Надеюсь, это поможет
Пьер

58

Вы можете использовать функцию агрегирования 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'));

Вот метод расширения:

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

21

Это самый короткий путь:

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

1
Этот вкладыш также помогает, когда вам это нужно в инициализаторах.
Гуней Озсан

8

Оооо, ужас производительности! Ответ немного устарел, но все же ...

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

Строки - это просто неизменяемые массивы символов

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

  • либо с помощью StringBuilder
  • идти в unsafeмир и играть с указателями (хотя опасно)

и попробуйте перебрать массив символов наименьшее количество раз. Обратите внимание на HashSetздесь, так как он избегает проходить последовательность символов внутри цикла. Если вам нужен еще более быстрый поиск, вы можете заменить HashSetего оптимизированным поиском char(на основе 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;
        }
    }
}

Редактировать - Оптимизированная версия

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

Помните, что строки находятся wchar_tв .net, вы заменяете только подмножество всех возможных символов (и вам нужно 65536 bools, чтобы оптимизировать это ...)
gog

3

Вы также можете просто написать эти методы расширения строк и поместить их в свое решение:

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



1

С точки зрения производительности это, возможно, не лучшее решение, но оно работает.

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(), "");
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.