Consejos para jugar golf en sed

19

¿Qué consejos generales tienes para jugar al golf en sed? Estoy buscando ideas que se puedan aplicar a problemas de código de golf y que también sean al menos algo específicas para sed (por ejemplo, "eliminar comentarios" no es una respuesta).

Por favor, publique un consejo por respuesta.

Toby Speight
fuente
44
No es realmente un consejo de golf (pero sigue siendo un consejo para jugar al golf): los avances de línea consumen tantos bytes como punto y coma, por lo que puede mantener su código corto y legible.
Dennis
Tampoco es un consejo, sino un problema: tengo GNU sed, pero el Fcomando nunca funcionó. ¿Alguien sabe por qué?
seshoumara
@seshoumara Ffunciona en mi GNU sed (prueba de Debian). Solo se imprime -si se lee desde stdin, por supuesto, pero eso es de esperar. ¿Qué se obtiene a partir de sed -e 'F;Q' /etc/hostname?
Toby Speight
@TobySpeight Eso le da este error: char 1: unknown command: F. Tengo que actualizar sed tal vez; ¿Qué versión tienes? El Lcomando tampoco funciona, pero de todos modos es inútil ya que -l nexiste. Todo lo demás mencionado en el sitio de GNU sed funciona.
seshoumara
1
Abrí la sala de chat bash, sed and dcpara todos los que quieran hablar y preguntar sobre estos idiomas. ¡Hagamos una comunidad!
seshoumara

Respuestas:

11

Si necesita usar etiquetas, entonces seguramente querrá que los nombres de sus etiquetas sean lo más cortos posible. De hecho, llevado al extremo, incluso puede usar la cadena vacía como nombre de etiqueta:

:    # define label ""
p    # print pattern space
b    # infinite loop! - branch to label ""
Trauma digital
fuente
44
A partir de gnu sed 4.3, este comportamiento fue eliminado . :ahora requiere una etiqueta.
Kevin
De hecho, aquí también está el enlace git commit real . Supongo que para PPCG esto no cambiará mucho, ya que podemos publicar respuestas para GNU sed 4.2.x, pero es bueno saber, aunque lamentablemente, que este truco ya no funcionará oficialmente.
seshoumara
8

La documentación de GNU sed describe el scomando como "navaja suiza de sed" . Pero si todo lo que quiere hacer es reemplazar todas las instancias de un personaje con otro, entonces el ycomando es lo que necesita:

y/a/b/

es un char más corto que:

s/a/b/g
Trauma digital
fuente
también es mucho más rápido y puede intercambiar caracteres en su lugar:y/12/21/
mikeserv
6

Considere usar una sintaxis de expresiones regulares extendida (en GNU sed). La -ropción cuesta un byte en puntuación, pero usarla solo una vez para eliminar las barras invertidas de un par \(...\)ya se ha pagado por sí misma.

Toby Speight
fuente
2
Con la nota adicional que -rparece ser sedespecífica de GNU .
manatwork
@manat: agregado (pero es una respuesta Wiki de la comunidad, por lo que podría haber editado usted mismo).
Toby Speight
Por supuesto. Simplemente no lo consideré parte del consejo, solo una nota adicional.
manatwork
Y sigue pagando por sí mismo cuando se utiliza +, ?, {}y |en partidos de expresiones regulares, ya que no hay barras invertidas son necesarios tampoco.
seshoumara
-Efunciona como un alias -ren muchas sedimplementaciones si no recuerdo mal.
phk
6

Al reemplazar repetidamente en un bucle:

loop:
s/foo/bar/g
tloop

Por lo general, no es necesario reemplazarlo globalmente, ya que el bucle eventualmente reemplazará todas las ocurrencias:

# GNU sed
:
s/foo/bar/
t

Tenga en cuenta también la extensión GNU anterior: una etiqueta puede tener un nombre vacío, ahorrando más bytes preciosos. En otras implementaciones, una etiqueta no puede estar vacía, y saltar sin una etiqueta transfiere el flujo al final del script (es decir, igual que n).

