Dicas para jogar golfe em sed

19

Que dicas gerais você tem para jogar golfe no sed? Estou procurando idéias que possam ser aplicadas a problemas de código-golfe e que também sejam pelo menos um pouco específicas para sed (por exemplo, "remover comentários" não é uma resposta).

Poste uma dica por resposta.

Toby Speight
fonte
4
Não é realmente uma dica de golfe (mas ainda é uma dica de golfe): os feeds de linha consomem tantos bytes quanto ponto-e-vírgula, para que você possa manter seu código curto e legível.
Dennis
Também não é uma dica, mas um problema: eu tenho o GNU sed, mas o Fcomando nunca funcionou. Alguem sabe por quê?
precisa saber é o seguinte
@seshoumara Ftrabalha no meu GNU sed (teste Debian). Apenas imprime -se estiver lendo a partir de stdin, é claro, mas isso é esperado. Do que você ganha sed -e 'F;Q' /etc/hostname?
Toby Speight
@TobySpeight Isso dá este erro: char 1: unknown command: F. Eu tenho que atualizar o sed talvez; Que versão você tem? O Lcomando também não funciona, mas é inútil de qualquer maneira, já que -l nexiste. Tudo o mais mencionado no site do GNU sed funciona.
precisa saber é o seguinte
11
Abri a sala de bate-papo bash, sed and dcpara todos que querem conversar e perguntar sobre esses idiomas. Vamos fazer uma comunidade!
precisa saber é o seguinte

Respostas:

11

Se você precisar usar rótulos , com certeza desejará que seus nomes sejam os mais curtos possível. De fato, levado ao extremo, você pode até usar a string vazia como um nome de rótulo:

:    # define label ""
p    # print pattern space
b    # infinite loop! - branch to label ""
Trauma Digital
fonte
4
A partir do gnu sed 4.3, esse comportamento foi removido . :agora requer um rótulo.
21417 Kevin
De fato, aqui também está o link de confirmação do git real . Eu acho que para o PPCG isso não vai mudar muito, pois temos permissão para postar respostas para o GNU sed 4.2.x, mas é bom saber, embora lamentavelmente, que esse truque não funcione mais oficialmente.
seshoumara
8

A documentação do GNU sed descreve o scomando como "canivete suíço do sed" . Mas se tudo o que você deseja fazer é substituir todas as instâncias de um caractere por outro, o ycomando é o que você precisa:

y/a/b/

é um caractere menor que:

s/a/b/g
Trauma Digital
fonte
também é muito mais rápido e pode trocar caracteres no lugar:y/12/21/
mikeserv 23/12/2015
6

Considere usar a sintaxe regex estendida (no GNU sed). A -ropção custa um byte na pontuação, mas usá-lo apenas uma vez para eliminar as barras invertidas de um par de \(...\)já se pagou.

Toby Speight
fonte
2
Com a nota adicional que -rparece ser sedespecífica do GNU .
manatwork
@manat - adicionado (mas é uma resposta do Community Wiki, para que você possa ter se editado).
Toby Speight
Claro. Apenas não considerei parte da dica, apenas uma observação adicional.
manatwork
E ele continua pagando por si quando se utiliza +, ?, {}e |em partidas regex, uma vez que não barras invertidas são necessários também.
precisa saber é o seguinte
-Efunciona como um alias para -rem muitas sedimplementações, se bem me lembro.
phk
6

Ao substituir repetidamente em um loop:

loop:
s/foo/bar/g
tloop

geralmente não é necessário substituir globalmente, pois o loop eventualmente substituirá todas as ocorrências:

# GNU sed
:
s/foo/bar/
t

Observe também a extensão GNU acima: um rótulo pode ter um nome vazio, economizando mais bytes preciosos. Em outras implementações, um rótulo não pode estar vazio, e saltar sem um rótulo transfere o fluxo para o final do script (ou seja, o mesmo que n).

Toby Speight
fonte
11
O nome do rótulo vazio é específico do GNU, o POSIX requer ramificações sem argumento para ir para o final do script (parece ser o comportamento nos BSDs e Busybox, também no GNU sed, se você não adicionar um vazio :)
ninjalj
2
O rótulo sem nome sempre foi um bug no GNU sed, não uma extensão e, na versão 4.3 e superior, esse bug foi, lamentavelmente, corrigido. Veja aqui .
seshoumara
5

