【问题标题】:String.Replace doesn't replace all matchesString.Replace 不会替换所有匹配项
【发布时间】:2013-02-05 17:55:26
【问题描述】:

为什么line2 只替换了交替出现的一半?

    Dim line1 As String = "AAA|BBB|CCC|CCC|CCC|CCC|EEE|FFF"
    Dim line2 As String = "AAA|BBB|CCC|CCC|CCC|CCC|EEE|FFF"
    Dim line3 As String = "AAA|BBB|CCC|CCC|CCC|CCC|EEE|FFF"

    line1 = line1.Replace("CCC", "")
    line2 = line2.Replace("|CCC|", "||")
    line3 = line3.Replace("CCC|", "|")

结果:

line1 = "AAA|BBB|||||EEE|FFF" -- OK, but fails when element is "..|ZZZCCCZZZ|.."
line2 = "AAA|BBB||CCC||CCC|EEE|FFF" -- Not OK
line3 = "AAA|BBB|||||EEE|FFF" -- OK, but fails similar to Line1 edge-case for "..|ZZZCCC|.."

我曾尝试使用 RegEx,但得到了类似的结果。

下面真的没有比这更好的方法了吗?

Do While line1.Contains("|CCC|")
    line1 = line1.Replace("|CCC|", "||")
Loop

【问题讨论】:

  • 一旦找到第一个标记,它就会开始寻找 next after 该标记。所以它找到|CCC|,替换它,然后继续,它看到的第一件事是CCC|,它不匹配。

标签: .net vb.net string replace


【解决方案1】:

一旦找到第一个令牌,它就会开始寻找下一个 该令牌之后的一个。所以它找到|CCC|,替换它,然后继续,它看到的第一件事是CCC|,它不匹配。它不会预先扫描字符串以查找要替换的标记。

这样考虑:

给定AAA|BBB|CCC|CCC|CCC|CCC|EEE|FFF

它运行到AAA|BBB|CCC| HOLD IT 找到|CCC|,让我们开始构建我们的字符串:

AAA|BBB + ||(我们的替代品)

现在让我们继续前进,我们现在还有 CCC|CCC|CCC|EEE|FFF 可以使用。

它运行到CCC|CCC| HOLD IT 找到|CCC|,让我们继续添加到我们的字符串中:

AAA|BBB||CCC + ||(我们的替代品)

现在让我们继续,我们现在有 CCC|CCC|EEE|FFF 等等等等。

编辑:考虑MSDN 上描述返回值的条目:

与当前字符串等价的字符串,除了所有 oldValue 的实例被替换为 newValue。

一个 可以 读取它作为您期望它预扫描字符串并找到 所有 匹配。我在 MSDN 文档中没有看到任何描述这种极端情况的内容。也许这是应该添加到 MSDN 文档中的内容。

【讨论】:

  • 我可以理解为什么这会令人困惑,因为它不会修改原始字符串。这几乎看起来像是一个实现细节,但却是一个非常合乎逻辑的细节。可惜这里没有提到:msdn.microsoft.com/en-us/library/…
  • @JasonSperske 它似乎并不令人困惑.. 至少在使用这种方法一两次之后不会:D 匹配消耗它匹配的所有内容并且跳过 任何替换的文本。如果不是这种情况,那么可能会有无限的替换递归!例如line1.Replace("X", "X")
  • @pst - 这似乎是一种逻辑优化,尽管它也可以通过计算 while found, replace 执行的次数来完成,并根据原始字符串的长度、模式和替换。
  • 我认为这不一定是无限递归的问题;我没有看到它 re - 评估字符串。我可以看到它扫描一次,找到要替换的索引,可能做一些数学/移位,然后运行一次替换它们,即使在共享令牌时也是如此。只是这将花费更长的时间(两次通过)并且行为与编程中的大多数字符串“替换”方法不同(AFAIK .NET 实现基于“标准”方式)。如果您想推出自己的替代品,当然欢迎您(扩展方法鼓励您这样做!)
  • @ChrisSinclair 如果没有同时跳过匹配和替换,如何评估"XX".Replace("X", "XX")? (我已经从上面稍微更改了数据以增加复杂性/模糊性。请注意,Leon 的答案包含具有特定规则的最大迭代限制。)
【解决方案2】:

除了使用正则表达式或string.Replace,您还可以解析值,过滤掉不需要的值并将它们重新组合在一起。

line1 = string.Join("|", line1.Split("|").Select(s => s == "CCC" ? "" : s).ToArray());

抱歉,我不知道对应的 VB。

【讨论】:

  • 问题只是因为我很好奇:-) 这不是一个消耗内存的操作吗?我的意思是......字符串操作基本上是消耗内存的。
  • @MatsMagnem 好吧,它肯定会创建一个字符串数组,然后是新连接的字符串,但所有这些都将用于垃圾收集。但是,是的,这可能会导致非常大的字符串出现内存不足错误。在这种情况下,您需要一种缓冲区类型的算法,但我认为在这种情况下,字符串不会那么大。
【解决方案3】:

对于未来的任何人,我已经添加了一个扩展方法来克服框架中的这个限制:

<System.Runtime.CompilerServices.Extension()>
Public Function ReplaceAll(ByVal original As String, ByVal oldValue As String, ByVal newValue As String) As String

    If newValue.Contains(oldValue) Then
        Throw New ArgumentException("New value can't be a subset of OldValue as infinite replacements can occur.", newValue)
    End If

    Dim maxIterations As Integer = original.Length \ oldValue.Length

    While maxIterations > 0 AndAlso original.Contains(oldValue)
        original = original.Replace(oldValue, newValue)
        maxIterations -= 1
    End While

    Return original

End Function

【讨论】:

  • 我认为这不是“限制”.. 但如果这是您需要的.. 但是,我怀疑在某些情况下这会失败(或“意外行动”)。特别是,在执行所有原始替换之前,最大迭代可能过早耗尽。
  • (如果匹配可以匹配替换,这将导致我在之前的 cmets 中谈到的无限递归情况。)
  • 如果新值中包含旧值 .Replace("X","XX") 抛出异常。
【解决方案4】:

对于这种情况,我可能会使用regular expression replacelook-around

考虑这个例子:

Regex.Replace("FCCCF|CCC|CCC|", "((?<=[|])CCC(?=[|]))", "")
// ->
"FCCCF|||"

这将始终匹配正确的次数,并且不容易出现任何无限递归问题。它需要修改适当的正则表达式并更改替换数据。

但是,请注意克里斯的评论:

Regex.Replace("FCCCF|CCC|CCC||CCC|", "((?<=[|])CCC(?=[|]))", "")
// -> only 5 pipes: verify this is correct per the intended semantics
"FCCCF|||||"

【讨论】:

  • 正如我在评论中提到的那样,当我期望 FCCCF||||||(5 对 6 个管道)时,使用该正则表达式 FCCCF|CCC|CCC||CCC| 会导致 FCCCF|||||。编辑:也许此时我们正在从“基类库”领域转向“业务规则”:)
  • @ChrisSinclair 感谢您指出这一点 - 出乎意料的小细节会让人非常难受。
猜你喜欢
  • 2016-01-15
  • 1970-01-01
  • 1970-01-01
  • 2012-05-18
  • 2016-11-04
  • 2019-05-16
  • 2015-08-05
  • 1970-01-01
  • 2021-09-29
相关资源
最近更新 更多