Toby Speight
fuente
1
El nombre de la etiqueta vacía es específico de GNU, POSIX requiere ramas sin argumento para saltar al final del script (parece ser el comportamiento en los BSD y Busybox, también en GNU sed si no agrega un vacío :)
ninjalj
2
La etiqueta sin nombre siempre fue un error en GNU sed, no una extensión, y en la versión 4.3 y superior, este error fue, lamentablemente, corregido. Ver aquí .
seshoumara
5

No hay aritmética integrada, pero los cálculos se pueden hacer en unario o en un decimal codificado. El siguiente código convierte el decimal a UCD, con x como unidad y 0 como separador de dígitos:

s/[1-9]/0&/g
s/[5-9]/4&/g
y/8/4/
s/9/4&/g
s/4/22/g
s/[37]/2x/g
s/[26]/xx/g
s/[1-9]/x/g

y aquí está la conversión de nuevo a decimal:

s/0x/-x/g
s/xx/2/g
y/x/1/
s/22/4/g
s/44/8/g
s/81/9/g
s/42/6/g
s/21/3/g
s/61/7/g
s/41/5/g
s/-//g

Ambos se toman de una respuesta a "Multiplicar dos números sin usar ningún número" .

El unario simple se puede convertir usando este par de bucles de esta respuesta a "{Números rizados};" , donde está la unidad ;. He usado vy xemparejar a Roman para 5y 10; bproviene de "bis".

# unary to decimal
:d
/;/{
s/;;;;;/v/g
s/vv/x/g
/[;v]/!s/x\+/&0/
s/;;/b/g
s/bb/4/
s/b;/3/
s/v;/6/
s/vb/7/
s/v3/8/
s/v4/9/
y/;bvx/125;/
td
}

# Decimal to unary
:u
s/\b9/;8/
s/\b8/;7/
s/\b7/;6/
s/\b6/;5/
s/\b5/;4/
s/\b4/;3/
s/\b3/;2/
s/\b2/;1/
s/\b1/;0/
s/\b0//
/[^;]/s/;/&&&&&&&&&&/g
tu
Toby Speight
fuente
1
... y si tiene que usar cualquiera de estos, es muy probable que ya haya perdido el código de golf, aunque aún puede ser competitivo con las respuestas de Java ;-) Sin embargo, aún es divertido de usar.
Trauma digital
La conversión de unario simple a decimal da respuestas incorrectas para el equivalente de entrada unaria de la forma decimal X0X, por ejemplo 108. La línea responsable de esto es /[;v]/!s/\b/0/2, que debe cambiarse /[;v]/!s:x\+:&0:para que funcione. Ver aquí .
seshoumara
@seshoumara, tu enlace parece ser una página vacía. Pero es completamente plausible que haya cometido un error al extraer ese código de la respuesta a la que se hace referencia, por lo que simplemente aplicaré su solución.
Toby Speight
El enlace se carga correctamente, pero esperaba algo más que una página gris con "TIO" y algo que se parece al logotipo de Ubuntu, ¿es eso lo que se pretende? Y me refería a la segunda de las respuestas a las que hice referencia ( 58007 ), ya que ahí es donde se originó la muestra simple.
Toby Speight
El enlace TIO debería haber contenido el código corregido, más una entrada de ejemplo, 108 en unario. Al ejecutar el código, debería haber visto el resultado correcto 108, y no 180, como se generó anteriormente con esa línea de código ahora fija. La actualización de la respuesta a la que se hace referencia depende totalmente de usted. Esta es una wiki comunitaria.
seshoumara
4

Como se menciona en man sed(GNU), puede usar cualquier carácter como delimitador para expresiones regulares utilizando la sintaxis

\%regexp%

donde %es un marcador de posición para cualquier personaje.

Esto es útil para comandos como

/^http:\/\//

que son más cortos como

\%^http://%

Lo que se menciona en la sed de GNU Manual , pero no en man sedes que se puede cambiar los delimitadores de s///y y///así.

Por ejemplo, el comando

ss/ssg

elimina todas las barras del espacio del patrón.

Dennis
fuente
4

