【问题标题】:Replace Multiple String Elements in C#在 C# 中替换多个字符串元素
【发布时间】:2010-11-22 05:19:56
【问题描述】:

有没有更好的方法...

MyString.Trim().Replace("&", "and").Replace(",", "").Replace("  ", " ")
         .Replace(" ", "-").Replace("'", "").Replace("/", "").ToLower();

我已经扩展了字符串类以将其简化为一项工作,但有更快的方法吗?

public static class StringExtension
{
    public static string clean(this string s)
    {
        return s.Replace("&", "and").Replace(",", "").Replace("  ", " ")
                .Replace(" ", "-").Replace("'", "").Replace(".", "")
                .Replace("eacute;", "é").ToLower();
    }
}

只是为了好玩(并停止 cmets 中的参数) 我提出了一个要点,对下面的各种示例进行基准测试。

https://gist.github.com/ChrisMcKee/5937656

正则表达式选项得分非常高;字典选项出现最快; stringbuilder replace 的冗长版本比简写略快。

【问题讨论】:

  • 根据您在基准测试中的内容,看起来字典版本并没有做所有的替换,我怀疑是什么让它比 StringBuilder 解决方案更快。
  • @toad 嗨,从 2009 年开始;我在 4 月份在下面添加了一条关于这个明显错误的评论。虽然我跳过了 D,但要点已更新。字典版本仍然更快。
  • @TotZam 至少在标记之前检查日期;这是从 2009 年开始,从 2012 年开始
  • 由于这里的许多答案似乎与性能有关,我相信应该指出Andrej Adamanko's answer对于许多替代品来说可能是最快的;肯定比链接 .Replace() 更快,尤其是在他的回答中所述的大输入字符串上。

标签: c# string refactoring immutability


【解决方案1】:

更快 - 不。更有效 - 是的,如果您将使用 StringBuilder 类。通过您的实现,每个操作都会生成一个字符串副本,在某些情况下可能会影响性能。字符串是不可变的对象,所以每个操作只返回一个修改后的副本。

如果您希望在多个很长的Strings 上主动调用此方法,最好将其实现“迁移”到StringBuilder 类上。使用它可以直接在该实例上执行任何修改,因此您可以避免不必要的复制操作。

public static class StringExtention
{
    public static string clean(this string s)
    {
        StringBuilder sb = new StringBuilder (s);

        sb.Replace("&", "and");
        sb.Replace(",", "");
        sb.Replace("  ", " ");
        sb.Replace(" ", "-");
        sb.Replace("'", "");
        sb.Replace(".", "");
        sb.Replace("eacute;", "é");

        return sb.ToString().ToLower();
    }
}

【讨论】:

  • 为了清楚起见,字典答案是最快的stackoverflow.com/a/1321366/52912
  • 在您对gist.github.com/ChrisMcKee/5937656 的基准测试中,字典测试不完整:它不会进行所有替换并且“替换”而不是“”。没有进行所有替换可能是它在基准测试中最快的原因。正则表达式替换也不完整。但最重要的是,您的字符串 TestData very 很短。与接受的答案状态一样,字符串必须足够长才能使 StringBuilder 具有优势。您能否用 10kB、100kB 和 1MB 的字符串重复基准测试?
  • 这是一个好点;就目前而言,它被用于 url 清理,因此 100kb - 1mb 的测试将是不现实的。我将更新基准,以便它使用整个东西,但这是一个错误。
  • 为了获得最佳性能,请循环​​遍历字符并自行替换它们。但是,如果您有多个单个字符串,这可能会很乏味(找到它们会强制您一次比较多个字符,而替换它们需要分配更多内存并移动字符串的其余部分)。
  • 当输入字符串中没有出现要替换的字符或字符串时,这将是一个非常糟糕的解决方案。在这种情况下,String.Replace 只会返回原始引用,并且与 StringBuilder 解决方案相比非常便宜。
【解决方案2】:

这样会更有效率:

public static class StringExtension
{
    public static string clean(this string s)
    {
        return new StringBuilder(s)
              .Replace("&", "and")
              .Replace(",", "")
              .Replace("  ", " ")
              .Replace(" ", "-")
              .Replace("'", "")
              .Replace(".", "")
              .Replace("eacute;", "é")
              .ToString()
              .ToLower();
    }
}

【讨论】:

  • 真的很难读。我相信你知道它的作用,但是初级开发者会对实际发生的事情摸不着头脑。我同意——我也一直在寻找写东西的捷径——但这只是为了我自己的满足。其他人被一堆乱七八糟的东西吓坏了。
  • 这实际上更慢。 BenchmarkOverhead... 13ms StringClean-user151323... 2843ms StringClean-TheVillageIdiot... 2921ms 因重新运行而异,但答案胜出gist.github.com/anonymous/5937596
【解决方案3】:

也许更易读一点?

    public static class StringExtension {

        private static Dictionary<string, string> _replacements = new Dictionary<string, string>();

        static StringExtension() {
            _replacements["&"] = "and";
            _replacements[","] = "";
            _replacements["  "] = " ";
            // etc...
        }

        public static string clean(this string s) {
            foreach (string to_replace in _replacements.Keys) {
                s = s.Replace(to_replace, _replacements[to_replace]);
            }
            return s;
        }
    }

同时添加 New In Town 对 StringBuilder 的建议...

