Ajouter des nombres avec Regex

39

Je veux essayer un nouveau type de défi de golf regex, qui vous demande de résoudre des tâches de calcul non triviales avec rien de moins que la substitution de regex. Pour rendre cela plus possible et moins pénible, il vous sera permis d'appliquer plusieurs substitutions, l'une après l'autre.

Le défi

Commençons simplement: étant donné qu'une chaîne contenant deux entiers positifs, sous forme de nombres décimaux séparés par un ,, produit une chaîne contenant leur somme, également sous la forme d'un nombre décimal. Donc, très simplement

47,987

devrait se transformer en

1034

Votre réponse devrait fonctionner pour des entiers positifs arbitraires.

Le format

Chaque réponse devrait être une séquence d'étapes de substitution, chaque étape consistant en une expression rationnelle et une chaîne de remplacement. Facultativement, pour chacune de ces étapes de la séquence, vous pouvez choisir de répéter la substitution jusqu'à ce que la chaîne cesse de changer. Voici un exemple de soumission (qui ne résout pas le problème ci-dessus):

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

Compte tenu de l'entrée 123,456, cette soumission traiterait l'entrée comme suit: la première substitution est appliquée une fois et donne:

|123,|456

Maintenant, la deuxième substitution est appliquée dans une boucle jusqu'à ce que la chaîne cesse de changer:

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

Et enfin, la troisième substitution est appliquée une fois:

111111

Notez que le critère de terminaison pour les boucles est de savoir si la chaîne a été modifiée, et non si l'expression régulière a trouvé une correspondance. (En d’autres termes, il est également possible qu’il se termine si vous trouvez une correspondance, mais le remplacement est identique à la correspondance.)

Notation

Votre score principal sera le nombre d'étapes de substitution dans votre soumission. Chaque substitution répétée comptera pour 10 étapes. Ainsi, l'exemple ci-dessus marquerait 1 + 10 + 1 = 12.

Dans le cas d'égalité (pas très improbable), le score secondaire est la somme des tailles de toutes les étapes. Pour chaque étape, ajoutez l'expression régulière ( sans délimiteurs), les modificateurs et la chaîne de substitution. Pour l'exemple ci-dessus, ce serait(6 + 1 + 3) + (3 + 0 + 2) + (2 + 1 + 0) = 18 .

Règles diverses

Vous pouvez utiliser n’importe quel goût de regex (que vous devez indiquer), mais toutes les étapes doivent utiliser le même goût. De plus, vous ne devez utiliser aucune fonctionnalité du langage hôte de la version, comme les rappels de remplacement ou le emodificateur de Perl , qui évalue le code Perl. Toute manipulation doit avoir lieu exclusivement par substitution de regex.

Notez que cela dépend de votre saveur et des modificateurs si chaque remplacement remplace toutes les occurrences ou si une seule. Par exemple, si vous choisissez la variante ECMAScript, une seule étape ne remplacera par défaut qu'une occurrence, à moins que vous n'utilisiez le gmodificateur. Par contre, si vous utilisez la version .NET, chaque étape remplacera toujours toutes les occurrences.

Pour les langues ayant différentes méthodes de substitution pour le remplacement unique et global (par exemple, Ruby's subvs. gsub), supposons que le remplacement unique soit la valeur par défaut et traite le remplacement global comme un gmodificateur.

Essai

Si votre version choisie est .NET ou ECMAScript, vous pouvez utiliser Retina pour tester votre soumission (on me dit que cela fonctionne également sur Mono). Pour les autres variantes, vous devrez probablement écrire un petit programme dans le langage hôte qui applique les substitutions dans l’ordre. Si vous le faites, veuillez inclure ce programme de test dans votre réponse.

Martin Ender
la source
Si quelqu'un a une bonne idée de comment appeler ce type de défi, laissez un commentaire! :) (Juste au cas où j'en ferais plus à l'avenir.)
Martin Ender
Ceux qui aiment cela pourraient aussi profiter de Ajouter sans ajout et Multiplier sans nombre
Toby Speight
Le regex "flavour" de Retina est-il une soumission valide? : P (Je suis assez fier de moi pour avoir réussi à ajouter deux chiffres, sans parler du golf.)
totalement humain
La saveur de @icrieverytim Retina n'est que la saveur .NET.
Martin Ender
Mais Retina a des fonctionnalités. NET n'a pas, non?
totalement humain

Réponses:

32

Saveur .NET, score: 2

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

Je ne suis pas encore dérangé pour jouer au golf, et xc'est juste pour ignorer les espaces blancs.

Il insère d’abord 9876543210à chaque position, puis supprime les caractères originaux et ceux qui ne sont pas le chiffre actuel de la somme.

Le grand regex (1346 octets sans espaces et commentaires):

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

Cela m'a fait penser au niveau final de Manufactoria ... Mais je pense que la regex .NET, qui n'est évidemment plus "régulière", peut résoudre tous les problèmes de PH. Et ceci est juste un algorithme dans L.

jimmy23013
la source
4
Tous saluent les groupes d'équilibrage .NET.
Sp3000
J'ai d'abord pensé que mon processus en cinq étapes était plutôt bon. Ensuite, j'ai vu quelqu'un réclamer une solution deux fois moins longue. Ensuite ceci. Est-ce que cela compte même comme une regex?
John Dvorak
1
@JanDvorak Pour "l'expression régulière" théorique, non. Pour "regex", oui, tout le monde appelle cela une regex, et presque chaque saveur de regex a quelque chose comme ça. Microsoft les appelle encore " expressions régulières " officiellement.
jimmy23013
Wow, c'est un travail incroyable!
user230910
6

Score: 24

Je pense que ça marche ...

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

Je n'ai pas encore passé beaucoup de temps à jouer au golf dans les expressions régulières. Je vais essayer de poster une explication bientôt, mais il se fait tard maintenant. En attendant, voici le résultat entre chaque étape:

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

Programme complet en Perl:

$_ = <>;
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";
grc
la source
Cela ressemble beaucoup à ma propre preuve de concept. :) J'ai eu 7 substitutions sans boucle cependant, mais je n'ai pas essayé particulièrement de les réduire.
Martin Ender
@ MartinBüttner haha ​​nice! Je suis presque sûr que mes deux derniers sous-marins pourraient également être fusionnés, mais j'en ai assez pour une journée ...
grc
Tous les espaces principaux sont-ils intentionnels?
Optimiseur
@Optimizer oui. J'aurais dû choisir un meilleur personnage désolé.
grc
5

