Comment remplacer chaque correspondance par un compteur incrémentiel?

14

Je veux rechercher et remplacer chaque occurrence d'un certain modèle par un nombre décimal qui commence à 1et augmente par un pour chaque correspondance.

Je peux trouver des questions formulées de manière similaire qui ne consistent pas à incrémenter un compteur mais à modifier chaque correspondance d'un montant fixe. D'autres questions similaires concernent l'insertion de numéros de ligne plutôt qu'un compteur d'incrémentation.

Exemple, avant:

#1
#1.25
#1.5
#2

Après:

#1
#2
#3
#4

Mes vraies données ont beaucoup plus de texte tout autour des choses que je veux renuméroter.

hippietrail
la source
2
si vous en avez perldo, vous pouvez utiliser:%perldo s/#\K\d+(\.\d+)?/++$i/ge
Sundeep
@Sundeep: j'aurais dû mentionner que je suis sur Windows 10 vanilla, désolé!
hippietrail

Réponses:

15

Vous avez besoin d'une substitution par un état. Je me souviens avoir fourni une (/ plusieurs?) Solution complète pour ce genre de problèmes sur SO.

Voici une autre façon de procéder (1). Maintenant, je vais procéder en 2 étapes:

  • une variable de liste fictive dont j'ai besoin pour l'astuce sale et alambiquée que j'emploierai
  • une substitution où j'insère le len de ce tableau factice que je remplis à chaque occurrence correspondante.

Qui donne:

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

Si vous n'êtes pas habitué aux expressions rationnelles vim, j'utilise :h /\zset \zepour spécifier le sous-motif que je fais correspondre, alors je fais correspondre une série de chiffres éventuellement suivie d'un point et d'autres chiffres. Ce n'est pas parfait pour n'importe quel nombre à virgule flottante, mais cela suffit ici.

Remarque: vous devrez le conclure dans une fonction couple + commande pour une interface simple. Encore une fois, il y a des exemples sur SO / vim ( ici , ici , ici ) De nos jours, je connais assez de vim pour ne pas se soucier d'encapsuler cette astuce dans une commande. En effet je pourrai écrire cette commande du premier coup, tandis que je prendrai quelques minutes pour me souvenir du nom de la commande.


(1) L'objectif est de pouvoir maintenir un état entre les substitutions et de remplacer l'occurrence actuelle par quelque chose qui dépend de l'état actuel.

Merci à :s\=nous sommes en mesure d'insérer quelque chose résultant d'un calcul.

Reste le problème de l'Etat. Soit nous définissons une fonction qui gère un état externe, soit nous mettons à jour nous-mêmes un état externe. En C (et dans les langages apparentés), nous aurions pu utiliser quelque chose comme length++ou length+=1. Malheureusement, dans les scripts vim, +=ne peut pas être utilisé hors de la boîte. Il doit être utilisé avec :setou avec :let. Cela signifie que :let length+=1incrémente un nombre, mais ne renvoie rien. Nous ne pouvons pas écrire :s/pattern/\=(length+=1). Nous avons besoin d'autre chose.

Nous avons besoin de fonctions de mutation. c'est-à-dire des fonctions qui modifient leurs entrées. Nous avons setreg(), map(), add()et probablement plus. Commençons par eux.

  • setreg()mute un registre. Parfait. Nous pouvons écrire setreg('a',@a+1)comme dans la solution de @Doktor OSwaldo. Et pourtant, cela ne suffit pas. setreg()est plus une procédure qu'une fonction (pour ceux d'entre nous qui connaissent Pascal, Ada ...). Cela signifie qu'il ne retourne rien. En fait, cela retourne quelque chose. La sortie nominale (c'est -à- dire les sorties non exceptionnelles ) renvoie toujours quelque chose. Par défaut, lorsque nous avons oublié de renvoyer quelque chose, 0 est retourné - cela s'applique également aux fonctions intégrées. C'est pourquoi dans sa solution, l'expression de remplacement est en fait \=@a+setreg(...). Tricky, n'est-ce pas?

  • map()pourrait également être utilisé. Si nous partons d'une liste avec un seul 0 ( :let single_length=[0]), nous pourrions l'incrémenter grâce à map(single_length, 'v:val + 1'). Ensuite, nous devons renvoyer la nouvelle longueur. Contrairement à setreg(), map()renvoie son entrée mutée. C'est parfait, la longueur est stockée à la première (et unique, et donc aussi la dernière) de la liste. L'expression de remplacement pourrait être \=map(...)[0].

  • add()est celui que j'utilise souvent par habitude (je l'ai juste réfléchi map()et je n'ai pas encore évalué leurs performances respectives). L'idée avec add()est d'utiliser une liste comme état actuel et d'ajouter quelque chose à la fin avant chaque substitution. Je stocke souvent la nouvelle valeur à la fin de la liste et l'utilise pour le remplacement. Comme add()retourne aussi sa liste d'entrée mutées, nous pouvons utiliser: \=add(state, Func(state[-1], submatch(0)))[-1]. Dans le cas d'OP, il suffit de se rappeler combien de correspondances ont été détectées jusqu'à présent. Il suffit de renvoyer la longueur de cette liste d'états. D'où mon \=len(add(state, whatever)).

