簡體   English   中英

替換 C# 字符串中的多個字符

[英]Replace multiple characters in a C# string

有沒有更好的方法來替換字符串?

我很驚訝 Replace 不接受字符數組或字符串數組。 我想我可以編寫自己的擴展,但我很好奇是否有更好的內置方法來執行以下操作? 注意最后一個 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 );
   }
}

瞧...

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世界並玩指針(雖然危險)

並嘗試以最少的次數遍歷字符數組。 注意這里的HashSet ,因為它避免遍歷循環內的字符序列。 如果您需要更快的查找,您可以用優化的char查找替換HashSet (基於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();

您也可以簡單地編寫這些字符串擴展方法,並將它們放在您的解決方案中的某個位置:

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

性能方面,這可能不是最好的解決方案,但它有效。

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

使用 RegEx.Replace,如下所示:

  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.Replace 的MSDN 文檔的更多信息

用於將一組定義的字符串字符替換為特定字符的 .NET Core 版本。 它利用了最近引入的Span類型和string.Create方法。

這個想法是准備一個替換數組,所以每個字符串字符不需要實際的比較操作。 因此,替換過程提醒了狀態機的工作方式。 為了避免替換數組的所有項目的初始化,讓我們在那里存儲oldChar ^ newChar (XOR'ed) 值,這樣做有以下好處:

  • 如果一個字符沒有改變: ch ^ ch = 0 - 不需要初始化不變的項目
  • 最終的字符可以通過 XOR'ing 找到: ch ^ repl[ch]
    • ch ^ 0 = ch - 不改變字符大小寫
    • ch ^ (ch ^ newChar) = newChar - 替換的字符

所以唯一的要求是確保替換數組在初始化時為零。 每次調用ReplaceAll方法時,我們將使用ArrayPool<char>來避免分配。 並且,為了確保數組被清零而無需昂貴地調用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);
        }
    }
}

我知道這個問題非常古老,但我想提供兩個更有效的選項:

首先,Paul Walls 發布的擴展方法很好,但可以通過使用 StringBuilder 類來提高效率,該類類似於字符串數據類型,但特別適用於您將多次更改字符串值的情況。 這是我使用 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 需要 73 毫秒,而使用字符串需要 81 毫秒。 因此,差異通常可以忽略不計,除非您正在運行許多操作或使用巨大的字符串。

其次,這是您可以使用的 1 個襯里循環:

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

我個人認為這是最好的選擇。 它非常高效,不需要編寫擴展方法。 在我的測試中,這僅在 63 毫秒內運行了 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'); }

本示例中的前 2 行歸功於 Paul Walls。

沒有“替換”(僅限 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 ) );

我也擺弄過這個問題,發現這里的大多數解決方案都很慢。 最快的實際上是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;
        }
    }
}

對我來說,這最多是兩個用於實際輸入字符串的分配。 由於某些原因, StringBuilder對我來說要慢得多。 它比 LINQ 變體快 2 倍。

在構建了我自己的解決方案並查看了此處使用的解決方案之后,我利用了一個不使用復雜代碼並且通常對大多數參數都有效的答案。

  1. 涵蓋其他方法更合適的基本情況。 如果沒有要替換的字符,則返回原始字符串。 如果只有一個,只需使用 Replace 方法即可。
  2. 使用 StringBuilder 並將容量初始化為原始字符串的長度。 畢竟,如果僅替換字符,正在構建的新字符串將與原始字符串具有相同的長度。 這確保只有 1 個 memory 分配用於新字符串。
  3. 假設“char”長度可能很小或很大都會影響性能。 較大的 collections 使用哈希集更好,而較小的 collections 則不然。 這是Hybrid Dictionaries近乎完美的用例。 一旦集合變得太大,他們就會切換到使用基於 Hash 的查找。 但是,我們不關心字典的值,所以我只是將它設置為“true”。
  4. 有不同的方法用於 StringBuilder verse 只是一個字符串將防止不必要的 memory 分配。 如果它只是一個字符串,除非檢查了基本情況,否則不要實例化 StringBuilder。 如果它已經是 StringBuilder,則執行替換並返回 StringBuilder 本身(就像Append等其他 StringBuilder 方法所做的那樣)。
  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;
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM