Adicionando números com o Regex


39

Quero tentar um novo tipo de desafio de golfe regex, que solicite que você resolva tarefas computacionais não triviais com nada além de substituição de regex. Para tornar isso mais possível e menos trabalhoso, você poderá aplicar várias substituições, uma após a outra.

O desafio

Começaremos simples: dada uma string contendo dois números inteiros positivos, como números decimais separados por a ,, produza uma string contendo sua soma, também como um número decimal. Então, muito simplesmente

47,987

deve se transformar em

1034

Sua resposta deve funcionar para números inteiros positivos arbitrários.

O formato

Cada resposta deve ser uma sequência de etapas de substituição, cada etapa consistindo em uma regex e uma sequência de substituição. Opcionalmente, para cada uma dessas etapas na sequência, você pode optar por repetir a substituição até que a sequência pare de mudar. Aqui está um exemplo de envio (que não resolve o problema acima):

Regex    Modifiers   Replacement   Repeat?
\b(\d)   g           |$1           No
|\d      <none>      1|            Yes
\D       g           <empty>       No

Dada a entrada 123,456, essa submissão processaria a entrada da seguinte maneira: a primeira substituição é aplicada uma vez e produz:

|123,|456

Agora a segunda substituição é aplicada em um loop até que a string pare de mudar:

1|23,|456
11|3,|456
111|,|456
111|,1|56
111|,11|6
111|,111|

E, finalmente, a terceira substituição é aplicada uma vez:

111111

Observe que o critério de finalização para loops é se a cadeia é alterada, não se a regex encontrou uma correspondência. (Ou seja, também pode ser encerrado se você encontrar uma correspondência, mas a substituição for idêntica à correspondência.)

Pontuação

Sua pontuação principal será o número de etapas de substituição no seu envio. Toda substituição repetida contará 10 etapas. Portanto, o exemplo acima teria pontuação 1 + 10 + 1 = 12.

No caso (não muito improvável) de empate, a pontuação secundária é a soma dos tamanhos de todas as etapas. Para cada etapa, adicione o regex ( sem delimitadores), os modificadores e a string de substituição. Para o exemplo acima, isso seria (6 + 1 + 3) + (3 + 0 + 2) + (2 + 1 + 0) = 18.

Regras Diversas

Você pode usar qualquer sabor de regex (que você deve indicar), mas todas as etapas devem usar o mesmo sabor. Além disso, você não deve usar nenhum recurso do idioma do host do sabor, como retornos de chamada de substituição ou emodificador do Perl , que avalia o código do Perl. Toda manipulação deve ocorrer exclusivamente através da substituição de regex.

Observe que depende do seu sabor e modificadores se cada substituição única substitui todas as ocorrências ou apenas uma única. Por exemplo, se você escolher o sabor ECMAScript, uma única etapa, por padrão, substituirá apenas uma ocorrência, a menos que você use o gmodificador. Por outro lado, se você estiver usando o sabor do .NET, cada etapa sempre substituirá todas as ocorrências.

Para idiomas que possuem métodos de substituição diferentes para substituição única e global (por exemplo, Ruby subvs. gsub), suponha que a substituição única seja o padrão e trate a substituição global como um gmodificador.

Teste

Se o seu sabor escolhido for .NET ou ECMAScript, você poderá usar o Retina para testar seu envio (me disseram que também funciona no Mono). Para outros tipos, você provavelmente precisará escrever um pequeno programa no idioma host que aplique as substituições em ordem. Se o fizer, inclua este programa de teste na sua resposta.


Se alguém tiver uma boa idéia do que chamar esse tipo de desafio, deixe um comentário! :) (Apenas no caso de eu fazer mais disso no futuro.)
Martin Ender

Quem gosta disso também pode gostar de Adicionar sem adição e Multiplicar sem números
Toby Speight

O regex "sabor" da Retina é um envio válido? : P (Estou bastante orgulhoso de mim mesmo por conseguir adicionar dois números, muito menos jogar golfe.)
totallyhuman

@icrieverytim O sabor da Retina é apenas o sabor .NET.
Martin Ender

Mas o Retina tem recursos que o .NET não tem, não?
totallyhuman

Respostas:


32

Sabor .NET, pontuação: 2

Regex        Modifiers  Replacement  Repeat?
<empty>      <none>     9876543210   No
<see below>  x          <empty>      No

