概述
使用Oracle的实现是可能的,但出于许多原因,我不建议采用这种方法:
它依赖于实现中的错误,它将*
、+
或{n,}
解释为{0, 0x7FFFFFFF}
、{1, 0x7FFFFFFF}
、{n, 0x7FFFFFFF}
,这允许后向查找包含这些量化器。由于它依赖于一个错误,所以不能保证它将来会以类似的方式工作。
它是一团乱麻,难以维护。编写正常的代码和任何有基本Java知识的人都可以阅读它,但在这个答案中使用正则表达式限制了能够一眼理解代码的人数,只有那些了解正则表达式实现的人才能理解。
因此,这个答案是为教育目的而存在的,而不是用于生产代码。
解决方案
这里是一个一行的replaceAll
正则表达式解决方案:
String output = input.replaceAll("(.)(?=(.*))(?<=(?=\\1.*?\\1\\2$).+)","")
打印出正则表达式:
(.)(?=(.*))(?<=(?=\1.*?\1\2$).+)
我们想要做的是向后查看,看同样的字符是否出现过。在开头的捕获组
(.)
捕获当前字符,并且向后查找组在那里检查字符是否之前出现过。到目前为止还不错。
然而,由于回溯引用
\1
没有明显的长度,它不能直接出现在向后查找中。
这就是我们利用错误向后查找字符串开始部分的地方,然后在向后查找内部使用向前查找来包括回溯引用的地方,正如您所看到的
(?<=(?=
...
).+)
。
然而,问题并没有结束。虽然向后查找内部的非断言模式
.+
不能越过
(.)
位置之后的位置,但是
向前查找内部却可以。作为一个简单的测试:
"haaaaaaaaa".replaceAll("h(?<=(?=(.*)).*)","$1")
> "aaaaaaaaaaaaaaaaaa"
为确保搜索不超出当前字符,我使用先行断言 (?=(.*)) 捕获字符串的其余部分,并将其用于“标记”当前位置 (?=\\1.*?\\1\\2$)。
能否在不使用后行断言的情况下完成一次替换?
我认为这是不可能的。我们需要区分一个字符的第一次出现和同一字符的后续出现。虽然我们可以针对一个固定字符(例如 a)进行此操作,但问题要求我们对字符串中的所有字符都进行此操作。
供您参考,这是用于删除所有后续出现的固定字符(此处使用 h):
.replaceAll("^([^h]*h[^h]*)|(?!^)\\Gh+([^h]*)","$1$2")
为了对多个字符执行此操作,我们必须跟踪该字符是否出现过,
跨匹配和
所有字符。上面的正则表达式显示了
跨匹配部分,但另一个条件使这种情况变得不可能。
显然,我们不能在单个匹配中完成此操作,因为后续出现的次数可能是不连续和任意的。
hhaiha
,你的输出是什么? - Avinash Rajhhaiha
将会得到iha
。 - Johny