Luc Hermitte
la source
Je pense que je comprends celui-ci, mais pourquoi l'astuce avec le tableau et sa longueur par rapport à l'ajout d'un à une variable?
hippietrail
1
C'est parce \=qu'attend une expression, et parce que contrairement à C, ce i+=1n'est pas quelque chose qui incrémente et renvoie une expression. Cela signifie que derrière \=j'ai besoin de quelque chose qui peut modifier un compteur et qui renvoie une expression (égale à ce compteur). Jusqu'à présent, les seules choses que j'ai trouvées sont les fonctions de manipulation de liste (et de dictionnaire). @Doktor OSwaldo a utilisé une autre fonction de mutation ( setreg()). la différence est que setreg()ne renvoie jamais rien, ce qui signifie qu'il renvoie toujours le nombre 0.
Luc Hermitte
Wow intéressant! Votre astuce et la sienne sont si magiques que je pense que vos réponses gagneraient à les expliquer dans vos réponses. Je suppose que seuls les vimscripters les plus fluides connaîtront de tels idiomes non intuitifs.
hippietrail
2
@hippietrail. Explications ajoutées. Faites-moi savoir si vous avez besoin de précisions plus spécifiques.
Luc Hermitte
13
 :let @a=1 | %s/search/\='replace'.(@a+setreg('a',@a+1))/g

Mais attention, cela écrasera votre registre a. Je pense que c'est un peu plus simple que la réponse de luc, mais peut-être que la sienne est plus rapide. Si cette solution est en quelque sorte pire que la sienne, j'aimerais entendre tout commentaire pourquoi sa réponse est meilleure. Tout commentaire pour améliorer la réponse sera très apprécié!

(Il est également basé sur une réponse SO de la mienne /programming/43539251/how-to-replace-finding-words-with-the-different-in-each-occurrence-in-vi-vim -edi / 43539546 # 43539546 )

Doktor OSwaldo
la source
Je ne vois pas comment @a+setreg('a',@a+1)est plus court que len(add(t,1)). Sinon, c'est une autre astuce sale :). Je n'ai pas encore pensé à celui-ci. En ce qui concerne l'utilisation d'une fonction de mutation de dictionnaire dans le texte de remplacement, de :set substitute(), j'ai remarqué que c'est beaucoup plus rapide que les boucles explicites - d'où l' implémentation de mes fonctions de liste dans lh-vim-lib . Je suppose que votre solution sera comparable à la mienne, peut-être un peu plus rapide, je ne sais pas.
Luc Hermitte
2
Concernant les préférences, je préfère ma solution pour une seule raison: elle laisse @ainchangée. Dans les scripts, c'est important IMO. En mode interactif, en tant qu'utilisateur final, je saurai quel registre je peux utiliser. Jouer avec un registre est moins important. Dans ma solution, en mode interactif, une variable globale est gâchée; dans un script, ce serait une variable locale.
Luc Hermitte
@LucHermitte Désolé, ma solution n'est en effet pas plus courte que la vôtre, je devrais la lire mieux avant d'écrire une telle déclaration. J'ai supprimé ladite déclaration de ma réponse et je voudrais m'excuser! Merci pour vos commentaires intéressants, je l'apprécie.
Doktor OSwaldo
Ne t'en fais pas. En raison de l'expression régulière, il est facile de penser qu'il y a beaucoup à taper. De plus, j'admets volontairement que ma solution est compliquée. Vous êtes les bienvenus pour les commentaires. :)
Luc Hermitte
1
En effet, vous êtes juste. La plupart du temps, j'extrais une autre information que je stocke dans la dernière position du tableau, qui est ce que (le dernier élément) j'insère à la fin. Par exemple, pour un +3, je pourrais écrire quelque chose comme \=add(thelist, 3 + get(thelist, -1, 0))[-1].
Luc Hermitte
5

J'ai trouvé une question similaire mais différente que j'ai posée il y a quelques années et j'ai réussi à changer l'une de ses réponses sans bien comprendre ce que je faisais et cela fonctionne très bien:

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

Plus précisément, je ne comprends pas pourquoi la mienne n'utilise pas %ou pourquoi j'utilise simplement une variable simple que les autres réponses évitent pour une raison quelconque.