【讨论】:

  • 这样会更易读:private static Dictionary&lt;string, string&gt; _replacements = new Dictionary&lt;string, string&gt;() { {"&amp;", "and"}, {",", ""}, {" ", " "} /* etc */ };
  • 或者当然... private static readonly Dictionary Replacements = new Dictionary() { { "&", "and" }, { ",", ““ }, { “ “, “ “ } /* 等等 */ }; public static string Clean(this string s) { return Replacements.Keys.Aggregate(s, (current, toReplace) => current.Replace(toReplace, Replacements[toReplace])); }
  • -1 :使用字典在这里没有任何意义。只需使用List&lt;Tuple&lt;string,string&gt;&gt;。这也改变了替换的顺序并且不像例如那样快。 s.Replace("a").Replace("b").Replace("c")。不要用这个!
【解决方案4】:

我正在做类似的事情,但就我而言,我正在做序列化/反序列化,所以我需要能够双向进行。我发现使用 string[][] 的工作方式几乎与字典相同,包括初始化,但您也可以朝另一个方向发展,将替代品返回到它们的原始值,这是字典真正没有设置做的事情。

编辑:您可以使用Dictionary&lt;Key,List&lt;Values&gt;&gt; 以获得与 string[][] 相同的结果

【讨论】:

  • 这似乎无法回答问题
【解决方案5】:

如果您只是追求一个漂亮的解决方案并且不需要节省几纳秒,那么一些 LINQ 糖怎么样?

var input = "test1test2test3";
var replacements = new Dictionary<string, string> { { "1", "*" }, { "2", "_" }, { "3", "&" } };

var output = replacements.Aggregate(input, (current, replacement) => current.Replace(replacement.Key, replacement.Value));

【讨论】:

  • 与 Gist 中的示例 C 类似(如果您在上面看,评论中会出现更丑的 linq 语句)
  • 有趣的是,您将功能性陈述定义为比程序性陈述“更丑”。
  • 不会为此争论;它只是偏好。正如您所说, linq 只是语法糖;正如我所说,我已经将等价物放在代码上方:)
【解决方案6】:

建议的解决方案中可能会优化一件事。多次调用Replace() 会使代码对同一个字符串进行多次传递。对于非常长的字符串,由于 CPU 缓存容量未命中,解决方案可能会很慢。可能应该考虑replacing multiple strings in a single pass

该链接的基本内容:

static string MultipleReplace(string text, Dictionary replacements) {
            return Regex.Replace(text, 
                                    "(" + String.Join("|", adict.Keys.ToArray()) + ")",
                                    delegate(Match m) { return replacements[m.Value]; }
                                    );
        }
    // somewhere else in code
            string temp = "Jonathan Smith is a developer";
            adict.Add("Jonathan", "David");
            adict.Add("Smith", "Seruyange");
            string rep = MultipleReplace(temp, adict);


【讨论】:

  • 很多答案似乎都关心性能,在这种情况下这是最好的。这很简单,因为它只是 String.Replace 的 a documented overload,您根据匹配返回预期值,在此示例中,使用字典将它们匹配起来。应该简单易懂。
  • 添加了链接页面的代码,以防止链接页面失效时此答案变得无用
【解决方案7】:
string input = "it's worth a lot of money, if you can find a buyer.";
for (dynamic i = 0, repl = new string[,] { { "'", "''" }, { "money", "$" }, { "find", "locate" } }; i < repl.Length / 2; i++) {
    input = input.Replace(repl[i, 0], repl[i, 1]);
}

【讨论】:

  • 您应该考虑在您的答案中添加上下文。就像对它在做什么的简要解释,如果相关,为什么你要这样写。
【解决方案8】:

使用 linq 的另一个选项是

[TestMethod]
public void Test()
{
  var input = "it's worth a lot of money, if you can find a buyer.";
  var expected = "its worth a lot of money if you can find a buyer";
  var removeList = new string[] { ".", ",", "'" };
  var result = input;

  removeList.ToList().ForEach(o => result = result.Replace(o, string.Empty));

  Assert.AreEqual(expected, result);
}

【讨论】:

  • 您可以声明var removeList = new List&lt;string&gt; { /*...*/ };,然后只需调用removeList.ForEach( /*...*/ ); 并简化您的代码。另请注意,它并没有完全回答问题,因为 all 找到的字符串被替换为 String.Empty
  • Linq 到底在哪里使用?这浪费了将removeList 转换为List 的做法,因为这是不必要的目标,即使其成为单行。但是 Lamdas 和 Linq 不是同义词。
  • 注意,List.ForEach 不是 LINQ 的东西,它是 List 的东西
【解决方案9】:

也可以使用带有MatchEvaluator 的正则表达式:

    var pattern = new Regex(@"These|words|are|placed|in|parentheses");
    var input = "The matching words in this text are being placed inside parentheses.";
    var result = pattern.Replace(input , match=> $"({match.Value})");

注意:

  • 显然不同的表达式(如:\b(\w*test\w*)\b)可用于单词匹配。
  • 我希望它能够更加优化以找到表达式中的模式并进行替换
  • 优点是能够在替换的同时处理匹配的元素

【讨论】:

  • 这个答案可以通过更好地使用匹配委托来改进,而不是简单地提供相同的匹配值;这是一个非操作
【解决方案10】:

这本质上是Paolo Tedesco's answer,但我想让它可重复使用。

    public class StringMultipleReplaceHelper
    {
        private readonly Dictionary<string, string> _replacements;

        public StringMultipleReplaceHelper(Dictionary<string, string> replacements)
        {
            _replacements = replacements;
        }

        public string clean(string s)
        {
            foreach (string to_replace in _replacements.Keys)
            {
                s = s.Replace(to_replace, _replacements[to_replace]);
            }
            return s;
        }
    }

需要注意的一点是,我必须停止将其作为扩展程序,删除 static 修饰符,并从 clean(this string s) 中删除 this。我愿意接受有关如何更好地实施这一点的建议。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-27
    • 1970-01-01
    • 2021-09-26
    • 1970-01-01
    • 1970-01-01
    • 2013-04-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多