如何用递增计数器替换每个匹配项?


14

我想用十进制数搜索并替换每次出现的某种特定模式,该十进制数1以每一个匹配项开始并以1递增。

我可以找到措辞类似的问题,这些问题原来不是关于增加计数器,而是将每个匹配项修改为固定的数量。其他类似的问题是关于插入行号而不是递增计数器。

之前的示例:

#1
#1.25
#1.5
#2

后:

#1
#2
#3
#4

我的真实数据中有很多要重新编号的文字。


2
如果你有perldo,你可以使用:%perldo s/#\K\d+(\.\d+)?/++$i/ge
森迪普•

@Sundeep:我应该提到我在香草Windows 10上,对不起!
hippietrail

Answers:


15

您需要用状态替换。我记得为SO上的此类问题提供了(/几个)完整的解决方案

这是另一种进行方法(1)。现在,我将进行2个步骤:

  • 我将使用的虚拟列表变量,用于处理肮脏而复杂的技巧
  • 在每次匹配出现的地方插入我要填充的这个虚拟数组的len的替换。

这使:

:let t=[]
:%s/#\zs\d\+\(\.\d\+\)\=\ze/\=len(add(t,1))/g

如果您不习惯使用vim正则表达式,则使用:h /\zs\ze指定要匹配的子模式,然后匹配一系列数字,可能后面跟一个点和其他数字。对于任何浮点数来说,这都不是完美的选择,但这在这里就足够了。

注意:您必须将其包装在几个函数+命令中,以实现简单的界面。再次,有一些关于SO / vim的示例(在这里在这里在这里)如今,我对vim的了解已经足够多了,不必在意将这个技巧包装到命令中。确实,我将能够在第一次尝试时编写此命令,而我将花几分钟记住该命令的名称。


(1)目的是能够维持替换之间的状态,并用依赖于当前状态的某种东西替换当前出现的状态。

多亏了:s\=我们能够插入计算结果。

仍然是国家的问题。我们要么定义一个管理外部状态的函数,要么更新自己的外部状态。在C语言(和相关语言)中,我们可以使用length++length+=1。不幸的是,在vim脚本中,+=不能立即使用。它需要与:set或一起使用:let。这意味着:let length+=1递增一个数字,但不返回任何东西。我们不能写:s/pattern/\=(length+=1)。我们还需要其他东西。

我们需要变异函数。即改变其输入的功能。我们有setreg()map()add()以及更多可能性。让我们从他们开始。

  • setreg()改变寄存器。完善。我们可以用setreg('a',@a+1)@Doktor OSwaldo的解决方案编写。但是,这还不够。setreg()对于我们来说,更多的是一个过程而不是一个函数(对于那些认识Pascal,Ada ...的人)。这意味着它不返回任何东西。实际上,它确实返回了一些东西。名义出口(即非例外出口)总是返回某些东西。默认情况下,当我们忘记返回某些内容时,将返回0-它也适用于内置函数。这就是为什么在他的解决方案中替换表达式实际上是\=@a+setreg(...)。整rick,不是吗?

  • map()也可以使用。如果从一个以0(:let single_length=[0])开头的列表开始,则可以通过来增加它map(single_length, 'v:val + 1')。然后,我们需要返回新的长度。与不同setreg()map()返回其变异的输入。完美,长度存储在列表的第一个(也是唯一的,因此也是最后一个)位置。替换表达式可以是\=map(...)[0]

  • add()是我经常出于习惯而使用的一种(虽然map()我已经差不多了,但还没有参加他们各自的表演)。的想法add()是使用列表作为当前状态,并在每次替换之前在末尾添加一些内容。我经常将新值存储在列表的末尾,并将其用于替换。由于add()还返回了其变异的输入列表,因此我们可以使用:\=add(state, Func(state[-1], submatch(0)))[-1]。在OP的情况下,我们只需要记住到目前为止已检测到多少个匹配项即可。返回此状态列表的长度就足够了。因此,我的\=len(add(state, whatever))


我想我理解这一点,但为什么将数组及其技巧与将一个变量加到变量上相比却有些技巧呢?
hippietrail

1
这是因为\=期望表达式,并且因为与C不同,i+=1它不是可以递增返回表达式的东西。这意味着在后面,\=我需要一些可以修改计数器并返回表达式(等于该计数器)的东西。到目前为止,我发现的唯一内容就是列表(和字典)操纵函数。@Doktor OSwaldo使用了另一个变异函数(setreg())。区别在于,它setreg()从不返回任何东西,这意味着它总是返回数字0
卢克·赫米特

哇有趣!你的把戏和他的魔术都很神奇,我认为你的答案将从解释你的答案中受益。我认为只有最流利的vim编写者才会知道这种不直观的习惯用法。
hippietrail

2
@hippietrail。添加了说明。如果需要更具体的精度,请告诉我。
卢克·赫米特

13
 :let @a=1 | %s/search/\='replace'.(@a+setreg('a',@a+1))/g

但是要注意,它将覆盖您的寄存器a。我认为这比luc的回答更直接,但是也许他更快。如果此解决方案比他的解决方案差,我很想听听他的回答为何更好的任何反馈。任何反馈,以改善答案将不胜感激!

(它也是基于我的一个SO答案/programming/43539251/how-to-replace-finding-words-with-the-different-in-each-occurrence-in-vi-vim -edi / 43539546#43539546


我看不出@a+setreg('a',@a+1)比短len(add(t,1))。否则,这是另一个不错的技巧:)。我还没有这个。关于在:sand 的替换文本中使用字典变异函数substitute(),我注意到这比显式循环要快得多-因此在lh-vim-lib中实现了列表函数。我猜您的解决方案将与我的解决方案相提并论,可能会更快一些,我不知道。
卢克·赫米特