hippietrail
la source
1
c'est aussi une possibilité. Je pense que le principal inconvénient ici est que vous utilisez une commande de substitution par match. C'est donc probablement plus lent. La raison pour laquelle nous n'utilisons pas de variable simple, c'est qu'elle ne sera pas mise à jour dans une s//ginstruction normale . Quoi qu'il en soit, c'est une solution intéressante. Peut-être que @LucHermitte peut vous en dire plus sur les avantages et les inconvénients, car mes connaissances sur vimscript sont assez limitées par rapport aux siennes.
Doktor OSwaldo
1
@DoktorOSwaldo. Je suppose que cette solution fonctionne depuis plus longtemps - sans pour autant printf()- car les listes ont été introduites dans Vim 7. Mais je dois admettre que je ne m'attendais pas (/ je ne me souviens pas?) À <bar>appartenir à la portée de :global- IOW, le scénario auquel je m'attendais était d'appliquer le :subsur les lignes correspondantes, puis d'incrémenter iune fois à la toute fin. Je m'attends à ce que cette solution soit légèrement plus lente. Mais est-ce que c'est vraiment important? L'important est la facilité avec laquelle nous pouvons trouver une solution de travail à partir de la mémoire + essais et erreurs. Par exemple, les Vimgolfers préfèrent les macros.
Luc Hermitte
1
@ LucHermitte ouais j'ai ecpexted la même chose, et non la vitesse n'a pas d'importance. Je pense que c'est une bonne réponse, et j'en ai encore appris quelque chose. Peut-être que le g/s//comportement de la portée permet d'autres astuces sales. Alors merci à vous deux pour les réponses intéressantes et la discussion, je n'apprends pas souvent grand-chose en donnant une réponse =).
Doktor OSwaldo
4

Il y a déjà trois bonnes réponses sur cette page, mais, comme l'a suggéré Luc Hermitte dans un commentaire , si vous faites cela au pied levé, l'important est la rapidité et la facilité avec laquelle vous pouvez trouver une solution de travail.

En tant que tel, c'est un problème que je n'utiliserais pas :substitutedu tout: c'est un problème qui peut facilement être résolu en utilisant des commandes normales en mode normal et une macro récursive:

  1. (Si nécessaire) Tout d'abord, désactivez 'wrapscan'. L'expression régulière que nous allons utiliser correspondra au texte de résultat souhaité ainsi qu'au texte initial, donc avec 'wrapscan'on, la macro continuerait sinon à être lue pour toujours. (Ou jusqu'à ce que vous réalisiez ce qui se passe et appuyez sur <C-C>.):

    :set nowrapscan
    
  2. Configurez votre terme de recherche (en utilisant la même expression régulière de base déjà mentionnée dans les réponses existantes):

    /#\d\+\(\.\d\+\)\?<CR>
    
  3. (Si nécessaire) Revenez au premier match en appuyant Nautant de fois que nécessaire,

  4. (Si nécessaire) Remplacez la première correspondance par le texte souhaité:

    cE#1<Esc> 
    
  5. Effacez le "qregistre et commencez à enregistrer une macro:

    qqqqq
    
  6. Tirez sur le compteur actuel:

    yiW
    
  7. Passez au match suivant:

    n
    
  8. Remplacez le compteur actuel par celui que nous venons de tirer:

    vEp
    
  9. Incrémentez le compteur:

    <C-A>
    
  10. Jouer la macro q. Le registre "qest toujours vide car nous l'avons effacé à l'étape 5, donc rien ne se passe à ce stade:

    @q
    
  11. Arrêtez l'enregistrement de la macro:

    q
    
  12. Jouez la nouvelle macro et regardez!

    @q
    

Comme pour toutes les macros, cela ressemble à beaucoup d'étapes lorsque je l'explique comme je l'ai fait ci-dessus, mais notez qu'en fait, les saisir est très rapide pour moi: à part le récapitulatif-macro-enregistrement-passe-partout, ils ne sont que les habituels les commandes d'édition que j'exécute tout le temps pendant l'édition. La seule étape où j'ai dû faire quoi que ce soit même en approchant de la pensée était l'étape 2, où j'ai écrit l'expression régulière pour effectuer la recherche.

Formatée en deux commandes en mode ligne de commande et une série de frappes, la vitesse de ce type de solution devient plus claire: je peux évoquer les éléments suivants à peu près aussi vite que je peux taper 1 :

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

J'aurais probablement pu trouver les autres solutions sur cette page avec un peu de réflexion et un certain référencement de la documentation 2 , mais, une fois que vous comprenez le fonctionnement des macros, elles sont vraiment faciles à produire à la vitesse que vous modifiez habituellement.

1: Il y a des situations où les macros nécessitent plus de réflexion, mais je trouve qu'elles n'apparaissent pas beaucoup dans la pratique. Et généralement, les situations où elles se produisent sont celles où une macro est la seule solution pratique.

2: Cela ne veut pas dire que les autres répondeurs n'auraient pas pu trouver leurs solutions de la même manière: ils ont juste besoin de compétences / connaissances que je n'ai pas si facilement à portée de main. Mais tous les utilisateurs de Vim savent utiliser les commandes d'édition habituelles!

Riches
la source