Toute saveur 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

Essayons unaire. dsert pour un séparateur d'ordre numérique, xstocke la valeur. Tout d'abord, nous décomposons chaque chiffre, puis nous comprimons les multiplicateurs x10 à gauche, puis supprimons tous les séparateurs, puis réinsérons les multiplicateurs, puis convertissons chaque commande en chiffres.

John Dvorak
la source
5

.NET Regex, 14

Pas aussi bon que la solution de user23013, mais c'était amusant. Aucun des remplaçants ont modificateurs.

La raison de la regex .NET n’est pas due à l’équilibrage des groupes pour une fois - je viens de tester avec Retina , qui utilise .NET, et j'ai également constaté que la recherche de longueur variable aidait beaucoup.

Remplacement 1 (répéter = non)

Regex:

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

Remplacement

0$1$2

Échangez les deux nombres, le rembourrage ayant le même nombre de zéros au début.

Remplacement 2 (répétition = non)

Regex:

(\d+)

Remplacement:

 $1

Ajouter un espace avant chaque numéro

Remplacement 3 (répétition = non)

$

Remplacement:

&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

Ajoutez un support (le &0) ainsi que la table de recherche géante de<c> <a> <b> <carry of a+b+c> <last digit of a+b+c> .

Remplacement 4 (répéter = oui)

Regex:

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

Remplacement:

$4$10

Continuez à prendre les derniers chiffres de chaque numéro et trouvez leur (somme, report). Mettez la somme au début de la chaîne et remplacez le report.

Remplacement 5 (répétez = non)

Regex:

^0*| .*

Remplacement:

<empty>

Nettoyer.

Exemple d'exécution

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

(En combinant quelques-unes des étapes, je peux obtenir 12, mais comme cela devient assez compliqué et que je ne gagnerai pas de toute façon, je pense que je garderai cette version plus élégante à la place.)

Sp3000
la source
4

Score: 50 40 31 21

Merci pour cet excellent challenge. Cette solution n'est pas très élégante, mais compte tenu des restrictions, je ne voyais aucun moyen de gérer un chiffre de manière générique dans la sortie.

Cette solution comporte des groupes de capture qui parfois ne correspondent pas et exige qu’ils soient vides le cas échéant. Cela fonctionne en Perl, bien qu'il génère normalement un avertissement.

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)

Exemple complet de code Perl, avec explication et impression des résultats intermédiaires:

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;

Mise à jour: J'ai été capable de combiner deux des expressions rationnelles en boucle ensemble, en économisant 10.

Mise à jour 2: J'ai réussi à déchiffrer la conversion des chiffres d'entrée avec une seule expression régulière.

Mise à jour 3: J'ai réduit à une seule expression rationnelle en boucle.


la source
Solution intéressante. :) Que font les accolades dans les chaînes de remplacement? Est ${1}différent de $1? En outre, vous pouvez inclure le nombre d'octets en cas d'égalité.
Martin Ender
@ MartinBüttner, les accolades séparent simplement le nom de la variable des autres caractères pouvant figurer dans une variable.
Ah, c'est logique. Merci.
Martin Ender
@ MartinBüttner, je l'ai changé pour utiliser \1, etc., en enregistrant quelques caractères.