Não há aritmética embutida, mas os cálculos podem ser feitos em decimal unário ou em código unário. O código a seguir converte decimal em UCD, com x como a unidade e 0 como o 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

e aqui está a conversão de volta para 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 são retirados de uma resposta para "Multiplique dois números sem usar nenhum número" .

Unário antigo simples pode ser convertido usando este par de loops desta resposta para "{Números Curly};" , onde está a unidade ;. Eu usei ve xpara combinar Roman para 5e 10; bvem 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
fonte
11
... e se você precisar usar um desses, certamente já perdeu o código golf, embora ainda possa ser competitivo com as respostas Java ;-) Ainda assim, é divertido de usar.
Digital Trauma
A conversão de simples unário para decimal fornece respostas incorretas para o equivalente de entrada unário da forma decimal X0X, por exemplo 108. A linha responsável por isso é /[;v]/!s/\b/0/2, que precisa ser alterada /[;v]/!s:x\+:&0:para que funcione. Veja aqui .
seshoumara
@seshoumara, seu link parece ser uma página vazia. Mas é totalmente plausível que eu tenha cometido um erro ao extrair esse código da resposta referenciada; portanto, aplicarei sua correção.
perfil completo de Toby Speight
O link é carregado corretamente, mas eu estava esperando algo diferente de uma página cinza com "TIO" e algo que se parece com o logotipo do Ubuntu - é isso que se destina? E eu estava me referindo à segunda das respostas que referenciei ( 58007 ), pois foi aí que a amostra simples e unária se originou.
quer
O link TIO deve ter contido o código corrigido, além de uma entrada de exemplo, 108 em unário. Ao executar o código, você deve ter visto o resultado correto 108, e não 180, conforme anteriormente gerado por essa linha de código agora fixa. A atualização da resposta referenciada depende inteiramente de você. Este é um wiki da comunidade.
seshoumara
4

Conforme mencionado em man sed(GNU), você pode usar qualquer caractere como delimitador para expressões regulares usando a sintaxe

\%regexp%

onde %é um espaço reservado para qualquer caractere.

Isso é útil para comandos como

/^http:\/\//

que são mais curtos quanto

\%^http://%

O que é mencionado no manual do GNU sed, mas não no, man sedé que você pode alterar os delimitadores s///e y///também.

Por exemplo, o comando

ss/ssg

remove todas as barras do espaço do padrão.

Dennis
fonte
4

Se não for explicitamente banido pela pergunta, o consenso para essa meta questão é que a entrada numérica pode ser unária. Isso economiza os 86 bytes de decimal para unário, de acordo com esta resposta .

Trauma Digital
fonte
Esse meta consenso para sed não está se referindo a um formato unário antigo e simples? Tenho várias respostas nas quais uma entrada no UCD me ajudaria, caso seja de qualquer maneira.
seshoumara
@seshoumara eu quis dizer unário, não UCD
Trauma Digital
Em seguida, a conversão de decimal para unário antigo comum economiza 126 bytes, conforme a resposta que você vinculou. Os 86 bytes são para a conversão em UCD.
seshoumara
4

Expandindo a resposta desta dica , em relação às conversões entre os formatos de número decimal e simples, apresentamos os seguintes métodos alternativos, com suas vantagens e desvantagens.

Decimal para unário simples: 102 + 1 (sinalizador r) = 103 bytes. Eu contei \tcomo uma guia 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}

Experimente online!

Vantagem: é 22 bytes mais curto e, como extra, funciona com números inteiros negativos como entrada

Desvantagem: substitui o espaço de espera. No entanto, como é mais provável que você precise converter o número inteiro de entrada logo no início do programa, essa limitação raramente é sentida.

Simples unário ao decimal: 102 + 1 (sinalizador r) = 103 bytes

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

Experimente online!

Vantagem: é 14 bytes mais curto. Desta vez, ambas as versões de ponta funcionam para números inteiros negativos como entrada.

Desvantagem: substitui o espaço de espera

Para um desafio complicado, você precisará adaptar esses trechos para trabalhar com outras informações que possam existir no espaço padrão ou manter espaço, além do número a ser convertido. O código pode ser mais golfado, se você souber que trabalha apenas com números positivos ou que apenas o zero não será uma entrada / saída válida.

Um exemplo dessa resposta de desafio, onde eu criei e usei esses trechos, é o Recíproco de um número (1 / x) .

seshoumara
fonte
Para unário para decimal, você pode salvar dois bytes combinando as duas últimas substituições: s:\n|@$::g . tio.run/##K05N@f@/2ErX3krNwIpL30G/…
Jordânia
Eu tive minha própria tentativa no conversor de decimal para unário. Aqui estão 97 bytes :) Experimente online! (também não requer -r, mas com novo consenso, os sinalizadores não contam para o número de bytes de qualquer maneira e não atrapalham o espaço de espera)
Kritixi Lithos
Na verdade, se você alterar a última linha de /\n/ta de /\n/t, você economiza 1 byte para obter 96
Kritixi Lithos
@Cowsquack Obrigado, 96 é ótimo! Não tem tempo agora, vai dar uma olhada neste fim de semana.
Sshoumara # 22/18
Claro, me enviar um ping no bate-papo então :)
Kritixi Lithos
3

Vamos falar sobre os comandos te T, que, embora sejam explicados na página de manual, é fácil esquecê-lo e introduzir bugs acidentalmente, especialmente quando o código fica complicado.

Declaração da página de manual para t:

Se a s///fez uma substituição bem-sucedida desde a última leitura da linha de entrada e desde o último comando t ou T, ramifique para o rótulo.

Exemplo mostrando o que quero dizer: digamos que você tenha uma lista de números e que deseja contar quantos negativos existem. Código parcial abaixo:

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

Parece ok, mas não está. Se o primeiro número for positivo, esse código ainda achará negativo, porque o salto realizado pela tprimeira linha de entrada é realizado independentemente, pois houve uma ssubstituição bem-sucedida quando inicializamos o contador! Correta é: /-/b increment_counter.

Se isso parecesse fácil, você ainda poderia ser enganado ao fazer vários saltos para frente e para trás para simular funções. No nosso exemplo, oincrement_counter bloco de código com certeza usaria muitos scomandos. Retornar com b mainpode fazer com que outro check-in "main" caia na mesma armadilha. É por isso que geralmente retorno de blocos de código com s/.*/&/;t label. É feio, mas útil.

seshoumara
fonte
2

Em vez de limpar o espaço do padrão s/.*//, use o zcomando (minúsculo) se você for com o GNU sed. Além da contagem de bytes mais baixos, tem a vantagem de não iniciar o próximo ciclo como o comando d, o que pode ser útil em determinadas situações.

seshoumara
fonte
11
Também pode ser benéfico se você tiver seqüências de vários bytes inválidas (que não são correspondidas por .).
precisa saber é o seguinte
2

Sei que esse é um encadeamento antigo, mas acabei de encontrar os conversores desajeitados de decimal para UCD, com quase cem bytes, alguns até atrapalhando o espaço de espera ou exigindo sedversões especiais com defeito .

Para decimal em UCD eu uso (68 bytes; o melhor foi postado aqui 87 bytes)

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

UCD para decimal é (também 66 bytes; o melhor foi postado aqui 96)

s/$/\n0123456789/
:a      
s/\([0-8]\)x\(.*\n.*\)\1\(.\)/\3\2\1\3/
ta      
P;d
  • \nna substituição não é portátil. Você pode usar um caractere diferente e salvar dois bytes, mas precisará de mais bytes para remover o apêndice em vez deP;d ; veja a próxima observação. Ou, se seu espaço de espera estiver vazio, faça G;s/$/9876543210/sem penalidade de bytes.
  • Se você precisar de processamento adicional, precisará de mais alguns bytes para s/\n.*// vez de P;d.
  • Você pode salvar dois bytes cada para os antigos GNU com erros sed versões
  • Não, você não pode salvar essas seis barras invertidas, pois expressões regulares estendidas não fazem referências a trás
