Agregando Números con Regex

39

Quiero probar un nuevo tipo de desafío regex golf, que le pide que resuelva tareas computacionales no triviales con nada más que la sustitución de expresiones regulares. Para hacer esto más posible y menos complicado, se le permitirá aplicar varias sustituciones, una tras otra.

El reto

Comenzaremos de manera simple: dada una cadena que contiene dos enteros positivos, como números decimales separados por a ,, se produce una cadena que contiene su suma, también como un número decimal. Entonces, muy simple

47,987

debería convertirse en

1034

Su respuesta debería funcionar para enteros positivos arbitrarios.

El formato

Cada respuesta debe ser una secuencia de pasos de sustitución, cada paso compuesto por una expresión regular y una cadena de reemplazo. Opcionalmente, para cada uno de esos pasos en la secuencia, puede elegir repetir la sustitución hasta que la cadena deje de cambiar. Aquí hay un ejemplo de envío (que no resuelve el problema anterior):

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

Dada la entrada 123,456, esta presentación procesará la entrada de la siguiente manera: la primera sustitución se aplica una vez y produce:

|123,|456

Ahora la segunda sustitución se aplica en un bucle hasta que la cadena deja de cambiar:

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

Y, por último, la tercera sustitución se aplica una vez:

111111

Tenga en cuenta que el criterio de terminación para los bucles es si la cadena cambia, no si la expresión regular encontró una coincidencia. (Es decir, también podría terminar si encuentra una coincidencia, pero el reemplazo es idéntico a la coincidencia).

Tanteo

Su puntaje principal será el número de pasos de sustitución en su envío. Cada sustitución repetida contará para 10 pasos. Entonces, el ejemplo anterior se puntuaría 1 + 10 + 1 = 12.

En el caso (no muy improbable) de un empate, la puntuación secundaria es la suma de los tamaños de todos los pasos. Para cada paso, agregue la expresión regular ( sin delimitadores), los modificadores y la cadena de sustitución. Para el ejemplo anterior esto sería (6 + 1 + 3) + (3 + 0 + 2) + (2 + 1 + 0) = 18.

Reglas misceláneas

Puede usar cualquier sabor regex (que debe indicar), pero todos los pasos deben usar el mismo sabor. Por otra parte, se debe no utilizar las funciones del lenguaje anfitrión del sabor, como las devoluciones de llamada de repuesto o de Perl emodificador, que evalúa código Perl. Toda manipulación debe ocurrir exclusivamente a través de la sustitución de expresiones regulares.

Tenga en cuenta que depende de su sabor y modificadores si cada reemplazo reemplaza todos los sucesos o solo uno. Por ejemplo, si elige el sabor ECMAScript, un solo paso reemplazará de manera predeterminada solo una aparición, a menos que use el gmodificador. Por otro lado, si está utilizando el sabor .NET, cada paso siempre reemplazará todas las ocurrencias.

Para los idiomas que tienen diferentes métodos de sustitución para el reemplazo único y global (por ejemplo, Ruby's subvs. gsub), suponga que el reemplazo único es el predeterminado y trate el reemplazo global como un gmodificador.

Pruebas

Si su sabor elegido es .NET o ECMAScript, puede usar Retina para probar su envío (me han dicho, también funciona en Mono). Para otros sabores, probablemente tendrá que escribir un pequeño programa en el idioma del host que aplique las sustituciones en orden. Si lo hace, incluya este programa de prueba en su respuesta.

Martin Ender
fuente
Si alguien tiene una buena idea de cómo llamar a este tipo de desafío, ¡deje un comentario! :) (Por si acaso voy a hacer más de estos en el futuro.)
Martin Ender
Aquellos a quienes les guste esto también pueden disfrutar de Agregar sin sumar
Toby Speight
¿El "sabor" de la expresión regular de Retina es una presentación válida? : P (estoy bastante orgulloso de mí mismo por haber logrado sumar dos números, y mucho menos jugar golf)
Totalmente humano el
El sabor de @icrieverytim Retina es solo el sabor de .NET.
Martin Ender
Pero Retina tiene características .NET no tiene, ¿no?
Totalmente humano

Respuestas:

32

Sabor .NET, puntuación: 2

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

Todavía no me molesto en jugar golf, y xes solo por ignorar los espacios en blanco.

Primero inserte 9876543210en cada posición, luego elimine los caracteres originales y los caracteres que no son el dígito actual de la suma.

La expresión regular grande (1346 bytes sin espacios en blanco y comentarios):

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

Esto me hizo pensar en el nivel final de Manufactoria ... Pero creo que .NET regex, que obviamente ya no es "regular", puede resolver cualquier problema en PH. Y esto es solo un algoritmo en L.

