Wie ersetze ich jede Übereinstimmung durch einen inkrementierenden Zähler?

13

Ich möchte jedes Vorkommen eines bestimmten Musters suchen und durch eine Dezimalzahl ersetzen, die bei 1jeder Übereinstimmung beginnt und um eins erhöht wird.

Ich kann ähnlich formulierte Fragen finden, bei denen es nicht darum geht, einen Zähler zu erhöhen, sondern jede Übereinstimmung um einen festen Betrag zu ändern. Andere ähnliche Fragen betreffen das Einfügen von Zeilennummern anstelle eines inkrementierenden Zählers.

Beispiel vor:

#1
#1.25
#1.5
#2

Nach:

#1
#2
#3
#4

Meine realen Daten enthalten viel mehr Text rund um das Material, das ich neu nummerieren möchte.

Hippietrail
quelle
2
Wenn Sie haben perldo, können Sie verwenden:%perldo s/#\K\d+(\.\d+)?/++$i/ge
Sundeep
@Sundeep: Ich hätte erwähnen sollen, dass ich auf Vanille Windows 10 bin, sorry!
Hippietrail

Antworten:

15

Sie müssen durch einen Staat ersetzt werden. Ich erinnere mich, dass ich eine (/ mehrere?) Komplette Lösung für diese Art von Problemen auf SO bereitgestellt habe .

Hier ist eine andere Möglichkeit, um fortzufahren (1). Jetzt gehe ich in zwei Schritten vor:

  • Eine Dummy-Listenvariable, die ich für den schmutzigen und verschlungenen Trick brauche, den ich anwenden werde
  • Eine Ersetzung, bei der ich die Länge dieses Dummy-Arrays einfüge, das ich bei jedem übereinstimmenden Vorkommen fülle.

Welches gibt:

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

Wenn Sie es nicht gewohnt sind, reguläre Ausdrücke zu vimieren, verwende ich :h /\zsund gebe \zean, mit welchem ​​Untermuster ich übereinstimme. Dann stimme ich mit einer Reihe von Ziffern überein, möglicherweise gefolgt von einem Punkt und anderen Ziffern. Dies ist nicht perfekt für eine Gleitkommazahl, aber dies reicht hier aus.

Hinweis: Für eine einfache Benutzeroberfläche müssen Sie es in ein paar Funktionen + Befehl zusammenfassen. Wieder gibt es Beispiele für SO / vim ( hier , hier , hier ). Heutzutage weiß ich genug über vim, um diesen Trick nicht in einen Befehl zu packen. In der Tat kann ich diesen Befehl beim ersten Versuch schreiben, während ich mir einige Minuten Zeit nehme, um mir den Namen des Befehls zu merken.


(1) Ziel ist es, einen Zustand zwischen den Substitutionen aufrechtzuerhalten und das aktuelle Vorkommen durch etwas zu ersetzen, das vom aktuellen Zustand abhängt.

Dank können :s\=wir etwas einfügen, das aus einer Berechnung resultiert.

Bleibt das Problem des Staates. Entweder definieren wir eine Funktion, die einen externen Status verwaltet, oder wir aktualisieren uns selbst einen externen Status. In C (und verwandten Sprachen) hätten wir so etwas wie length++oder verwenden können length+=1. Leider kann in vim-Skripten +=nicht sofort verwendet werden. Es muss entweder mit :setoder mit verwendet werden :let. Dies bedeutet, dass :let length+=1eine Zahl erhöht wird, aber nichts zurückgegeben wird. Wir können nicht schreiben :s/pattern/\=(length+=1). Wir brauchen noch etwas.

