【问题标题】:Remove first occurrence of duplicate lines删除第一次出现的重复行
【发布时间】:2014-12-14 22:46:24
【问题描述】:

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

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

喜欢这个example
如何仅使用正则表达式来保留第一次出现
例子

Apple
Banana
TEST
apple
Cherry
banana
bananA
Var
cherry
applE
cherrY

结果:

Apple
Banana
TEST
Cherry
Var

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

【问题讨论】:

  • 你想达到什么目的?您可以在此处发布示例以及预期的输出并进行一些解释吗?
  • 我认为替换所有操作是不可能的(从右到左扫描字符串时可能 - 可以在.NET中打开)。给定ABAB,如果要删除第二个A,则需要消耗第一个B,这将完全阻止B被删除。顺便说一句,您可能想要这个正则表达式:regex101.com/r/rP4lH9/2
  • 正如nhahtdh 所说,纯PCRE 无法做到这一点。您使用什么语言?我认为可以不使用正则表达式或部分使用正则表达式来完成。

标签: regex pcre


【解决方案1】:

这对于单个正则表达式或s/// 正则表达式替换是不可能的,除非解释器支持动态宽度后向。

我将在 vim 中解决这个问题,它的正则表达式解释器实际上支持动态后视,但它真的很迟钝,所以我将首先重新创建 delete-first-instance 变体(问题中的^(\w+)\R(?=.*?^\1$))。

:%s/^\(\w\+\)\n\ze\%(^\w\+\n\)*\1$//ig vim 命令 (:) 将对所有行 (%) 使用替换 (s/…//ig) 来删除以 (@987654327 开头的行的正则表达式的不区分大小写的全局匹配) @) 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 的\%(…\)\@<= 让它看起来更像(?<=…),但这更丑,我无法让它工作。)那个“跳过”组被移动到保持在零宽度的一侧。

尽管宽度为零,但每次替换都需要运行一次(在本例中为 4 次)。我相信这是因为匹配是在最后一个实例上设置的,所以每次替换都必须消耗空间直到最后匹配(这个正则表达式是贪婪的)然后后退,但是在第一次替换之后,它不知道迭代倒退到下一个捕获。

四次运行后,您将得到:

Apple
Banana
TEST
Cherry
Var

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


这是一个使用 Javascript 的更实用的解决方案,使用正则表达式完成尽可能多的工作:

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 循环的条件内,它基本上是通过比较替换前的字符串 (!=) 与替换后的字符串来表示“执行此替换并循环直到它不做任何事情”替换。循环意味着我们不必处理零宽度,因为我们在每次迭代时重新开始(否则正则表达式会从它停止的地方恢复,因此需要零宽度)。

正则表达式本身只是在自己的行中捕获一个单词 (^(\w+\n)),然后匹配零个或多个其他单词 (((?:\w+\n)*)),然后再次捕获的单词 (\1)。

while 循环的主体为空(1 是空操作),因为条件包含所有逻辑。 Javascript 在给出单个命令时不需要大括号,但更正式的形式是
while ( test != ( test = test.replace(…) ) ) { true; }

此循环四次(您可以通过在循环前设置i=0 并将循环内的1 更改为i++ 来计数)然后将test 保留为:

Apple
Banana
TEST
Cherry
Var

【讨论】:

    猜你喜欢
    • 2022-11-22
    • 2016-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多