Ainda não me preocupo em jogar golfe, e xé apenas por ignorar os espaços em branco.

Primeiro, insira 9876543210em cada posição e exclua os caracteres originais e os caracteres que não são o dígito atual da soma.

O grande regex (1346 bytes sem espaços em branco e comentários):

# If the length of the left number <= right number, delete every digit on the left.
.(?=.*,(?<=^(?<len>.)*,)(?<-len>.)*(?(len)(?!)))|

# Do the opposite if it is not the case.
.(?<=(?(len)(?!))(?<-len>.)*(?=(?<len>.)*$),.*)|

# Remove leading zeros.
(?<=(^|,).{9})0|

# Delete everything that is not the current digit of the sum.
.(?!
    # For digits in the left part:
    (?<cur>.){0,9}               # cur = the matched digit
    (?=(.{11})*,)                # and find the position before the next digit.
    (?<first>)                   # first = true
    (                            # Loop on the less significant digits:
        (?<cur>){10}             # cur += 10
        (?<=                     # cur -= the current digit in this number.
            (
                0|^|
                1(?<-cur>)|
                2(?<-cur>){2}|
                3(?<-cur>){3}|
                4(?<-cur>){4}|
                5(?<-cur>){5}|
                6(?<-cur>){6}|
                7(?<-cur>){7}|
                8(?<-cur>){8}|
                9(?<-cur>){9}
            )
            .{10}
        )
        (?=
            (?<pos>.{11})*,      # pos = which digit it is.
            .*$(?<=              # cur -= the current digit in the other number.
                (
                    0|,|
                    1(?<-cur>)|
                    2(?<-cur>){2}|
                    3(?<-cur>){3}|
                    4(?<-cur>){4}|
                    5(?<-cur>){5}|
                    6(?<-cur>){6}|
                    7(?<-cur>){7}|
                    8(?<-cur>){8}|
                    9(?<-cur>){9}
                )
                .{10}
                (?(pos)(?!))     # Assert pos = 0.
                                 # Skip pos input digits from the end.
                                 # But stop and set pos = 0 if the comma is encountered.
                ((?<-pos>\d{11})|(?<=(?>(?<-pos>.)*),.{10}))*
            )
        )
        (?(first)                # If first:
            (?>((?<-cur>){10})?) #  cur -= 10 in case there is no carry.
                                 #  Assert cur = 0 or 1, and if cur = 1, set cur = 10 as carry.
            (?(cur)(?<-cur>)(?(cur)(?!))(?<cur>){10})
            (?<-first>)          #  first = false
        |                        # Else:
                                 #  cur is 10 or 20 at the beginning of an iteration.
                                 #  It must be 1 to 11 to make the equation satisfiable.
            (?<-cur>)            #  cur -= 1
            (?(cur)              #  If cur > 0:
                                 #   cur -= max(cur, 9)
                (?(cur)(?<-cur>)){9}
                (?(cur)          #   If cur > 0:
                                 #    Assert cur = 1 (was 11) and set cur = 10.
                    (?<-cur>)(?(cur)(?!))(?<cur>){10}
                |                #   Else:
                    .*(?=,)      #    cur was 2 to 10, break from the loop.
                )
            )                    #  Else cur is 0 (was 1) and do nothing.
        )
        (.{11}|,)                # Jump to the next digit.
    )*(?<=,)(?(cur)(?!))         # End the loop if it is the last digit, and assert cur = 0.
|
    # Do the same to the right part. So the sum will be calculated two times.
    # Both are truncated to the original length of the number on that side + 1.
    # Only the sum on the longer side will be preserved in the result.
    (?<cur>\d){0,9}
    (?=(\d{11})*$)
    (?<first>)
    (
        (?<cur>){10}
        (?<=
            (
                0|,|
                1(?<-cur>)|
                2(?<-cur>){2}|
                3(?<-cur>){3}|
                4(?<-cur>){4}|
                5(?<-cur>){5}|
                6(?<-cur>){6}|
                7(?<-cur>){7}|
                8(?<-cur>){8}|
                9(?<-cur>){9}
            )
            .{10}
        )
        (?=
            (?<pos>.{11})*$
            (?<=
                (
                    0|^|
                    1(?<-cur>)|
                    2(?<-cur>){2}|
                    3(?<-cur>){3}|
                    4(?<-cur>){4}|
                    5(?<-cur>){5}|
                    6(?<-cur>){6}|
                    7(?<-cur>){7}|
                    8(?<-cur>){8}|
                    9(?<-cur>){9}
                )
                .{10}
                (?(pos)(?!))
                ((?<-pos>\d{11})|(?<=^.{10})(?=(?>(?<-pos>.)*)))*
                ,.*
            )
        )
        (?(first)
            (?>((?<-cur>){10})?)
            (?(cur)(?<-cur>)(?(cur)(?!))(?<cur>){10})
            (?<-first>)
        |
            (?<-cur>)
            (?(cur)
                (?(cur)(?<-cur>)){9}
                (?(cur)
                    (?<-cur>)(?(cur)(?!))(?<cur>){10}
                |
                    .*$(?<end>)
                )
            )
        )
        (.{11}|$(?<end>))
    )*(?<-end>)(?(cur)(?!))
)