jimmy23013
fuente
44
Todos saludan a los grupos de equilibrio de .NET.
Sp3000
Primero pensé que mi proceso de cinco pasos era bastante bueno. Entonces vi a alguien reclamar una solución con la mitad de la longitud. Luego esto. ¿Esto incluso cuenta como una expresión regular?
John Dvorak
1
@ JanDvorak Para la "expresión regular" teórica, no. Para "regex", sí, todo el mundo llama a esto una expresión regular, y casi todos los sabores de expresiones regulares tienen algo como esto. Microsoft todavía los llama " expresiones regulares " oficialmente.
jimmy23013
¡Guau, este es un trabajo increíble!
user230910
6

Puntuación: 24

Creo que esto 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

Todavía no he pasado mucho tiempo jugando golf con las expresiones regulares individuales. Intentaré publicar una explicación pronto, pero ya se está haciendo tarde. Mientras tanto, aquí está el resultado entre cada paso:

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

Programa completo de 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
fuente
Esto se parece mucho a mi propia prueba de concepto. :) Sin embargo, tuve 7 sustituciones sin bucle, pero no me esforcé especialmente por mantenerlas bajas.
Martin Ender
@ MartinBüttner jaja agradable! Estoy bastante seguro de que mis dos últimos subs también podrían fusionarse, pero he tenido suficiente por un día ...
grc 9/15
Todos los espacios principales intencionales?
Optimizador
@ Optimizador sí. Debería haber elegido un mejor personaje lo siento.
grc
5

Cualquier sabor 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

Probemos unario. dsirve para un separador de orden de dígitos, xalmacena el valor. Primero desarmamos cada dígito, luego exprimimos los multiplicadores x10 a la izquierda, luego soltamos todos los separadores, luego volvemos a insertar los multiplicadores, luego convertimos cada orden nuevamente en dígitos.

John Dvorak
fuente
5

.NET Regex, 14

No es tan bueno como la solución de user23013, pero fue divertido. Ninguno de los reemplazos tiene modificadores.

La razón de la expresión regular de .NET no se debe al equilibrio de los grupos por una vez: solo probé con Retina , que usa .NET, y también descubrí que las miradas de longitud variable me ayudaron mucho.

Reemplazo 1 (repetir = no)

Regex:

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

Reemplazo

0$1$2

Cambie los dos números, rellenando para tener el mismo número de ceros a la izquierda.

Reemplazo 2 (repetir = no)

Regex:

(\d+)

Reemplazo:

 $1

Agrega un espacio antes de cada número

Reemplazo 3 (repetir = no)

$

Reemplazo:

&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

Agregue un bit de acarreo (el &0) así como la tabla de búsqueda gigante de <c> <a> <b> <carry of a+b+c> <last digit of a+b+c>.

Reemplazo 4 (repetir = sí)

Regex:

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

Reemplazo:

$4$10

Sigue tomando los últimos dígitos de cada número y encuentra su (suma, acarreo). Pon la suma al comienzo de la cuerda y reemplaza el carry.

Reemplazo 5 (repetir = no)

Regex:

^0*| .*

Reemplazo:

<empty>

Limpiar.

Ejecución de ejemplo

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

(Al combinar algunos de los pasos puedo obtener 12, pero dado que se vuelve bastante complicado y no ganará de todos modos, creo que seguiré con esta versión más elegante).

Sp3000
fuente
4

Puntuación: 50 40 31 21

Gracias por este excelente desafío. Esta solución no es muy elegante, pero, dadas las restricciones, no pude ver ninguna forma de manejar un dígito genéricamente en la salida.

Esta solución presenta grupos de captura que a veces no coinciden y depende de que estén vacíos cuando eso ocurre. Esto funciona en Perl, aunque normalmente produce una advertencia.

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)

Ejemplo completo de código Perl, con explicación e impresión de resultados intermedios:

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;

Actualización: pude combinar dos de las expresiones regulares en bucle, ahorrando 10.

Actualización 2: logré descifrar la conversión de dígitos de entrada con una sola expresión regular.

Actualización 3: reduje a una sola expresión regular de bucle.


fuente
Solución interesante :) ¿Qué hacen las llaves en las cadenas de reemplazo? Es ${1}diferente de $1? Además, es posible que desee incluir el recuento de bytes en caso de empate.
Martin Ender
@ MartinBüttner, las llaves simplemente separan el nombre de la variable de otros caracteres que podrían estar en una variable.
Ah, eso tiene sentido. Gracias.
Martin Ender
@ MartinBüttner, lo cambié para usar \1, etc., en cambio, guardando algunos caracteres.