Wir brauchen mutierende Funktionen. dh Funktionen, die ihre Eingaben mutieren. Wir haben setreg(), map(), add()und wahrscheinlich mehr. Beginnen wir mit ihnen.

  • setreg()mutiert ein Register. Perfekt. Wir können setreg('a',@a+1)wie in der Lösung von @Doktor OSwaldo schreiben . Und doch ist das nicht genug. setreg()ist eher eine Prozedur als eine Funktion (für diejenigen unter uns, die Pascal, Ada ... kennen). Dies bedeutet, dass nichts zurückgegeben wird. Eigentlich gibt es etwas zurück. Nominal Exit (dh nicht außergewöhnliche Exits) geben immer etwas zurück. Wenn wir vergessen haben, etwas zurückzugeben, wird standardmäßig 0 zurückgegeben - dies gilt auch für integrierte Funktionen. Deshalb ist in seiner Lösung der Ersatzausdruck tatsächlich \=@a+setreg(...). Tricky, nicht wahr?

  • map()könnte auch verwendet werden. Wenn wir von einer Liste mit einer einzelnen 0 ( :let single_length=[0]) ausgehen, können wir sie dank erhöhen map(single_length, 'v:val + 1'). Dann müssen wir die neue Länge zurückgeben. Im Gegensatz dazu setreg()wird map()die mutierte Eingabe zurückgegeben. Das ist perfekt, die Länge wird an der ersten (und eindeutigen und damit auch letzten) Position der Liste gespeichert. Der Ersatzausdruck könnte sein \=map(...)[0].

  • add()ist die, die ich oft aus Gewohnheit benutze (ich habe gerade darüber map()nachgedacht, und ich habe ihre jeweiligen Auftritte noch nicht festgelegt). Die Idee dabei add()ist, eine Liste als aktuellen Status zu verwenden und am Ende vor jeder Ersetzung etwas anzuhängen. Ich speichere den neuen Wert oft am Ende der Liste und verwende ihn als Ersatz. Da add()auch die mutierte Eingabeliste zurückgegeben wird, können wir verwenden : \=add(state, Func(state[-1], submatch(0)))[-1]. Im Fall von OP müssen wir uns nur daran erinnern, wie viele Übereinstimmungen bisher erkannt wurden. Es reicht aus, die Länge dieser Statusliste zurückzugeben. Daher mein \=len(add(state, whatever)).

Luc Hermitte
quelle
Ich glaube, ich verstehe das, aber warum der Trick mit dem Array und seiner Länge im Vergleich zum Hinzufügen eines Variablen zu einer Variablen?
Hippietrail
1
Dies liegt daran, \=dass ein Ausdruck erwartet wird und dass im Gegensatz zu C i+=1kein Ausdruck inkrementiert und zurückgegeben wird. Dies bedeutet, dass \=ich etwas brauche, das einen Zähler ändern kann und einen Ausdruck zurückgibt (der diesem Zähler entspricht). Bisher habe ich nur die Manipulationsfunktionen für Listen (und Wörterbücher) gefunden. @Doktor OSwaldo hat eine andere Mutationsfunktion verwendet ( setreg()). Der Unterschied besteht darin, dass setreg()niemals etwas zurückgegeben wird, was bedeutet, dass immer die Nummer zurückgegeben wird 0.
Luc Hermitte
Wow interessant! Sowohl Ihr als auch sein Trick sind so magisch, dass ich denke, Ihre Antworten würden davon profitieren, sie in Ihren Antworten zu erklären. Ich würde annehmen, dass nur die fließendsten Vimscripter solche unintuitiven Redewendungen kennen würden.
Hippietrail
2
@ Hippietrail. Erklärungen hinzugefügt. Lassen Sie mich wissen, wenn Sie spezifischere Präzisionen benötigen.
Luc Hermitte
13
 :let @a=1 | %s/search/\='replace'.(@a+setreg('a',@a+1))/g

Aber Vorsicht, es wird Ihr Register überschreiben a. Ich denke, es ist ein bisschen einfacher als Lucs Antwort, aber vielleicht ist seine schneller. Wenn diese Lösung irgendwie schlechter ist als seine, würde ich gerne Feedback hören, warum seine Antwort besser ist. Jedes Feedback zur Verbesserung der Antwort wird sehr geschätzt!

