Jak zastąpić każde dopasowanie rosnącym licznikiem?

14

Chcę wyszukać i zastąpić każde wystąpienie określonego wzorca liczbą dziesiętną, która zaczyna się 1i zwiększa o jeden dla każdego dopasowania.

Mogę znaleźć podobnie sformułowane pytania, które okazują się nie dotyczyć zwiększania licznika, ale modyfikowania każdego dopasowania o ustaloną kwotę. Inne podobne pytania dotyczą wstawiania numerów linii zamiast licznika rosnącego.

Przykład przed:

#1
#1.25
#1.5
#2

Po:

#1
#2
#3
#4

Moje prawdziwe dane zawierają o wiele więcej tekstu wokół wszystkich rzeczy, które chcę ponownie numerować.

hippietrail
źródło
2
jeśli masz perldo, możesz użyć:%perldo s/#\K\d+(\.\d+)?/++$i/ge
Sundeep
@ Sundeep: powinienem wspomnieć, że jestem na waniliowym systemie Windows 10, przepraszam!
hippietrail

Odpowiedzi:

15

Potrzebujesz podstawienia ze stanem. Pamiętam, że dostarczyłem (/ kilka?) Kompletne rozwiązanie dla tego rodzaju problemów na SO.

Oto inny sposób postępowania (1). Teraz przejdę do 2 kroków:

  • zmienna fikcyjna lista, której potrzebuję do brudnej i skomplikowanej sztuczki, którą zastosuję
  • podstawienie, w którym wstawiam len tej sztucznej tablicy, którą wypełniam przy każdym pasującym wystąpieniu.

Co daje:

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

Jeśli nie jesteś przyzwyczajony do vimowania wyrażeń regularnych, używam :h /\zsi \zedo określenia, który pod-wzór pasuję, a następnie dopasowuję ciąg cyfr, po którym ewentualnie może być kropka i inne cyfry. Nie jest to idealne dla żadnej liczby zmiennoprzecinkowej, ale tutaj wystarczy.

Uwaga: Będziesz musiał zawinąć to w kilka funkcji + polecenie dla prostego interfejsu. Znów są przykłady na SO / vim ( tutaj , tutaj , tutaj ) Obecnie znam wystarczająco dużo vima, aby nie obchodzić go zawarcie tej sztuczki w poleceniu. Rzeczywiście będę mógł napisać to polecenie przy pierwszej próbie, podczas gdy zajmie mi kilka minut, aby zapamiętać nazwę polecenia.


(1) Celem jest utrzymanie stanu pomiędzy podstawieniami i zastąpienie bieżącego wystąpienia czymś, co zależy od bieżącego stanu.

Dzięki temu :s\=możemy wstawić coś wynikającego z obliczeń.

Pozostaje problem państwa. Albo zdefiniujemy funkcję zarządzającą stanem zewnętrznym, albo sami zaktualizujemy stan zewnętrzny. W C (i językach pokrewnych) moglibyśmy użyć czegoś takiego jak length++lub length+=1. Niestety, w skryptach vim +=nie można używać po wyjęciu z pudełka. Należy go używać z :setlub z :let. Oznacza to, że :let length+=1zwiększa liczbę, ale niczego nie zwraca. Nie możemy pisać :s/pattern/\=(length+=1). Potrzebujemy czegoś innego.

Potrzebujemy funkcji mutacji. tj. funkcje, które mutują ich dane wejściowe. Mamy setreg(), map(), add()a prawdopodobnie więcej. Zacznijmy od nich.

  • setreg()mutuje rejestr. Idealny. Możemy pisać setreg('a',@a+1)jak w rozwiązaniu @Doktor OSwaldo. A jednak to nie wystarczy. setreg()jest raczej procedurą niż funkcją (dla tych, którzy znają Pascala, Adę ...). Oznacza to, że nic nie zwraca. W rzeczywistości coś zwraca. Nominalne wyjście (tj. Nie wyjątkowe wyjścia) zawsze coś zwraca. Domyślnie, gdy zapomnieliśmy coś zwrócić, zwracane jest 0 - dotyczy to również wbudowanych funkcji. Właśnie dlatego w jego rozwiązaniu wyrażenie zastępcze jest w rzeczywistości \=@a+setreg(...). Podstępne, prawda?

  • map()można również użyć. Jeśli zaczniemy od listy z pojedynczym 0 ( :let single_length=[0]), możemy ją zwiększyć dzięki map(single_length, 'v:val + 1'). Następnie musimy zwrócić nową długość. W przeciwieństwie setreg(), map()zwraca jego wejście zmutowany. To idealne, długość jest zapisywana na pierwszej (i unikalnej, a więc także na końcu) pozycji na liście. Wyrażeniem zastępującym może być \=map(...)[0].

  • add()to ten, którego często używam z przyzwyczajenia (tak map()naprawdę myślałem o tym i jeszcze nie porównałem ich odpowiednich występów). Chodzi o add()to, aby użyć listy jako bieżącego stanu i dołączyć coś na końcu przed każdą zamianą. Często przechowuję nową wartość na końcu listy i używam jej do zamiany. Jak add()również zwraca swoją listę wejściową zmutowany, możemy użyć: \=add(state, Func(state[-1], submatch(0)))[-1]. W przypadku OP musimy tylko pamiętać, ile wykryto do tej pory dopasowań. Wystarczy zwrócić długość tej listy stanów. Stąd mój \=len(add(state, whatever)).

Luc Hermitte
źródło
Myślę, że to rozumiem, ale dlaczego sztuczka z tablicą i jej długością w porównaniu do dodawania jednej do zmiennej?
hippietrail
1
Wynika to z faktu, \=że oczekuje wyrażenia, a ponieważ w przeciwieństwie do C, i+=1nie jest czymś, co zwiększa i zwraca wyrażenie. Oznacza to, \=że potrzebuję czegoś, co może zmodyfikować licznik i które zwraca wyrażenie (równe temu licznikowi). Jak dotąd jedyne rzeczy, które znalazłem, to funkcje manipulowania listą (i słownikiem). @Doktor OSwaldo użył innej funkcji mutującej ( setreg()). różnica polega na tym, że setreg()nigdy nie zwraca niczego, co oznacza, że ​​zawsze zwraca liczbę 0.
Luc Hermitte,
Wow, ciekawe! Zarówno twoja sztuczka, jak i jego są tak magiczne, że myślę, że twoje odpowiedzi skorzystałyby z wyjaśnienia ich w twoich odpowiedziach. Zakładam, że tylko najbardziej płynni vimscripterzy znają takie nieintuicyjne idiomy.
hippietrail
2
@hippietrail. Dodano wyjaśnienia. Daj mi znać, jeśli potrzebujesz bardziej szczegółowych informacji.
Luc Hermitte,
14
 :let @a=1 | %s/search/\='replace'.(@a+setreg('a',@a+1))/g

Ale uwaga, spowoduje to nadpisanie rejestru a. Myślę, że jest to trochę bardziej bezpośrednie niż odpowiedź Luca, ale może jego jest szybszy. Jeśli to rozwiązanie jest w jakiś sposób gorsze od jego, chciałbym usłyszeć wszelkie opinie, dlaczego jego odpowiedź jest lepsza. Wszelkie opinie na temat poprawy odpowiedzi będą mile widziane!

