删除第一次出现的重复行

时间:2014-10-20 01:38:54

标签: regex pcre

此模式(带有mis标志)保留最后一次出现的重复行

^(\w+)\R(?=.*?^\1$)

就像这个example一样 如何仅使用正则表达式保留第一个匹配项 例子

Apple
Banana
TEST
apple
Cherry
banana
bananA
Var
cherry
applE
cherrY

结果:

Apple
Banana
TEST
Cherry
Var

这里的要点是保留条目的原始顺序并删除重复项。

2 个答案:

答案 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

(是的,这是一条尾随在行的空白行。这可能是在同一操作中同时删除applecherrY的产物。)


这是使用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