¿Cómo reemplazar cada partido con un contador incremental?

14

Quiero buscar y reemplazar cada aparición de un cierto patrón con un número decimal que comienza en 1y se incrementa en uno para cada coincidencia.

Puedo encontrar preguntas redactadas de manera similar que no se trata de incrementar un contador sino de modificar cada partida en una cantidad fija. Otras preguntas similares son sobre insertar números de línea en lugar de un contador incremental.

Ejemplo, antes:

#1
#1.25
#1.5
#2

Después:

#1
#2
#3
#4

Mis datos reales tienen mucho más texto alrededor de las cosas que quiero volver a numerar.

hippietrail
fuente
2
si es así perldo, puede usar:%perldo s/#\K\d+(\.\d+)?/++$i/ge
Sundeep
@Sundeep: debería haber mencionado que estoy en Windows 10 vainilla, ¡lo siento!
hippietrail

Respuestas:

15

Necesitas sustitución con un estado. Recuerdo haber proporcionado una solución completa (¿/ varias?) Para este tipo de problemas en SO.

Aquí hay otra forma de proceder (1). Ahora, procederé en 2 pasos:

  • una variable de lista ficticia que necesito para el truco sucio y complicado que emplearé
  • una sustitución donde inserto el len de esta matriz ficticia que estoy rellenando en cada aparición coincidente.

Lo que da:

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

Si no está acostumbrado a vim regexes, utilizo :h /\zsy \zepara especificar qué subpatrón estoy haciendo coincidir, entonces hago coincidir una serie de dígitos posiblemente seguidos por un punto y otros dígitos. Esto no es perfecto para ningún número de coma flotante, pero aquí es suficiente.

Nota: Tendrás que envolverlo en un par de funciones + comando para una interfaz simple. Nuevamente, hay ejemplos en SO / vim ( aquí , aquí , aquí ) Hoy en día sé lo suficiente de vim como para no importarme incluir este truco en un comando. De hecho, podré escribir este comando en el primer intento, mientras que me tomaré varios minutos para recordar el nombre del comando.


(1) El objetivo es poder mantener un estado entre sustituciones y reemplazar la ocurrencia actual con algo que depende del estado actual.

Gracias a que :s\=podemos insertar algo resultante de un cálculo.

Sigue siendo el problema del estado. O definimos una función que gestiona un estado externo, o nos actualizamos a nosotros mismos un estado externo. En C (y lenguajes relacionados), podríamos haber usado algo como length++o length+=1. Desafortunadamente, en las secuencias de comandos vim, +=no se puede usar de fábrica. Debe usarse con :seto con :let. Esto significa que :let length+=1incrementa un número, pero no devuelve nada. No podemos escribir :s/pattern/\=(length+=1). Necesitamos algo mas.

Necesitamos funciones mutantes. es decir, funciones que mutan sus entradas. Tenemos setreg(), map(), add()y probablemente más. Comencemos con ellos.

  • setreg()Muta un registro. Perfecto. Podemos escribir setreg('a',@a+1)como en la solución de @Doktor OSwaldo. Y sin embargo, esto no es suficiente. setreg()es más un procedimiento que una función (para quienes conocemos a Pascal, Ada ...). Esto significa que no devuelve nada. En realidad, devuelve algo. La salida nominal (es decir , salidas no excepcionales ) siempre devuelve algo. Por defecto, cuando olvidamos devolver algo, se devuelve 0 ; también se aplica con las funciones integradas. Es por eso que en su solución la expresión de reemplazo es en realidad \=@a+setreg(...). Tricky, ¿no es así?

  • map()También podría ser utilizado. Si comenzamos desde una lista con un solo 0 ( :let single_length=[0]), podríamos incrementarlo gracias a map(single_length, 'v:val + 1'). Entonces necesitamos devolver la nueva longitud. A diferencia setreg(), map()devuelve su entrada mutada. Eso es perfecto, la longitud se almacena en la primera posición (y única, y por lo tanto también la última) de la lista. La expresión de reemplazo podría ser \=map(...)[0].

  • add()es el que uso habitualmente por costumbre (ya lo he pensado en map()realidad, y todavía no he hecho sus respectivas presentaciones). La idea add()es utilizar una lista como el estado actual y agregar algo al final antes de cada sustitución. A menudo almaceno el nuevo valor al final de la lista y lo uso para el reemplazo. Como add()también devuelve su lista de entrada mutado, podemos utilizar: \=add(state, Func(state[-1], submatch(0)))[-1]. En el caso de OP, solo necesitamos recordar cuántas coincidencias se han detectado hasta ahora. Devolver la longitud de esta lista de estados es suficiente. De ahí mi \=len(add(state, whatever)).

