此模式(带有mis
标志)保留最后一次出现的重复行
^(\w+)\R(?=.*?^\1$)
就像这个example一样 如何仅使用正则表达式保留第一个匹配项 例子
Apple
Banana
TEST
apple
Cherry
banana
bananA
Var
cherry
applE
cherrY
结果:
Apple
Banana
TEST
Cherry
Var
这里的要点是保留条目的原始顺序并删除重复项。
答案 0 :(得分:0)
你可以这样做:
^(\w+\R)([\s\S]+?\R)\1
Remplace with:
\1\2
这很好用。但是你必须多次运行脚本。如果你不能使用循环。 运行,运行运行,直到列表长度不变...
答案 1 :(得分:0)
除非单个解释器支持动态宽度lookbehinds,否则单个正则表达式或s///
正则表达式的替换是不可能的。
我将在vim中解决此问题,它的regex解释器实际上支持动态向后查找,但是它确实很钝,因此我将首先重新创建delete-first-instance变体(问题中的^(\w+)\R(?=.*?^\1$)
)。
对于所有行(:%s/^\(\w\+\)\n\ze\%(^\w\+\n\)*\1$//ig
,:
vim命令(%
)将使用替换(s/…//ig
)来删除a的不区分大小写的全局匹配以(^
开头的行的正则表达式,捕获(\(…\)
)个1个以上的字符(\w\+
),后跟换行符(\n
)。其余的匹配是零宽度预读(\ze
表示“零宽度结尾”,\zs…
类似于PCRE正则表达式结尾处的(?=…)
)。然后,在匹配原始捕获(\%(…\)*
)之前,我们跳过零个或多个非捕获组(\1
),它们各自包含一行。由于\ze
,当我们删除第一个实例时,该部分不会被删除,而留下:
TEST
bananA
Var
applE
cherrY
(我讨厌编写vimscript和vim正则表达式。我真的不知道您在这里如何说服我...)
这是一个可以接受的解决方案。 (我之所以这么说是因为/g
不够全局。)
:%s/^\(\w\+\)\n\%(\w\+\n\)*\zs\1\n//ig
使用与上一个delete-first-instance命令非常相似的组成。我已将\ze
更改为\zs
(“零宽度开始”,如PCRE \K
)。实际上,这是一个后向可变宽度。 (是的,从理论上讲,我可以使它看起来更像(?<=…)
和vim的\%(…\)\@<=
,但这很丑陋,我无法使其正常工作。)“跳过”组一直保持零状态。宽边。
尽管宽度为零,但每次替换都需要运行一次(在这种情况下为4x)。我相信这是因为匹配是在最终实例上设置的,因此每次替换都必须消耗空间直到最终匹配(此正则表达式是贪婪的),然后向后退一步,但是在第一次替换之后,它不知道要进行迭代后退到下一个捕获。
运行四次后,您将得到:
Apple
Banana
TEST
Cherry
Var
(是的,这是一条尾随在行的空白行。这可能是在同一操作中同时删除apple
和cherrY
的产物。)
这是使用Javascript的更实用的解决方案,而regex会做尽可能多的工作:
test = "Apple\nBanana\nTEST\napple\nCherry\nbanana\nbananA\nVar\ncherry\ncherrY\n";
while ( test != ( test = test.replace(/^(\w+\n)((?:\w+\n)*)\1/mig, "$1$2") ) ) 1;
所有逻辑都存在于while
循环的条件下,该循环基本上说“通过替换之前比较字符串(!=
)来执行此替换并循环,直到它什么都不做”替换后的字符串。循环意味着我们不必处理零宽度,因为我们在每次迭代时都重新开始(否则,正则表达式将在中断处重新开始,因此需要零宽度)。
regex本身只是捕获一行上的单词(^(\w+\n)
),然后匹配零个或多个其他单词(((?:\w+\n)*)
),然后再匹配捕获的单词(\1
)
while
循环的主体为空(1
是无操作),因为条件包含所有逻辑。当给定单个命令时,JavaScript不需要大括号,但这更正式地是
while ( test != ( test = test.replace(…) ) ) { true; }
这将循环四次(您可以通过在循环前设置i=0
并在循环内将1
更改为i++
来计数),然后将test
保留为: / p>
Apple
Banana
TEST
Cherry
Var