Aggiunta di numeri con Regex


39

Voglio provare un nuovo tipo di sfida golf regex, che ti chiede di risolvere compiti computazionali non banali con nient'altro che sostituzione regex. Per rendere questo più possibile e meno complicato, ti sarà permesso applicare più sostituzioni, una dopo l'altra.

La sfida

Inizieremo semplice: data una stringa contenente due numeri interi positivi, come numeri decimali separati da a ,, produce una stringa contenente la loro somma, anche come numero decimale. Quindi, molto semplicemente

47,987

dovrebbe trasformarsi in

1034

La tua risposta dovrebbe funzionare per interi positivi arbitrari.

Il formato

Ogni risposta dovrebbe essere una sequenza di passaggi di sostituzione, ciascuno composto da una regex e una stringa di sostituzione. Facoltativamente, per ciascuno di quei passaggi della sequenza, è possibile scegliere di ripetere la sostituzione fino a quando la stringa non smette di cambiare. Ecco un esempio di presentazione (che non risolve il problema sopra):

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

Dato l'input 123,456, questo invio elaborerà l'input come segue: la prima sostituzione viene applicata una volta e produce:

|123,|456

Ora la seconda sostituzione viene applicata in un ciclo fino a quando la stringa smette di cambiare:

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

E infine, la terza sostituzione viene applicata una volta:

111111

Si noti che il criterio di terminazione per i loop è se la stringa cambia, non se il regex ha trovato una corrispondenza. (Cioè, potrebbe anche terminare se trovi una corrispondenza ma la sostituzione è identica alla partita.)

punteggio

Il tuo punteggio principale sarà il numero di passaggi di sostituzione nella tua presentazione. Ogni sostituzione ripetuta conterà per 10 passaggi. Quindi l'esempio sopra avrebbe segnato 1 + 10 + 1 = 12.

Nel caso (non troppo improbabile) di un pareggio, il punteggio secondario è la somma delle dimensioni di tutti i passaggi. Per ogni passaggio aggiungi regex ( senza delimitatori), i modificatori e la stringa di sostituzione. Per l'esempio sopra questo sarebbe (6 + 1 + 3) + (3 + 0 + 2) + (2 + 1 + 0) = 18.

Regole varie

Puoi usare qualsiasi sapore regex (che dovresti indicare), ma tutti i passaggi devono usare lo stesso sapore. Inoltre, è necessario non usare tutte le funzioni del linguaggio host del sapore, come callback di ricambio o di Perl emodificatore, che valuta il codice Perl. Tutta la manipolazione deve avvenire esclusivamente mediante sostituzione regex.

Nota che dipende dal tuo sapore e dai modificatori se ogni singola sostituzione sostituisce tutte le occorrenze o solo una singola. Ad esempio, se si sceglie il sapore ECMAScript, per impostazione predefinita un solo passaggio sostituirà una sola occorrenza, a meno che non si utilizzi il gmodificatore. D'altra parte, se si utilizza il sapore .NET, ogni passaggio sostituirà sempre tutte le occorrenze.

Per le lingue che hanno diversi metodi di sostituzione per la sostituzione singola e globale (ad es. Ruby subvs. gsub), supponiamo che la sostituzione singola sia predefinita e trattano la sostituzione globale come un gmodificatore.

analisi

Se il sapore che hai scelto è .NET o ECMAScript, puoi utilizzare Retina per testare la tua proposta (mi è stato detto che funziona anche su Mono). Per altri gusti, probabilmente dovrai scrivere un piccolo programma nella lingua host che applica le sostituzioni in ordine. In tal caso, includere questo programma di test nella risposta.


Se qualcuno ha una buona idea di come chiamare questo tipo di sfida, lascia un commento! :) (Nel caso in cui ne farò di più in futuro.)
Martin Ender,

A chi piace questo potrebbe anche piacere Aggiungi senza aggiunta e Moltiplica senza numeri
Toby Speight,

Il "sapore" regex di Retina è una proposta valida? : P (Sono abbastanza orgoglioso di me stesso per essere riuscito ad aggiungere due numeri, per non parlare del golf.)
totalmente umano

@icrieverytim Il sapore di Retina è solo il sapore di .NET.
Martin Ender,

Ma Retina ha funzionalità che .NET non ha, no?
totalmente umano il

Risposte:


32

Sapore .NET, punteggio: 2

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

Non mi preoccupo ancora di giocare a golf, ed xè solo per ignorare gli spazi bianchi.

Innanzitutto inserisce 9876543210in ogni posizione, quindi elimina i caratteri originali e quelli che non sono la cifra corrente della somma.

La grande regex (1346 byte senza spazi bianchi e commenti):

# 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)(?!))
)

Questo mi ha fatto pensare al livello finale di Manufactoria ... Ma penso che .NET regex, che ovviamente non è più "regolare", possa risolvere eventuali problemi in PH. E questo è solo un algoritmo in L.


