Как заменить каждое совпадение счетчиком увеличения?

14

Я хочу найти и заменить каждое вхождение определенного шаблона десятичным числом, которое начинается с 1и увеличивается на единицу для каждого совпадения.

Я могу найти аналогичные формулировки вопросов, которые, как оказалось, не об увеличении счетчика, а об изменении каждого совпадения на фиксированную величину. Другие подобные вопросы касаются вставки номеров строк, а не увеличения счетчика.

Пример, перед тем:

#1
#1.25
#1.5
#2

После:

#1
#2
#3
#4

В моих реальных данных гораздо больше текста, который я хочу переименовать.

hippietrail
источник
2
если у вас есть perldo, вы можете использовать:%perldo s/#\K\d+(\.\d+)?/++$i/ge
Sundeep
@Sundeep: я должен был упомянуть, что я нахожусь на ванильной Windows 10, извините!
hippietrail

Ответы:

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()это больше процедура, чем функция (для тех из нас, кто знает Паскаль, Ада ...). Это значит, что ничего не возвращается. На самом деле, это что-то возвращает. Номинальный выход (то есть не исключительные выходы) всегда что-то возвращает. По умолчанию, когда мы забыли что-то вернуть, возвращается 0 - это также относится и к встроенным функциям. Вот почему в его решении выражение замены на самом деле \=@a+setreg(...). Хитрый, не правда ли?

  • 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.
Люк Эрмит
Вау, интересно! Ваш трюк и его трюк настолько волшебны, что я думаю, что ваши ответы выиграют от объяснения их в ваших ответах. Я предположил бы, что только самые беглые vimscripters знали бы такие неинтуитивные идиомы.
hippietrail
2
@hippietrail. Добавлены пояснения. Дайте мне знать, если вам нужна более конкретная точность.
Люк Эрмит
13
 :let @a=1 | %s/search/\='replace'.(@a+setreg('a',@a+1))/g

Но будьте осторожны, он перезапишет ваш регистр a. Я думаю, что это немного более прямо, чем ответ Люка, но, возможно, он быстрее. Если это решение чем-то хуже его, я хотел бы услышать любые отзывы, почему его ответ лучше. Любые отзывы для улучшения ответа будут высоко оценены!

(Это также основано на моем 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)). В противном случае, это хороший другой подвох :). Я не думал об этом. Что касается использования функции замены словаря в замещающем тексте, :sи substitute(), как я заметил, это намного быстрее, чем явные циклы - отсюда и реализация моих функций списка в lh-vim-lib . Я думаю, что ваше решение будет на одном уровне с моим, может быть, немного быстрее, я не знаю.
Люк Эрмит
2
Что касается предпочтений, я предпочитаю свое решение по одной причине: оно остается @aнеизменным. В скриптах это важно IMO. Будучи конечным пользователем в интерактивном режиме, я буду знать, какой регистр я могу использовать. Возиться с реестром менее важно. В моем решении в интерактивном режиме глобальная переменная смешана с; в сценарии это будет локальная переменная.
Люк Эрмит
@LucHermitte Извините, мое решение действительно не короче вашего, я должен прочитать его лучше, прежде чем писать такое заявление. Я удалил указанное утверждение из своего ответа и хотел бы извиниться! Спасибо за ваш интересный отзыв, я ценю это.
Доктор Освальдо
Не беспокойся об этом. Из-за регулярных выражений легко подумать, что тут есть что набирать. Также я добровольно признаю, что мое решение запутанно. Добро пожаловать за отзыв. :)
Люк Эрмит
1
Действительно ты прав. Большую часть времени я извлекаю другую информацию, которую храню в последней позиции массива, и это то, что (последний элемент) я вставляю в конце. Например, для +3, я мог бы написать что-то вроде \=add(thelist, 3 + get(thelist, -1, 0))[-1].
Люк Эрмит
5

Я нашел похожий, но другой вопрос, который я задал пару лет назад, и сумел изменить один из его ответов, не полностью понимая, что я делаю, и он отлично работает:

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

В частности, я не понимаю, почему моя не использует %или почему я просто использую простую переменную, которую другие ответы почему-то избегают.

hippietrail
источник
1
это также возможность. Я думаю, что основным недостатком здесь является то, что вы используете одну команду замены на матч. Так что, наверное, медленнее. Причина, по которой мы не используем простую переменную, заключается в том, что она не будет обновлена ​​в обычном s//gвыражении. В любом случае это интересное решение. Возможно, @LucHermitte может рассказать вам больше о плюсах и минусах, поскольку мои знания о vimscript весьма ограничены по сравнению с его.
Доктор Освальдо
1
@DoktorOSwaldo. Я предполагаю , что это решение работает в течение длительного времени - не printf()хотя - как списки были введены в Vim 7. Но я должен признать , что я не ожидал бы (? / Не помню) <bar>принадлежать к сфере :global- IOW, сценарий я бы ожидать было применить :subна согласующих линий, то приращение iодин раз в самом конце. Я ожидаю, что это решение будет немного медленнее. Но так ли это на самом деле важно? Важно то, как легко мы можем прийти с рабочим решением из памяти + проб и ошибок. Например, вимгольферы предпочитают макросы.
Люк Hermitte
1
@LucHermitte, да, я тоже самое, и скорость не имеет значения. Я думаю, что это хороший ответ, и я снова кое-что узнал из этого. Может быть, g/s//поведение в области видимости допускает другие грязные уловки. Так что спасибо вам обоим за интересные ответы и обсуждение, я не так часто учусь на них, давая ответы =).
Доктор Освальдо
4

На этой странице уже есть три отличных ответа , но, как предложил Люк Эрмитт в комментарии , если вы делаете это не по назначению, важно то, как быстро и легко вы сможете найти работающее решение.

Таким образом, это проблема, которую я бы вообще не использовал :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 знают, как использовать обычные команды редактирования!

Богатый
источник