(Opiera się również na mojej SO odpowiedzi /programming/43539251/how-to-replace-finding-words-with-the-different-in-each-occurrence-in-vi-vim -edi / 43539546 # 43539546 )

Doktor OSwaldo
źródło
Nie rozumiem, jak @a+setreg('a',@a+1)jest krótszy niż len(add(t,1)). W przeciwnym razie to fajna inna brudna sztuczka :). Nie myślałem o tym. Jeśli chodzi o użycie funkcji mutacji słownikowej w tekście zastępczym :si substitute()zauważyłem, że jest to znacznie szybsze niż jawne pętle - stąd implementacja funkcji mojej listy w lh-vim-lib . Myślę, że twoje rozwiązanie będzie na równi z moim, może być trochę szybsze, nie wiem.
Luc Hermitte,
2
Jeśli chodzi o preferencje, wolę moje rozwiązanie z jednego powodu: pozostawia @aniezmodyfikowane. W skryptach jest to ważne IMO. Będąc w trybie interaktywnym, jako użytkownik końcowy będę wiedział, którego rejestru mogę użyć. Bałaganowanie rejestru jest mniej ważne. W moim rozwiązaniu w trybie interaktywnym zmienna globalna jest pomieszana z; w skrypcie byłaby to zmienna lokalna.
Luc Hermitte,
@LucHermitte Przepraszam, moje rozwiązanie rzeczywiście nie jest krótsze niż twoje, powinienem je lepiej przeczytać przed napisaniem takiego oświadczenia. Usunąłem to oświadczenie z mojej odpowiedzi i chciałbym przeprosić! Dziękuję za interesujące opinie, doceniam to.
Doktor OSwaldo
Nie martw się o to. Z powodu wyrażenia regularnego łatwo jest pomyśleć, że jest wiele do pisania. Ponadto dobrowolnie przyznam, że moje rozwiązanie jest zawiłe. Czekamy na opinie. :)
Luc Hermitte,
1
Rzeczywiście jesteś sztywny. Przez większość czasu wydobywam inne informacje, które przechowuję na ostatniej pozycji tablicy, czyli to, co (ostatni element) wstawiam na końcu. Na przykład, +3mógłbym napisać coś takiego \=add(thelist, 3 + get(thelist, -1, 0))[-1].
Luc Hermitte,
5

Znalazłem podobne, ale inne pytanie, które zadałem kilka lat temu i udało mi się zmienić jedną z odpowiedzi, nie w pełni rozumiejąc, co robię i działa świetnie:

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

W szczególności nie rozumiem, dlaczego moja nie używa %lub dlaczego po prostu używam prostej zmiennej, której inne odpowiedzi unikają z jakiegoś powodu.

hippietrail
źródło
1
to też jest możliwe. Myślę, że główną wadą jest to, że używasz jednego polecenia zastępczego na mecz. Więc prawdopodobnie jest wolniejszy. Powodem, dla którego nie używamy zwykłej zmiennej, jest to, że nie będzie ona aktualizowana w zwykłej s//ginstrukcji. W każdym razie jest to interesujące rozwiązanie. Może @LucHermitte może powiedzieć więcej o zaletach i wadach, ponieważ moja wiedza na temat vimscript jest dość ograniczona w porównaniu z jego.
Doktor OSwaldo
1
@DoktorOSwaldo. Wydaje mi się, że to rozwiązanie działało przez dłuższy czas - printf()chociaż nie - ponieważ Listy zostały wprowadzone w Vimie 7. Ale muszę przyznać, że nie spodziewałbym się (/ nie pamiętam?), Że <bar>należy do zakresu :global- IOW, scenariusz, którego się spodziewałem, polegałby na zastosowaniu :subpasujących linii, a następnie zwiększeniu iraz na samym końcu. Spodziewam się, że to rozwiązanie będzie nieco wolniejsze. Ale czy to naprawdę ma znaczenie? Ważne jest, jak łatwo możemy znaleźć działające rozwiązanie z pamięci + próby i błędów. Na przykład Vimgolfers preferuje makra.
Luc Hermitte,
1
@LucHermitte tak, napisałem to samo, i nie ma znaczenia, że ​​prędkość. Myślę, że to dobra odpowiedź i znów się z tego czegoś nauczyłem. Może g/s//zachowanie lunety pozwala na inne brudne sztuczki. Dziękuję wam więc za interesujące odpowiedzi i dyskusję, często nie uczę się tak wiele z udzielania odpowiedzi =).
Doktor OSwaldo
4

Na tej stronie są już trzy świetne odpowiedzi , ale, jak sugerował Luc Hermitte w komentarzu , jeśli robisz to od razu , ważne jest to, jak szybko i łatwo można dotrzeć do działającego rozwiązania.