(Es basiert auch auf einer SO-Antwort von mir /programming/43539251/how-to-replace-finding-words-with-the-different-in-each-occurrence-in-vi-vim -edi / 43539546 # 43539546 )

Doktor OSwaldo
quelle
Ich sehe nicht, wie @a+setreg('a',@a+1)kürzer ist als len(add(t,1)). Ansonsten ist das ein schöner anderer schmutziger Trick :). Ich habe nicht an diesen gedacht. In Bezug auf die Verwendung einer Wörterbuch-Mutationsfunktion im Ersatztext von :sund habe substitute()ich festgestellt, dass dies viel schneller ist als explizite Schleifen - daher die Implementierung meiner Listenfunktionen in lh-vim-lib . Ich denke, Ihre Lösung wird meiner ebenbürtig sein, vielleicht etwas schneller, ich weiß es nicht.
Luc Hermitte
2
In Bezug auf Präferenzen bevorzuge ich meine Lösung aus einem einzigen Grund: Sie bleibt @aunverändert. In Skripten ist dies IMO wichtig. Im interaktiven Modus weiß ich als Endbenutzer, welches Register ich verwenden kann. Das Spielen mit einem Register ist weniger wichtig. In meiner Lösung wird im interaktiven Modus eine globale Variable durcheinander gebracht. In einem Skript wäre es eine lokale Variable.
Luc Hermitte
@LucHermitte Sorry, meine Lösung ist in der Tat nicht kürzer als deine, ich sollte sie besser lesen, bevor ich eine solche Erklärung schreibe. Ich habe diese Aussage aus meiner Antwort entfernt und möchte mich entschuldigen! Vielen Dank für Ihr interessantes Feedback, ich schätze es.
Doktor OSwaldo
Mach dir keine Sorgen. Aufgrund des regulären Ausdrucks ist es leicht zu glauben, dass es viel zu tippen gibt. Außerdem gebe ich freiwillig zu, dass meine Lösung kompliziert ist. Sie sind für das Feedback willkommen. :)
Luc Hermitte
1
In der Tat bist du streng. Meistens extrahiere ich andere Informationen, die ich an der letzten Position des Arrays speichere. Dies ist das (letzte Element), das ich am Ende einfüge. Zum Beispiel +3könnte ich so etwas schreiben \=add(thelist, 3 + get(thelist, -1, 0))[-1].
Luc Hermitte
5

Ich habe eine ähnliche, aber andere Frage gefunden, die ich vor ein paar Jahren gestellt habe, und es geschafft, eine ihrer Antworten zu ändern, ohne vollständig zu verstehen, was ich tat, und es hat großartig funktioniert:

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

Insbesondere verstehe ich nicht, warum meine nicht verwendet wird %oder warum ich nur eine einfache Variable verwende, die die anderen Antworten aus irgendeinem Grund vermeiden.

Hippietrail
quelle
1
das ist auch eine möglichkeit. Ich denke, der Hauptnachteil hier ist, dass Sie einen Ersatzbefehl pro Spiel verwenden. Es ist also wahrscheinlich langsamer. Der Grund, warum wir keine einfache Variable verwenden, ist, dass sie in einer normalen s//gAnweisung nicht aktualisiert wird . Auf jeden Fall ist es eine interessante Lösung. Vielleicht kann @LucHermitte Ihnen mehr über Vor- und Nachteile erzählen, da mein Wissen über Vimscript im Vergleich zu seinem ziemlich begrenzt ist.
Doktor OSwaldo
1
@ DoktorOSwaldo. Ich denke, diese Lösung funktioniert schon länger - ohne printf()jedoch -, als Listen in Vim 7 eingeführt wurden. Aber ich muss zugeben, ich hätte nicht erwartet (/ erinnere mich nicht?) <bar>, Dass sie zum Geltungsbereich von gehören :global- IOW, das Szenario, das ich erwartet hatte, war, das :subauf die übereinstimmenden Zeilen anzuwenden und dann iganz am Ende einmal zu erhöhen. Ich erwarte, dass diese Lösung etwas langsamer ist. Aber ist das wirklich wichtig? Wichtig ist, wie einfach es ist, eine funktionierende Lösung aus Speicher + Versuch & Irrtum zu finden. Zum Beispiel bevorzugen Vimgolfers Makros.
Luc Hermitte
1
@ LucHermitte Ja, ich habe das gleiche ecpexted, und nein, die Geschwindigkeit spielt keine Rolle. Ich denke, es ist eine gute Antwort, und ich habe wieder etwas daraus gelernt. Möglicherweise g/s//erlaubt das Scope-Verhalten andere schmutzige Tricks. Ich danke Ihnen beiden für die interessanten Antworten und die Diskussion. Ich lerne nicht oft so viel, wenn ich eine Antwort gebe =).
Doktor OSwaldo
4