Isso me fez pensar no nível final do Manufactoria ... Mas acho que o .NET regex, que obviamente não é mais "regular", pode resolver qualquer problema no PH. E este é apenas um algoritmo em L.


4
Todos saudam grupos de balanceamento do .NET
Sp3000

Primeiro, pensei que meu processo de cinco etapas era muito bom. Então eu vi alguém reivindicar uma solução com metade do comprimento. Então isso. Isso conta como um regex?
John Dvorak

1
@JanDvorak Para a "expressão regular" teórica, não. Para "regex", sim, todo mundo chama isso de regex, e quase todo sabor de regex tem algo parecido com isto. A Microsoft ainda os chama oficialmente de " expressões regulares ".
jimmy23013

Uau, isso é um trabalho incrível!
user230910 11/01

6

Pontuação: 24

Eu acho que isso funciona ...

Regex                                                                                                                       Modifiers   Replacement             Repeat?
(?|(\d*)(\d)(,\d*)(\d)|(^,?\d*)(\d)|, |^,)                                                                                  <none>      $1$3 $2$4               Yes
$                                                                                                                           <none>      ;111111111234567890     No
0|(?|(;.*)|9(?=.*(1{9}))|8(?=.*(1{8}))|7(?=.*(1{7}))|6(?=.*(1{6}))|5(?=.*(1{5}))|4(?=.*(1{4}))|3(?=.*(111))|2(?=.*(11)))    g           $1                      No
 1{10}                                                                                                                      <none>      1                       Yes
 (?|1{9}(?=.*(9))|1{8}(?=.*(8))|1{7}(?=.*(7))|1{6}(?=.*(6))|1{5}(?=.*(5))|1{4}(?=.*(4))|1{3}(?=.*(3))|1{2}(?=.*(2))|)       g            $1                     No
 (?!\d)(?=.*(0))| |;.*                                                                                                      g           $1                      No

Ainda não gastei muito tempo jogando golfe com expressões regulares individuais. Tentarei postar uma explicação em breve, mas está ficando tarde agora. Enquanto isso, aqui está o resultado entre cada etapa:

'47,987'
' 9 48 77'
' 9 48 77;111111111234567890'
' 111111111 111111111111 11111111111111;111111111234567890'
'1  111 1111;111111111234567890'
'1  3 4;111111111234567890'
'1034'

Programa Perl completo:

$_ = <>;
chomp;

do {
    $old = $_;
    s/(?|(\d*)(\d)(,\d*)(\d)|(^,?\d*)(\d)|, |^,)/$1$3 $2$4/;
} while ($old ne $_);

s/$/;111111111234567890/;

s/0|(?|(;.*)|9(?=.*(1{9}))|8(?=.*(1{8}))|7(?=.*(1{7}))|6(?=.*(1{6}))|5(?=.*(1{5}))|4(?=.*(1{4}))|3(?=.*(111))|2(?=.*(11)))/$1/g;

do {
    $old = $_;
    s/ 1{10}/1 /;
} while ($old ne $_);

s/ (?|1{9}(?=.*(9))|1{8}(?=.*(8))|1{7}(?=.*(7))|1{6}(?=.*(6))|1{5}(?=.*(5))|1{4}(?=.*(4))|1{3}(?=.*(3))|1{2}(?=.*(2))|)/ $1/g;

s/ (?!\d)(?=.*(0))| |;.*/$1/g;

print "$_\n";

Isso se parece muito com a minha própria prova de conceito. :) No entanto, eu tinha 7 substituições sem loop, mas não me esforcei muito para mantê-las baixas.
Martin Ender