2
关于首选项,我更喜欢我的解决方案有一个原因:它保持@a不变。在脚本中,这是重要的IMO。在交互模式下,作为最终用户,我会知道我可以使用哪个寄存器。弄乱寄存器不太重要。在我的解决方案中,在交互模式下,混乱了一个全局变量;在脚本中它将是局部变量。
卢克·赫米特

@LucHermitte对不起,我的解决方案的确比您的解决方案短,在写这样的声明之前,我应该阅读得更好。我已从回答中删除了该声明,并深表歉意!感谢您提供的有趣反馈,我们对此表示赞赏。
Doktor OSwaldo

不用担心 由于使用了正则表达式,因此很容易想到要输入的内容很多。另外,我自愿承认我的解决方案很复杂。欢迎您提供反馈。:)
卢克·赫米特

1
的确,你是严厉的。大多数时候,我会提取存储在数组最后位置的另一信息,这是我最后插入的信息(最后一个元素)。例如,对于a +3,我可以写类似的东西\=add(thelist, 3 + get(thelist, -1, 0))[-1]
卢克·赫米特

5

几年前,我发现了一个类似但又不同的问题,并且在不完全了解我在做什么的情况下设法更改了其中一个答案,并且效果很好:

:let i = 1 | g/#\d\+\(\.\d\+\)\=/s//\=printf("#%d", i)/ | let i = i+1

具体来说,我不明白为什么不使用%我的或为什么我只使用其他答案出于某种原因而避免使用的普通变量。


1
这也是一种可能性。我认为这里的主要缺点是,每场比赛只能使用一个替换命令。因此它可能更慢。我们不使用普通变量的原因是,它将不会在常规s//g语句中进行更新。无论如何,这是一个有趣的解决方案。也许@LucHermitte可以告诉您更多有关优缺点的信息,因为与他相比,我对vimscript的了解非常有限。
Doktor OSwaldo

1
@DoktorOSwaldo。我想这个解决方案已经工作了很长时间了- printf()尽管没有-因为List是在Vim 7中引入的。但是我必须承认,我不希望(/不记得了?)这个问题<bar>属于Vim 的范围。:global-IOW,我期望的情况是:sub在匹配的行上应用,然后i在末尾增加一次。我希望这个解决方案会稍微慢一些。但这真的重要吗?重要的是我们能够轻松地从内存+试用和错误中获得一个可行的解决方案。例如,Vimgolfers喜欢使用宏。
卢克·赫米特

1
@LucHermitte是的,我ecpexted一样,没有速度没关系。我认为这是一个很好的答案,我再次从中学到了一些东西。也许g/s//示波器的行为允许其他肮脏的把戏。因此,感谢你们提供了有趣的答案和讨论,我从提供答案=)中学到的并不多。
Doktor OSwaldo

4

此页面上已经有三个 不错的 答案,但是,正如Luc Hermitte在评论中所建议的那样,如果您要进行现成的操作,那么重要的是,您可以快速轻松地找到一个可行的解决方案。

因此,这是我完全不会使用:substitute的问题:使用常规的普通模式命令和递归宏可以轻松解决该问题:

  1. (如有必要)首先关闭'wrapscan'。我们将要使用的正则表达式将匹配所需的结果文本以及初始文本,因此,'wrapscan'启用后,宏将永远继续播放。(或者直到您意识到正在发生的事情,然后按<C-C>。):

    :set nowrapscan
    
  2. 设置您的搜索字词(使用现有答案中已经提到的相同基本正则表达式):

    /#\d\+\(\.\d\+\)\?<CR>
    
  3. (如有必要)N所需次数多次跳回第一场比赛,

  4. (如有必要)将第一个匹配项更改为所需的文本:

    cE#1<Esc> 
    
  5. 清除"q寄存器并开始记录宏:

    qqqqq
    
  6. 拉当前计数器:

    yiW
    
  7. 跳至下一场比赛:

    n
    
  8. 用我们刚要的计数器替换当前计数器:

    vEp
    
  9. 递增计数器:

    <C-A>
    
  10. 播放宏q。寄存器"q仍然为空,因为我们在步骤5中将其清除了,因此此时没有任何反应:

    @q
    
  11. 停止录制宏:

    q
    
  12. 播放新的宏,然后观看!

    @q
    

与所有宏一样,如我上面所做的解释一样,这看起来像很多步骤,但是请注意,实际上对我来说键入这些命令非常快:除了递归宏记录样板之外,它们都只是常规的编辑命令我在编辑过程中一直在执行。只有在那里我做任何事情,甚至一步逼近的想法是第2步,在我写的正则表达式来进行搜索。

格式化为两个命令行模式命令和一系列击键,这种类型的解决方案的速度变得更加清晰:我可以像输入它一样快地构想出以下内容1

:set nowrapscan
/#\d\+\(\.\d\+\)\?
cE#1<Esc>qqqqqyiWnvEp<C-A>@qq@q

我可能会在本页面上找到其他解决方案,但需要一些思想和一些参考文档2,但是,一旦您了解了宏的工作原理,它们就很容易以您通常编辑的速度来生产。

1:在某些情况下,宏需要更多的思考,但是我发现它们在实践中并没有太多用。通常情况下,发生宏的情况是唯一可行的解决方案。

2:并不意味着其他回答者不可能同样容易地提出他们的解决方案:他们只需要技能/知识,而我个人并不那么容易触手可及。但是所有 Vim用户都知道如何使用常规编辑命令!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.