Luc Hermitte
fuente
Creo que entiendo esto, pero ¿por qué el truco con la matriz y su longitud en comparación con agregar uno a una variable?
hippietrail
1
Esto se debe a que \=espera una expresión y porque, a diferencia de C, i+=1no es algo que incremente y devuelva una expresión. Esto significa que detrás \=necesito algo que pueda modificar un contador y que devuelva una expresión (igual a ese contador). Hasta ahora, lo único que he encontrado son las funciones de manipulación de listas (y diccionarios). @Doktor OSwaldo ha utilizado otra función de mutación ( setreg()). la diferencia es que setreg()nunca devuelve nada, lo que significa que siempre devuelve el número 0.
Luc Hermitte
¡Wow interesante! Tanto tu truco como el de él son tan mágicos que creo que tus respuestas se beneficiarían al explicarlas en tus respuestas. Supongo que solo los vimscripters más fluidos conocerían expresiones tan poco intuitivas.
hippietrail
2
@hippietrail. Explicaciones agregadas. Avíseme si necesita precisiones más específicas.
Luc Hermitte
13
 :let @a=1 | %s/search/\='replace'.(@a+setreg('a',@a+1))/g

Pero cuidado, sobrescribirá su registro a. Creo que es un poco más directo que la respuesta de luc, pero tal vez la suya sea más rápida. Si esta solución es de alguna manera peor que la suya, me encantaría escuchar cualquier comentario sobre por qué su respuesta es mejor. ¡Cualquier comentario para mejorar la respuesta será muy apreciado!

(También se basa en una respuesta SO tan mía /programming/43539251/how-to-replace-finding-words-with-the-different-in-each-occurrence-in-vi-vim -edi / 43539546 # 43539546 )

Doktor OSwaldo
fuente
No veo cómo @a+setreg('a',@a+1)es más corto que len(add(t,1)). De lo contrario, este es otro buen truco sucio :). No he pensado en este. Con respecto al uso de una función de mutación del diccionario en el texto de reemplazo, de :sy substitute(), he notado que esto es mucho más rápido que los bucles explícitos, de ahí la implementación de las funciones de mi lista en lh-vim-lib . Supongo que su solución estará a la par con la mía, puede ser un poco más rápido, no lo sé.
Luc Hermitte
2
En cuanto a las preferencias, prefiero mi solución por una sola razón: no se @amodifica. En los guiones, esto es importante para la OMI. Mientras esté en modo interactivo, como usuario final, sabré qué registro puedo usar. Jugar con un registro es menos importante. En mi solución, en modo interactivo, se enreda una variable global; en un script sería una variable local.
Luc Hermitte
@LucHermitte Lo siento, mi solución no es más corta que la suya, debería leerla mejor antes de escribir tal declaración. ¡He eliminado dicha declaración de mi respuesta y me gustaría disculparme! Gracias por sus interesantes comentarios, lo agradezco.
Doktor OSwaldo
No te preocupes por eso. Debido a la expresión regular, es fácil pensar que hay mucho que escribir. Además, voluntariamente admito que mi solución es complicada. Eres bienvenido por los comentarios. :)
Luc Hermitte
1
De hecho eres justo. La mayoría de las veces extraigo otra información que almaceno en la última posición de la matriz, que es lo que (el último elemento) inserto al final. Por ejemplo, para a +3, podría escribir algo como \=add(thelist, 3 + get(thelist, -1, 0))[-1].
Luc Hermitte
5

Encontré una pregunta similar pero diferente que hice hace un par de años y logré cambiar una de sus respuestas sin comprender completamente lo que estaba haciendo y ha funcionado muy bien:

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

Específicamente, no entiendo por qué el mío no usa %o por qué solo uso una variable simple que las otras respuestas evitan por alguna razón.