@ MartinBüttner haha ​​nice! Tenho certeza que meus últimos dois subs poderiam ser fundidos, bem como, mas eu tive o suficiente para um dia ...
grc

Todos os espaços principais são intencionais?
Optimizer

@Optimizer yes. Eu deveria ter escolhido um personagem melhor, desculpe.
grc

5

Qualquer sabor de regex, 41

    s/0/d/g
    ...
    s/9/dxxxxxxxxx/g
rep s/xd/dxxxxxxxxxxx/g
    s/[d,]//g
rep s/(^|d)xxxxxxxxxx/xd/g
    s/(^|d)xxxxxxxxx/9/g
    ...
    s/(^|d)x/1/g
    s/d/0/g

Vamos tentar unários. dserve para um separador de ordem de dígitos, xarmazena o valor. Primeiro, desarmaremos cada dígito, depois apertaremos os multiplicadores x10 para a esquerda, soltar todos os separadores, inserir novamente os multiplicadores e converter cada pedido novamente em dígitos.


5

.NET Regex, 14

Não é tão bom quanto a solução do user23013, mas foi divertido. Nenhuma das substituições possui modificadores.

O motivo do regex .NET não é por causa do balanceamento de grupos de uma vez - acabei de testar com o Retina , que usa o .NET, e também descobri que a aparência de comprimento variável ajudou muito.

Substituição 1 (repita = não)

Regex:

\d(?=\d+$)|\d(?=\d+,)|\d(?=,(\d+)$)|(?<=(\d+),\d*)\d$

Substituição

0$1$2

Troque os dois números, preenchendo para ter o mesmo número de zeros à esquerda.

Substituição 2 (repita = não)

Regex:

(\d+)

Substituição:

 $1

Adicione um espaço antes de cada número

Substituição 3 (repita = não)

$

Substituição:

&0 ~00000 ~00101 ~00202 ~00303 ~00404 ~00505 ~00606 ~00707 ~00808 ~00909 ~01001 ~01102 ~01203 ~01304 ~01405 ~01506 ~01607 ~01708 ~01809 ~01910 ~02002 ~02103 ~02204 ~02305 ~02406 ~02507 ~02608 ~02709 ~02810 ~02911 ~03003 ~03104 ~03205 ~03306 ~03407 ~03508 ~03609 ~03710 ~03811 ~03912 ~04004 ~04105 ~04206 ~04307 ~04408 ~04509 ~04610 ~04711 ~04812 ~04913 ~05005 ~05106 ~05207 ~05308 ~05409 ~05510 ~05611 ~05712 ~05813 ~05914 ~06006 ~06107 ~06208 ~06309 ~06410 ~06511 ~06612 ~06713 ~06814 ~06915 ~07007 ~07108 ~07209 ~07310 ~07411 ~07512 ~07613 ~07714 ~07815 ~07916 ~08008 ~08109 ~08210 ~08311 ~08412 ~08513 ~08614 ~08715 ~08816 ~08917 ~09009 ~09110 ~09211 ~09312 ~09413 ~09514 ~09615 ~09716 ~09817 ~09918 ~10001 ~10102 ~10203 ~10304 ~10405 ~10506 ~10607 ~10708 ~10809 ~10910 ~11002 ~11103 ~11204 ~11305 ~11406 ~11507 ~11608 ~11709 ~11810 ~11911 ~12003 ~12104 ~12205 ~12306 ~12407 ~12508 ~12609 ~12710 ~12811 ~12912 ~13004 ~13105 ~13206 ~13307 ~13408 ~13509 ~13610 ~13711 ~13812 ~13913 ~14005 ~14106 ~14207 ~14308 ~14409 ~14510 ~14611 ~14712 ~14813 ~14914 ~15006 ~15107 ~15208 ~15309 ~15410 ~15511 ~15612 ~15713 ~15814 ~15915 ~16007 ~16108 ~16209 ~16310 ~16411 ~16512 ~16613 ~16714 ~16815 ~16916 ~17008 ~17109 ~17210 ~17311 ~17412 ~17513 ~17614 ~17715 ~17816 ~17917 ~18009 ~18110 ~18211 ~18312 ~18413 ~18514 ~18615 ~18716 ~18817 ~18918 ~19010 ~19111 ~19212 ~19313 ~19414 ~19515 ~19616 ~19717 ~19818 ~19919