W związku z tym jest to problem, z którego w ogóle nie skorzystałbym :substitute: można go łatwo rozwiązać za pomocą zwykłych poleceń trybu normalnego i rekurencyjnego makra:

  1. (W razie potrzeby) Najpierw wyłącz 'wrapscan'. Wyrażenie regularne, którego użyjemy, będzie pasować do pożądanego tekstu wynikowego, a także tekstu początkowego, więc przy 'wrapscan'włączonym makrze w przeciwnym razie odtwarzanie byłoby odtwarzane na zawsze. (Lub dopóki nie zdasz sobie sprawy z tego, co się dzieje i naciśniesz <C-C>).

    :set nowrapscan
    
  2. Ustaw wyszukiwane hasło (używając tego samego podstawowego wyrażenia regularnego, które zostało już wspomniane w istniejących odpowiedziach):

    /#\d\+\(\.\d\+\)\?<CR>
    
  3. (W razie potrzeby) Wróć do pierwszego meczu, naciskając Ntyle razy, ile potrzeba,

  4. (W razie potrzeby) Zmień pierwsze dopasowanie na żądany tekst:

    cE#1<Esc> 
    
  5. Wyczyść "qrejestr i zacznij rejestrować makro:

    qqqqq
    
  6. Wybierz bieżący licznik:

    yiW
    
  7. Przejdź do następnego meczu:

    n
    
  8. Zastąp bieżący licznik tym, który właśnie szarpnęliśmy:

    vEp
    
  9. Zwiększ licznik:

    <C-A>
    
  10. Zagraj w makro q. Rejestr "qjest nadal pusty, ponieważ wyczyściliśmy go w kroku 5, więc w tym momencie nic się nie dzieje:

    @q
    
  11. Zatrzymaj rejestrowanie makra:

    q
    
  12. Zagraj w nowe makro i patrz!

    @q
    

Podobnie jak w przypadku wszystkich makr, wygląda to na wiele kroków, gdy wyjaśniono to tak, jak to zrobiłem powyżej, ale zauważ, że ich wpisanie jest dla mnie bardzo szybkie: oprócz płytki rekursywnej do nagrywania makr są one zwykłe polecenia edycyjne Podczas edycji wykonuję cały czas. Jedynym krokiem, w którym musiałem zrobić cokolwiek, nawet zbliżając się do myślenia, był krok 2, w którym napisałem wyrażenie regularne, aby przeprowadzić wyszukiwanie.

Sformatowane jako dwa polecenia trybu wiersza poleceń i seria naciśnięć klawiszy, szybkość tego rodzaju rozwiązania staje się bardziej wyraźna: mogę wyczarować następujące elementy tak szybko, jak potrafię je wpisać 1 :

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

Prawdopodobnie mógłbym wymyślić inne rozwiązania na tej stronie z odrobiną przemyślenia i odniesieniem do dokumentacji 2 , ale kiedy zrozumiesz, jak działają makra, naprawdę łatwo jest je wyrzucać przy dowolnej prędkości, którą zwykle edytujesz.

1: Tam sytuacje, w których makra wymagają większej uwagi, ale uważam, że nie pojawią się znacznie w praktyce. Ogólnie rzecz biorąc, sytuacje, w których występują, to takie, w których makro jest jedynym praktycznym rozwiązaniem.

2: Nie sugerując, że inni odpowiadający nie mogliby wymyślić swoich rozwiązań w podobny sposób: po prostu wymagają umiejętności / wiedzy, których osobiście nie mam tak łatwo na wyciągnięcie ręki. Ale wszyscy użytkownicy Vima wiedzą, jak korzystać z regularnych poleceń edycji!

Bogaty
źródło
1

Wystarczy dodać „#” na końcu plus licznik „c”

:let c=0 | g/^#\d\+.*/ let c+=1 | s//\='#' . c

Część globalna g/^#\d\+.*/ let c+=1pozwala nam zwiększać licznik tylko w liniach, które mają wzór

SergioAraujo
źródło
Czy to nie jest odpowiedź hippietrailów?
D. Ben Knoble
W tym przypadku myślę, że wymyśliłem krótszy sposób robienia czegoś podobnego, może komentarz byłby lepszy.
SergioAraujo