4
Tutti salutano i gruppi di bilanciamento .NET.
Sp3000,

Innanzitutto ho pensato che il mio processo in cinque fasi fosse abbastanza buono. Poi ho visto qualcuno chiedere una soluzione con metà della lunghezza. Poi questo. Conta come una regex?
John Dvorak,

1
@JanDvorak Per l '"espressione regolare" teorica, no. Per "regex", sì, tutti lo chiamano regex e quasi tutti i sapori di regex hanno qualcosa del genere. Microsoft li chiama ancora " espressioni regolari " ufficialmente.
jimmy23013

Wow, questo è un lavoro fantastico!
user230910

6

Punteggio: 24

Penso che funzioni ...

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

Non ho ancora passato molto tempo a giocare a golf con le singole espressioni regolari. Proverò a pubblicare presto una spiegazione, ma ora si sta facendo tardi. Nel frattempo, ecco il risultato tra ogni passaggio:

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

Programma 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";

Questo assomiglia molto alla mia prova di concetto. :) Ho avuto 7 sostituzioni senza loop, ma non ho provato particolarmente a tenerle basse.
Martin Ender,

@ MartinBüttner haha ​​nice! Sono abbastanza sicuro che anche i miei ultimi due abbonati possano essere uniti, ma ne ho avuto abbastanza per un giorno ...
GRC

Tutti gli spazi principali intenzionali?
Ottimizzatore

@Optimizer sì. Avrei dovuto scegliere un personaggio migliore, scusa.
grc

5

Qualsiasi sapore 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

Proviamo unario. dserve per un separatore dell'ordine delle cifre, xmemorizza il valore. Per prima cosa unariamo ciascuna cifra, quindi stringiamo i moltiplicatori x10 a sinistra, quindi rilasciamo tutti i separatori, quindi reinseriamo i moltiplicatori, quindi riconvertiamo ogni ordine in cifre.


5

.NET Regex, 14

Non buono come la soluzione dell'utente23013, ma è stato divertente. Nessuna delle sostituzioni ha modificatori.

Il motivo del regex .NET non è dovuto al bilanciamento dei gruppi per una volta: ho appena provato con Retina , che utilizza .NET, e ho anche scoperto che i lookbehinds di lunghezza variabile mi hanno aiutato molto.

Sostituzione 1 (ripetere = no)

regex:

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

Sostituzione

0$1$2

Scambia i due numeri, imbottitura per avere lo stesso numero di zeri iniziali.

Sostituzione 2 (ripetizione = no)

regex:

(\d+)

Sostituzione:

 $1

Aggiungi uno spazio prima di ogni numero

Sostituzione 3 (ripetizione = no)

$

Sostituzione:

&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

Aggiungi un bit di trasporto (il &0) così come la tabella di ricerca gigante di <c> <a> <b> <carry of a+b+c> <last digit of a+b+c>.

Sostituzione 4 (ripetere = sì)

regex:

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

Sostituzione:

$4$10

Continua a prendere le ultime cifre di ogni numero e trova il loro (somma, carry). Metti la somma all'inizio della stringa e sostituisci il carry.

Sostituzione 5 (ripetizione = no)

regex:

^0*| .*

Sostituzione:

<empty>

Pulire.

Esempio di esecuzione

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

(Combinando alcuni dei passaggi posso ottenere 12, ma dal momento che diventa piuttosto disordinato e non vincerà comunque, penso che terrò invece questa versione più elegante.)


4

Punteggio: 50 40 31 21

Grazie per questa eccellente sfida. Questa soluzione non è molto elegante, ma, date le restrizioni, non riuscivo a vedere alcun modo per gestire una cifra genericamente nell'output.

Questa soluzione presenta gruppi di acquisizione che a volte non corrispondono e fa affidamento sul fatto che siano vuoti quando ciò si verifica. Funziona in Perl, sebbene normalmente generi un avvertimento.

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)

Esempio di codice Perl completo, con spiegazione e stampa dei risultati intermedi:

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;

Aggiornamento: sono stato in grado di combinare due delle regex in loop, risparmiando 10.

Aggiornamento 2: sono riuscito a decifrare la conversione delle cifre di input con una sola regex.

Aggiornamento 3: ho ridotto a un regex a ciclo singolo.


Soluzione interessante. :) Cosa fanno le parentesi graffe nelle stringhe di ricambio? È ${1}diverso da $1? Inoltre, potresti voler includere il conteggio dei byte in caso di vincoli.
Martin Ender,

@ MartinBüttner, le parentesi graffe semplicemente separano il nome della variabile da altri caratteri che potrebbero essere in una variabile.

Ah, ha senso. Grazie.
Martin Ender,

@ MartinBüttner, l'ho cambiato per usarlo \1, ecc., Invece, salvando alcuni caratteri.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.