Adicione um bit de transporte (a &0), bem como a tabela de pesquisa gigante de<c> <a> <b> <carry of a+b+c> <last digit of a+b+c> .

Substituição 4 (repita = sim)

Regex:

(?<=(\d),.*(\d)&)(\d)(?=.*~\3\1\2(.))|(\d)(?=,.*\d&)|(?<=\d,.*)(\d)(?=&)|^(?=.* .*(\d),.*(\d)&(\d).*~\9\7\8.(.))

Substituição:

$4$10

Continue anotando os últimos dígitos de cada número e encontre seus (soma, letra). Coloque a soma no início da string e substitua o carry.

Substituição 5 (repita = não)

Regex:

^0*| .*

Substituição:

<empty>

Limpar.

Exemplo de execução

Repl no.        String
(input)         1428,57
1               000057,001428
2                000057, 001428
3                000057, 001428&0 <lookup table>
4               5 00005, 00142&1 <lookup table>
4               85 0000, 0014&0 <lookup table>
4               485 000, 001&0 <lookup table>
4               1485 00, 00&0 <lookup table>
4               01485 0, 0&0 <lookup table>
4               001485 , &0 <lookup table>
5               1485

(Ao combinar algumas das etapas, posso obter 12, mas como fica muito bagunçado e não ganha de qualquer maneira, acho que vou manter essa versão mais elegante.)


4

Pontuação: 50 40 31 21

Obrigado por este excelente desafio. Essa solução não é muito elegante, mas, dadas as restrições, não consegui ver como manipular um dígito genericamente na saída.

Esta solução apresenta grupos de captura que às vezes não correspondem e depende deles estarem vazios quando isso ocorre. Isso funciona no Perl, embora normalmente produza um aviso.

Regex 1:     (((((((((9)|8)|7)|6)|5)|4)|3)|2)|1)|0                                            
Modifiers:   g
Replacement: <$1$2$3$4$5$6$7$8$9>             
Repeat:      no

Regex 2:     (.*)<(\d*)>(,.*)<(\d*)>|(.*)<(\d*)>(.*)|(?:(^[^<]*)b(\d*)c)?(b)\d{9}(\d)(\d*)(c)
Modifiers:   none 
Replacement: \8\1\5\3b$9$11\2\6\4c\7$10$12$13 
Repeat:      yes

Regexes 3-12: ,?baaaaaaaaac
Modifiers:    g
Replacement:  9 etc. (one for each digit)

Amostra de código Perl completa, com explicação e impressão de resultados intermediários:

no warnings;
use 5.16.0;

$_ = '47,987';

#Convert numbers to beans
s/(((((((((9)|8)|7)|6)|5)|4)|3)|2)|1)|0/<$1$2$3$4$5$6$7$8$9>/g;

say;
my $last;

#Combine pairs of digits, starting with least significant.
do {
    $last=$_;
    s/(.*)<(\d*)>(,.*)<(\d*)>|(.*)<(\d*)>(.*)|(?:(^[^<]*)b(\d*)c)?(b)\d{9}(\d)(\d*)(c)/\8\1\5\3b$9$11\2\6\4c\7$10$12$13/;
    say;
}
while ($last ne $_);

#Convert beans back to numbers.
s/,?b\d{9}c/9/g;
s/,?b\d{8}c/8/g;
s/,?b\d{7}c/7/g;
s/,?b\d{6}c/6/g;
s/,?b\d{5}c/5/g;
s/,?b\d{4}c/4/g;
s/,?b\d{3}c/3/g;
s/,?b\d{2}c/2/g;
s/,?b\d{1}c/1/g;
s/,?bc/0/g;

say;

Atualização: Consegui combinar duas das regexes em loop, economizando 10.

Atualização 2: eu consegui quebrar a conversão de dígitos de entrada com um único regex.

Atualização 3: reduzi a um único regex em loop.


Solução interessante. :) O que os aparelhos fazem nas cordas de substituição? É ${1}diferente de $1? Além disso, convém incluir a contagem de bytes em caso de empate.
Martin Ender

@ MartinBüttner, as chaves simplesmente separam o nome da variável de outros caracteres que poderiam estar em uma variável.

Ah, isso faz sentido. Obrigado.
Martin Ender

@ MartinBüttner, mudei para usar \1etc., salvando alguns caracteres.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.