Philippos
fonte
Não há conversores decimais para UCD e reverso publicados neste encadeamento que bagunçam o espaço de espera ou exigem versões sed com defeito.
seshoumara
Sua própria resposta de 6 de abril usa o espaço dourado e será executada apenas com sedversões antigas que violam o padrão POSIX.
Philippos
Não estou fazendo conversões decimais em UCD! Leia o tópico novamente com atenção. UCD significa que 12 é convertido em 0x0xx (o que sua resposta calcula), enquanto que unário (o que minha resposta calcula) significa que 12 é convertido em xxxxxxxxxxxx. Eu escolhi @ como símbolo, mas você entendeu. Além disso, no PPCG não é necessário aderir ao padrão POSIX.
seshoumara
Se lhe agrada, xerife
Philippos
2

Leia toda a entrada de uma só vez com -z

Geralmente, você precisa operar toda a entrada de uma só vez, em vez de uma linha de cada vez. O Ncomando é útil para isso:

:
$!{N;b}

... mas geralmente você pode pular e usar o -z bandeira.

O -zsinalizador faz com que o sed use NUL ( \0) como seu separador de linhas de entrada em vez de \n, portanto, se você souber que sua entrada não conterá \0, ela lerá todas as entradas de uma só vez como uma única “linha”:

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

Experimente online!

Jordânia
fonte
2

Anexar uma nova linha em um byte

O Gcomando anexa uma nova linha e o conteúdo do espaço de espera ao espaço do padrão; portanto, se o espaço de espera estiver vazio, em vez disso:

s/$/\n/

Você consegue fazer isso:

G

Anexar uma nova linha em três bytes

O Hcomando anexa uma nova linha e o conteúdo do espaço do padrão ao espaço de espera e xtroca os dois; portanto, se o espaço de espera estiver vazio, em vez disso:

s/^/\n/

Você consegue fazer isso:

H;x

Isso poluirá seu espaço de espera, portanto, só funciona uma vez. Porém, para mais dois bytes, você pode limpar o espaço do padrão antes de trocar, o que ainda economiza dois bytes:

H;z;x
Jordânia
fonte
1

No sed, a coisa mais próxima de uma função que você pode ter é um rótulo. Uma função é útil porque você pode executar seu código várias vezes, economizando muitos bytes. No sed, no entanto, você precisaria especificar o rótulo de retorno e, como tal, não pode simplesmente chamar essa "função" várias vezes em todo o código da maneira que faria em outros idiomas.

A solução alternativa usada é adicionar em uma das duas memórias um sinalizador, usado para selecionar o rótulo de retorno. Isso funciona melhor quando o código de função precisa apenas de um único espaço de memória (o outro).

Exemplo mostrando o que eu quero dizer: retirado de um projeto meu para escrever um pequeno jogo em 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

É claro que os rótulos devem ter apenas uma letra, e usei nomes completos para uma melhor explicação.

seshoumara
fonte
1

Regexes vazias são equivalentes à regex encontrada anteriormente

(obrigado a Riley por descobrir isso a partir de um envio de anagol )

Aqui está um exemplo em que temos a tarefa de criar 100 @s em um buffer vazio.

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

A segunda solução é 1 byte menor e usa o fato de que expressões regulares vazias são preenchidas com a última expressão regular encontrada. Aqui, para a segunda substituição, o último regex foi .*, portanto, o regex vazio aqui será preenchido .*. Isso também funciona com expressões regulares em /conditionals/.

Observe que é o regex encontrado anteriormente , portanto, o seguinte também funcionaria.

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

A regex vazia é preenchida em @*vez de $porque s/$/@/nunca é alcançada.

Kritixi Lithos
fonte
Sim, boa resposta. Eu até fiz regexes por mais tempo para que possam ser correspondidos dessa maneira (diminuindo o programa).
Toby Speight
0

Passo principalmente inútil:

y|A-y|B-z|

Isso só será traduzido Apara Be ypara z(... e -para -;), mas nada mais, então

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

retornará apenas:

Hello world!

Pode-se assegurar que esta vai ser inútil, por exemplo, utilizando esta em valores hexadecimais minúsculas (contendo apenas 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, eou f.)

F. Hauri
fonte
2
Isso é algo que você descobriu da maneira mais difícil ?! ;-)
Toby Speight 15/15
Gosto de scripts inúteis: sed '; ;/s/b;y|A-y|B-z|;s ;s/ //; ; ;' <<<'Hello world'(Por que isso não suprimir o espaço?)
F. Hauri