Si la pregunta no lo prohíbe explícitamente, el consenso para esta meta pregunta es que la entrada numérica puede estar en unario. Esto le ahorra los 86 bytes de decimal a unario según esta respuesta .

Trauma digital
fuente
¿No es ese consenso de meta para sed que se refiere al formato antiguo simple simple? Tengo varias respuestas donde una entrada en UCD me ayudaría, en caso de que sea de cualquier manera.
seshoumara
@seshoumara quise decir unario, no UCD
Trauma digital
Luego, la conversión de decimal a simple unario antiguo le ahorra 126 bytes según la respuesta que vinculó. Los 86 bytes son para la conversión a UCD.
seshoumara
4

Ampliando esta respuesta de sugerencia , con respecto a las conversiones entre formatos de números unarios decimales y simples, presento los siguientes métodos alternativos, con sus ventajas y desventajas.

De decimal a simple unario: 102 + 1 (indicador r) = 103 bytes. Conté \tcomo una pestaña literal, como 1 byte.

h
:
s:\w::2g
y:9876543210:87654321\t :
/ /!s:$:@:
/\s/!t
x;s:-?.::;x
G;s:\s::g
/\w/{s:@:&&&&&&&&&&:g;t}

Pruébalo en línea!

Ventaja: es 22 bytes más corto y, como extra, funciona con enteros negativos como entrada

Desventaja: sobrescribe el espacio de espera. Sin embargo, dado que es más probable que necesite convertir el entero de entrada justo al comienzo del programa, esta limitación rara vez se siente.

Simple unario a decimal: 102 + 1 (bandera r) = 103 bytes

s:-?:&0:
/@/{:
s:\b9+:0&:
s:.9*@:/&:
h;s:.*/::
y:0123456789:1234567890:
x;s:/.*::
G;s:\n::
s:@::
/@/t}

Pruébalo en línea!

Ventaja: es 14 bytes más corto. Esta vez, ambas versiones de punta funcionan para enteros negativos como entrada.

Desventaja: sobrescribe el espacio de espera

Para un desafío complicado, tendrá que adaptar estos fragmentos para que funcionen con otra información que pueda existir en el espacio del patrón o el espacio de espera, además del número para convertir. El código se puede jugar más, si sabe que solo trabaja con números positivos o que el cero solo no será una entrada / salida válida.

Un ejemplo de dicha respuesta de desafío, donde creé y usé estos fragmentos, es el recíproco de un número (1 / x) .

seshoumara
fuente
Para unario-a-decimal puede guardar dos bytes mediante la combinación de las dos últimas sustituciones: s:\n|@$::g. tio.run/##K05N@f@/2ErX3krNwIpL30G/…
Jordania
Tuve mi propio intento en el convertidor decimal a unario. Aquí hay 97 bytes :) ¡ Pruébalo en línea! (tampoco requiere -r, pero con un nuevo consenso, las banderas no cuentan para el bytecount de todos modos, y no
arruina el
En realidad, si cambia la última línea de /\n/taa /\n/t, ahorra 1 byte para obtener 96
Kritixi Lithos
@Cowsquack ¡Gracias, 96 es genial! No tengo tiempo ahora, lo veré este fin de semana.
seshoumara
Claro, envíeme un ping en el chat entonces :)
Kritixi Lithos
3

Hablemos de los comandos ty T, que aunque se explican en la página del manual, es fácil olvidarse de ellos e introducir errores accidentalmente, especialmente cuando el código se complica.

Declaración de la página de manual para t:

Si a s///ha realizado una sustitución exitosa desde que se leyó la última línea de entrada y desde el último comando t o T, bifurque a la etiqueta.

Ejemplo que muestra lo que quiero decir: supongamos que tiene una lista de números y desea contar cuántos negativos hay. Código parcial a continuación:

1{x;s/.*/0/;x}                   # initialize the counter to 0 in hold space
s/-/&/                           # check if number is negative
t increment_counter              # if so, jump to 'increment_counter' code block
b                                # else, do nothing (start a next cycle)

:increment_counter
#function code here