hippietrail
fuente
1
eso también es una posibilidad. Creo que la principal desventaja aquí es que usas un comando sustituto por partido. Entonces probablemente sea más lento. La razón por la que no usamos una variable simple es que no se actualizará en una s//gdeclaración normal . De todos modos es una solución interesante. Quizás @LucHermitte pueda darle más información sobre los pros y los contras, ya que mi conocimiento sobre vimscript es bastante limitado en comparación con el suyo.
Doktor OSwaldo
1
@DoktorOSwaldo. Supongo que esta solución ha estado funcionando durante más tiempo, sin printf()embargo, ya que las Listas se introdujeron en Vim 7. Pero debo admitir que no habría esperado (¿o no lo recordaba?) <bar>Pertenecer al alcance de :global- IOW, el escenario que hubiera esperado era aplicar el :suben las líneas coincidentes, luego incrementar iuna vez al final. Espero que esta solución sea un poco más lenta. pero, realmente importa? Lo importante es la facilidad con la que podemos encontrar una solución de memoria + prueba y error. Por ejemplo, los Vimgolfers prefieren macros.
Luc Hermitte
1
@LucHermitte, sí, eclipsé lo mismo, y no, la velocidad no importa. Creo que es una buena respuesta, y nuevamente aprendí algo de ella. Quizás el g/s//comportamiento del alcance permite otros trucos sucios. Así que gracias a ambos por las interesantes respuestas y la discusión, a menudo no aprendo tanto al dar una respuesta =).
Doktor OSwaldo
4

Ya hay tres excelentes respuestas en esta página, pero, como sugirió Luc Hermitte en un comentario , si lo hace de manera espontánea, lo importante es qué tan rápido y fácil puede encontrar una solución que funcione.

Como tal, este es un problema que en realidad no usaría :substitutepara nada: es uno que se puede resolver fácilmente usando comandos normales de modo normal y una macro recursiva:

  1. (Si es necesario) Primero, apague 'wrapscan'. La expresión regular que vamos a utilizar coincidirá con el texto del resultado deseado, así como con el texto inicial, por lo que con 'wrapscan'on, la macro continuaría reproduciéndose para siempre. (O hasta que se dé cuenta de lo que está sucediendo y presione <C-C>):

    :set nowrapscan
    
  2. Configure su término de búsqueda (usando la misma expresión regular base ya mencionada en las respuestas existentes):

    /#\d\+\(\.\d\+\)\?<CR>
    
  3. (Si es necesario) Vuelva al primer partido presionando Ntantas veces como sea necesario,

  4. (Si es necesario) Cambie la primera coincidencia en el texto deseado:

    cE#1<Esc> 
    
  5. Borre el "qregistro y comience a grabar una macro:

    qqqqq
    
  6. Yank el contador actual:

    yiW
    
  7. Salta al siguiente partido:

    n
    
  8. Reemplace el contador actual con el que acabamos de extraer:

    vEp
    
  9. Incrementar el contador:

    <C-A>
    
  10. Jugar macro q. El registro "qtodavía está vacío porque lo borramos en el paso 5, por lo que no sucede nada en este punto:

    @q
    
  11. Deja de grabar la macro:

    q
    
  12. ¡Juega la nueva macro y mira!

    @q
    

Al igual que con todas las macros, esto se parece a muchos pasos cuando se explica como lo he hecho anteriormente, pero tenga en cuenta que escribir esto realmente es muy rápido para mí: aparte de la placa repetitiva de grabación de macro, todos son los normales. comandos de edición que realizo todo el tiempo durante la edición. El único paso en el que tenía que hacer algo, incluso acercarme al pensamiento, era el paso 2, donde escribía la expresión regular para realizar la búsqueda.

Formateada como dos comandos de modo de línea de comandos y una serie de pulsaciones de teclas, la velocidad de este tipo de solución se vuelve más clara: puedo conjurar lo siguiente casi tan rápido como puedo escribirlo 1 :

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

Probablemente podría haber encontrado las otras soluciones en esta página con un poco de reflexión y algunas referencias a la documentación 2 , pero, una vez que comprenda cómo funcionan las macros, son realmente fáciles de producir a la velocidad que normalmente edita.

1: No son situaciones en las que las macros requieren mayor reflexión, pero me parece que no suben mucho en la práctica. Y, en general, las situaciones en las que ocurren son aquellas en las que una macro es la única solución práctica.

2: No implica que los otros respondedores no podrían haber presentado sus soluciones de manera similar fácilmente: solo requieren habilidades / conocimientos que personalmente no tengo tan fácilmente a mi alcance. ¡Pero todos los usuarios de Vim saben cómo usar comandos de edición regulares!

Rico
fuente