Es gibt bereits drei großartige Antworten auf dieser Seite, aber wie Luc Hermitte in einem Kommentar vorgeschlagen hat , ist es wichtig, wie schnell und einfach Sie auf eine funktionierende Lösung stoßen können, wenn Sie dies direkt tun.

Als solches ist dies ein Problem, das ich eigentlich gar nicht verwenden würde :substitute: Es kann leicht mit regulären Normalmodusbefehlen und einem rekursiven Makro gelöst werden:

  1. (Falls erforderlich) Schalten Sie zuerst aus 'wrapscan'. Der reguläre Ausdruck, den wir verwenden werden, stimmt sowohl mit dem gewünschten Ergebnistext als auch mit dem Anfangstext überein. Wenn diese Option aktiviert ist, wird 'wrapscan'das Makro ansonsten für immer wiedergegeben. (Oder bis Sie erkennen, was passiert und drücken <C-C>.):

    :set nowrapscan
    
  2. Richten Sie Ihren Suchbegriff ein (unter Verwendung des gleichen regulären Basisausdrucks, der bereits in den vorhandenen Antworten erwähnt wurde):

    /#\d\+\(\.\d\+\)\?<CR>
    
  3. (Falls erforderlich) Kehren Sie zum ersten Spiel zurück, indem Sie Nso oft wie nötig drücken.

  4. (Falls erforderlich) Ändern Sie die erste Übereinstimmung in den gewünschten Text:

    cE#1<Esc> 
    
  5. Löschen Sie das "qRegister und beginnen Sie mit der Aufnahme eines Makros:

    qqqqq
    
  6. Ziehen Sie den aktuellen Zähler:

    yiW
    
  7. Zum nächsten Spiel springen:

    n
    
  8. Ersetzen Sie den aktuellen Zähler durch den, den wir gerade gezogen haben:

    vEp
    
  9. Erhöhen Sie den Zähler:

    <C-A>
    
  10. Makro abspielen q. Das Register "qist noch leer, da wir es in Schritt 5 gelöscht haben. Daher passiert zu diesem Zeitpunkt nichts:

    @q
    
  11. Beenden Sie die Aufzeichnung des Makros:

    q
    
  12. Spielen Sie das neue Makro und schauen Sie zu!

    @q
    

Wie bei allen Makros sieht dies nach vielen Schritten aus, wenn ich sie wie oben beschrieben erläutere. Beachten Sie jedoch, dass das Eintippen dieser Schritte für mich sehr schnell geht: Abgesehen von der rekursiven Makroaufzeichnungs-Boilerplate sind sie alle nur die regulären Bearbeitungsbefehle Ich führe die ganze Zeit während der Bearbeitung aus. Der einzige Schritt, in dem ich etwas tun musste, das sich dem Denken näherte , war Schritt 2, in dem ich den regulären Ausdruck schrieb, um die Suche durchzuführen.

Als zwei Befehle im Befehlszeilenmodus und eine Reihe von Tastenanschlägen formatiert, wird die Geschwindigkeit dieser Art von Lösung klarer: Ich kann Folgendes so schnell heraufbeschwören, wie ich es 1 eingeben kann :

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

Ich hätte mir wahrscheinlich die anderen Lösungen auf dieser Seite mit ein wenig Nachdenken und einigen Verweisen auf die Dokumentation 2 einfallen lassen können , aber wenn Sie erst einmal verstanden haben, wie Makros funktionieren, sind sie wirklich einfach mit der Geschwindigkeit zu erstellen, die Sie normalerweise bearbeiten.

1: Es gibt Situationen, in denen Makros mehr Nachdenken erfordern, aber ich finde, dass sie in der Praxis nicht viel auftauchen. Und im Allgemeinen treten sie in Situationen auf, in denen ein Makro die einzig praktikable Lösung ist.

2: Um nicht zu implizieren, dass die anderen Antwortenden ihre Lösungen nicht so einfach hätten finden können: Sie benötigen nur Fähigkeiten / Kenntnisse, die ich persönlich nicht so einfach zur Hand habe. Aber alle Vim-Benutzer wissen, wie man reguläre Bearbeitungsbefehle verwendet!

Reich
quelle