Se ve bien, pero no lo es. Si el primer número es positivo, ese código seguirá pensando que fue negativo, ya que el salto realizado a través tde la primera línea de entrada se realiza independientemente, ¡ya que hubo una ssustitución exitosa cuando inicializamos el contador! Correcta es: /-/b increment_counter.

Si esto parecía fácil, aún podría ser engañado al hacer múltiples saltos de un lado a otro para simular funciones. En nuestro ejemplo, el increment_counterbloque de código seguramente usaría muchos scomandos. Regresar con b mainpodría causar que otra verificación en "main" caiga en la misma trampa. Es por eso que generalmente regreso de los bloques de código con s/.*/&/;t label. Es feo, pero útil.

seshoumara
fuente
2

En lugar de borrar el espacio del patrón con s/.*//, use el zcomando (en minúsculas) si va con GNU sed. Además del recuento de bytes más bajo, tiene la ventaja de que no comenzará el próximo ciclo como lo hace el comando d, lo que puede ser útil en ciertas situaciones.

seshoumara
fuente
1
También puede ser beneficioso si tiene secuencias de varios bytes no válidas (que no coinciden .).
Toby Speight
2

Sé que este es un hilo viejo, pero acabo de encontrar esos torpes convertidores decimales a UCD, con casi cien bytes, algunos incluso desordenan el espacio de espera o requieren sedversiones especiales defectuosas .

Para decimal a UCD utilizo (68 bytes; anteriormente mejor publicado aquí 87 bytes)

s/$/\n9876543210/
:a
s/\([1-9]\)\(.*\n.*\)\1\(.\)/\3x\2\1\3/
ta
P;d

UCD a decimal es (también 66 bytes; anteriormente mejor publicado aquí 96)

s/$/\n0123456789/
:a      
s/\([0-8]\)x\(.*\n.*\)\1\(.\)/\3\2\1\3/
ta      
P;d
  • \nEn el reemplazo no es portátil. En su lugar, puede usar un carácter diferente y guardar dos bytes, pero necesitará más bytes para eliminar el apéndice en lugar de P;d; Ver siguiente comentario. O, si su espacio de espera está vacío, no tenga G;s/$/9876543210/penalización de bytes.
  • Si necesita más procesamiento, necesitará algunos bytes más en s/\n.*//lugar de P;d.
  • Puede guardar dos bytes cada uno para esas sedversiones GNU antiguas con errores
  • No, no puede guardar esas seis barras diagonales inversas ya que las expresiones regulares extendidas no hacen referencias inversas
Philippos
fuente
No hay decimales para UCD y convertidores de retorno publicados en este hilo que ensucian el espacio de retención o requieren versiones sed defectuosas.
seshoumara
Su propia respuesta del 6 de abril utiliza el espacio dorado y solo se ejecutará con sedversiones antiguas que violen el estándar POSIX.
Philippos
¡No estoy haciendo conversiones decimales a UCD! Lea el hilo nuevamente con cuidado. UCD significa que 12 se convierte a 0x0xx (lo que calcula su respuesta), mientras que simple (lo que calcula mi respuesta) significa que 12 se convierte a xxxxxxxxxxxx. Elegí @ como símbolo, pero entiendes la idea. Y además, en PPCG uno no necesita adherirse al estándar POSIX.
seshoumara
Si le agrada, alguacil
Philippos
2

Lea toda la entrada de una vez con -z

A menudo necesita operar en toda la entrada a la vez en lugar de una línea a la vez. El Ncomando es útil para eso:

:
$!{N;b}

... pero generalmente puede omitirlo y usar la -zbandera en su lugar.

El -zindicador hace que sed use NUL ( \0) como su separador de línea de entrada en lugar de \n, por lo que si sabe que su entrada no contendrá \0, leerá toda la entrada a la vez como una sola "línea":

$ echo 'foo
> bar
> baz' | sed -z '1y/ao/eu/'
fuu
ber
bez

Pruébalo en línea!

Jordán
fuente
2

Agregar una nueva línea en un byte

El Gcomando agrega una nueva línea y el contenido del espacio de espera al espacio del patrón, por lo que si su espacio de espera está vacío, en lugar de esto:

s/$/\n/

Puedes hacerlo:

G

Anteponer una nueva línea en tres bytes

El Hcomando agrega una nueva línea y el contenido del espacio del patrón al espacio de espera, y xcambia los dos, por lo que si su espacio de espera está vacío, en lugar de esto:

s/^/\n/

Puedes hacerlo:

H;x

Esto contaminará tu espacio de espera, por lo que solo funciona una vez. Sin embargo, para dos bytes más, puede borrar el espacio de su patrón antes de cambiar, lo que sigue siendo un ahorro de dos bytes:

H;z;x
Jordán
fuente
1

En sed, lo más parecido a una función que puede tener es una etiqueta. Una función es útil porque puede ejecutar su código varias veces, ahorrando así muchos bytes. Sin embargo, en sed necesitaría especificar la etiqueta de devolución y, como tal, no puede simplemente llamar a esta "función" varias veces a lo largo de su código de la forma en que lo haría en otros idiomas.

La solución alternativa que uso es agregar en una de las dos memorias una bandera, que se usa para seleccionar la etiqueta de devolución. Esto funciona mejor cuando el código de función solo necesita un único espacio de memoria (el otro).

Ejemplo que muestra lo que quiero decir: tomado de un proyecto mío para escribir un pequeño juego en sed

# after applying the player's move, I overwrite the pattern space with the flag "P"
s/.*/P/
b check_game_status
:continue_turn_from_player
#code

b calculate_bot_move
:return_bot_move
# here I call the same function 'check_game_status', but with a different flag: "B"
s/.*/B/
b check_game_status
:continue_turn_from_bot
#code (like say 'b update_screen')

:check_game_status   # this needs just the hold space to run
#code
/^P$/b continue_turn_from_player
/^B$/b continue_turn_from_bot

Por supuesto, las etiquetas deben tener una sola letra, utilicé los nombres completos para una mejor explicación.

seshoumara
fuente
1

Las expresiones regulares vacías son equivalentes a las expresiones regulares encontradas anteriormente

(gracias a Riley por descubrir esto de una presentación de anagol )

Aquí hay un ejemplo donde tenemos la tarea de crear 100 @s en un búfer vacío.

s/$/@@@@@@@@@@/;s/.*/&&&&&&&&&&/ # 31 bytes
s/.*/@@@@@@@@@@/;s//&&&&&&&&&&/  # 30 bytes

La segunda solución es 1 byte más corta y utiliza el hecho de que las expresiones regulares vacías se completan con la última expresión regular encontrada. Aquí, para la segunda sustitución, la última expresión regular fue .*, por lo que la expresión regular vacía aquí se llenará .*. Esto también funciona con expresiones regulares en /conditionals/.

Tenga en cuenta que es la expresión regular encontrada anteriormente , por lo que lo siguiente también funcionaría.

s/.*/@@@@@@@@@@/;/@*/!s/$/@/;s//&&&&&&&&&&/

La expresión regular vacía se llena en @*lugar de $porque s/$/@/nunca se alcanza.

Kritixi Lithos
fuente
Si, buena respuesta. Incluso he alargado las expresiones regulares para que puedan volver a coincidir de esta manera (haciendo que el programa sea más corto).
Toby Speight
0

Paso principalmente inútil:

y|A-y|B-z|

Esto solo se traducirá Ahacia By yhacia z(... y -hacia -;), pero nada más, así que

sed -e 'y|A-y|B-z|' <<<'Hello world!'

solo regresaremos:

Hello world!

Se podría asegurar que esto será inútil, por ejemplo mediante el uso de esta en los valores hexadecimales en minúscula (que contiene sólo 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, eo f.)

F. Hauri
fuente
2
¿Es esto algo que descubriste de la manera difícil? ;-)
Toby Speight
Me gustan los guiones inútiles: sed '; ;/s/b;y|A-y|B-z|;s ;s/ //; ; ;' <<<'Hello world'(¿Por qué esto no suprime el espacio?)
F. Hauri