Hinzufügen von Nummern mit Regex


39

Ich möchte eine neue Art von Regex-Golf-Herausforderung ausprobieren, bei der Sie aufgefordert werden, nicht-triviale Rechenaufgaben nur durch Regex-Substitution zu lösen. Um dies zu ermöglichen und weniger lästig zu sein, können Sie mehrere Substitutionen nacheinander anwenden.

Die Herausforderung

Wir fangen einfach an: Wenn eine Zeichenfolge mit zwei positiven Ganzzahlen als durch ein getrennte Dezimalzahlen angegeben wird ,, wird eine Zeichenfolge mit ihrer Summe erstellt, auch als Dezimalzahl. Also sehr einfach

47,987

sollte sich verwandeln in

1034

Ihre Antwort sollte für beliebige positive ganze Zahlen funktionieren.

Das Format

Jede Antwort sollte eine Folge von Substitutionsschritten sein, wobei jeder Schritt aus einem regulären Ausdruck und einer Ersatzzeichenfolge besteht. Optional können Sie für jeden dieser Schritte in der Sequenz die Ersetzung wiederholen, bis sich die Zeichenfolge nicht mehr ändert. Hier ist ein Beispielbeitrag (der das obige Problem nicht löst):

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

In Anbetracht der Eingabe 123,456würde diese Übermittlung die Eingabe wie folgt verarbeiten: Die erste Substitution wird einmal angewendet und ergibt:

|123,|456

Jetzt wird die zweite Ersetzung in einer Schleife angewendet, bis sich die Zeichenfolge nicht mehr ändert:

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

Und schließlich wird die dritte Ersetzung einmal angewendet:

111111

Beachten Sie, dass das Abbruchkriterium für Schleifen darin besteht, ob sich die Zeichenfolge ändert, und nicht, ob der reguläre Ausdruck eine Übereinstimmung gefunden hat. (Das heißt, es wird möglicherweise auch beendet, wenn Sie eine Übereinstimmung finden, die Ersetzung jedoch mit der Übereinstimmung identisch ist.)

Wertung

Ihre primäre Punktzahl ist die Anzahl der Substitutionsschritte in Ihrer Einreichung. Jede wiederholte Ersetzung zählt 10 Schritte. Das obige Beispiel würde also punkten 1 + 10 + 1 = 12.

Im (nicht allzu unwahrscheinlichen) Fall eines Unentschieden ist die sekundäre Punktzahl die Summe der Größen aller Schritte. Fügen Sie für jeden Schritt den regulären Ausdruck ( ohne Begrenzer), die Modifikatoren und die Ersetzungszeichenfolge hinzu. Für das obige Beispiel wäre dies (6 + 1 + 3) + (3 + 0 + 2) + (2 + 1 + 0) = 18.

Verschiedene Regeln

Sie können ein beliebiges Regex-Aroma verwenden (das Sie angeben sollten), aber alle Schritte müssen dasselbe Aroma verwenden. Außerdem müssen Sie nicht keine Funktionen der Wirtssprache Geschmack verwenden, wie Ersatz Rückrufe oder Perl- eModifikator, der Perl - Code auswertet. Alle Manipulationen müssen ausschließlich durch reguläre Ausdrücke erfolgen.

Beachten Sie, dass es von Ihrem Geschmack und Ihren Modifikatoren abhängt, ob jeder einzelne Ersatz alle Vorkommen oder nur ein einziges ersetzt. Wenn Sie beispielsweise die ECMAScript-Variante auswählen, ersetzt ein einzelner Schritt standardmäßig nur ein Vorkommen, es sei denn, Sie verwenden den gModifikator. Wenn Sie dagegen die .NET-Variante verwenden, werden bei jedem Schritt alle Vorkommen ersetzt.

Nehmen Sie für Sprachen mit unterschiedlichen Substitutionsmethoden für einzelne und globale Ersetzungen (z. B. Ruby's subvs. gsub) an, dass einzelne Ersetzungen die Standardeinstellung sind, und behandeln Sie globale Ersetzungen wie einen gModifikator.

Testen

Wenn Sie .NET oder ECMAScript ausgewählt haben, können Sie Retina verwenden , um Ihre Einreichung zu testen (mir wurde gesagt, es funktioniert auch mit Mono). Für andere Geschmacksrichtungen müssen Sie wahrscheinlich ein kleines Programm in der Host-Sprache schreiben, das die Substitutionen der Reihe nach anwendet. In diesem Fall fügen Sie bitte dieses Testprogramm in Ihre Antwort ein.


Wenn jemand eine gute Idee hat, wie man diese Art von Herausforderung nennt, hinterlasse einen Kommentar! :) (Nur für den Fall, dass ich in Zukunft mehr davon mache.)
Martin Ender

Diejenigen, die dies mögen, können auch Addieren ohne Addition und Multiplizieren ohne Zahlen
Toby Speight

Ist Retinas Regex "Flavour" eine gültige Vorlage? : P (Ich bin ziemlich stolz auf mich, dass ich es geschafft habe, überhaupt zwei Zahlen zu addieren, geschweige denn Golf zu spielen.)
totalhuman

@icrieverytim Retinas Geschmack ist nur der .NET Geschmack.
Martin Ender

Aber Retina hat Funktionen, die .NET nicht hat, oder?
Totalhuman

Antworten:


32

.NET-Geschmack, Punktzahl: 2

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

Ich habe noch keine Lust, Golf zu spielen und xignoriere nur Leerzeichen.

Es wird zuerst 9876543210an jeder Stelle eingefügt, dann werden die Originalzeichen und die Zeichen gelöscht, die nicht die aktuelle Ziffer der Summe sind.

Der große reguläre Ausdruck (1346 Bytes ohne Leerzeichen und Kommentare):

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

Das ließ mich an die letzte Stufe von Manufactoria denken ... Aber ich denke, .NET Regex, das offensichtlich nicht mehr "normal" ist, kann alle Probleme in der PH lösen. Und das ist nur ein Algorithmus in L.


4
Alle hageln .NET-Bilanzkreise.
Sp3000

Zuerst dachte ich, mein fünfstufiger Prozess sei ziemlich gut. Dann sah ich jemanden eine Lösung mit der halben Länge behaupten. Dann das. Zählt das überhaupt als Regex?
John Dvorak

1
@ JanDvorak Für den theoretischen "regulären Ausdruck", nein. Für "Regex", ja, jeder nennt dies Regex, und fast jeder Regex-Geschmack hat so etwas. Microsoft nennt sie offiziell immer noch " reguläre Ausdrücke ".
Jimmy23013

Wow, das ist großartige Arbeit!
user230910

6

Prüfungsergebnis: 24

Ich denke das funktioniert ...

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

Ich habe noch nicht viel Zeit damit verbracht, die einzelnen regulären Ausdrücke zu spielen. Ich werde versuchen, bald eine Erklärung zu veröffentlichen, aber jetzt wird es spät. In der Zwischenzeit ist hier das Ergebnis zwischen den einzelnen Schritten:

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

Volles Perl-Programm:

$_ = <>;
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";

Das sieht meinem eigenen Proof of Concept sehr ähnlich. :) Ich hatte zwar 7 Non-Loop-Substitutionen, habe mich aber nicht besonders bemüht, diese zu unterdrücken.
Martin Ender

@ MartinBüttner haha ​​schön! Ich bin mir ziemlich sicher, dass meine letzten beiden
U-Boote

Alle führenden Räume absichtlich?
Optimierer

@Optimizer ja. Ich hätte mir einen besseren Charakter aussuchen sollen.
Grc

5

Beliebiger Regexgeschmack, 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

Lass es uns unary versuchen. dDient als Trennzeichen für die Ziffernreihenfolge, xspeichert den Wert. Zuerst heben wir jede Ziffer auf, drücken dann die x10-Multiplikatoren nach links, lassen dann alle Trennzeichen fallen, fügen die Multiplikatoren wieder ein und konvertieren dann jede Reihenfolge zurück in Ziffern.


5

.NET Regex, 14

Nicht so gut wie die Lösung von user23013, aber es hat Spaß gemacht. Keiner der Ersetzungen hat Modifikatoren.

Der Grund für die .NET-Regex liegt nicht nur in den Bilanzkreisen. Ich habe gerade mit Retina getestet , das .NET verwendet, und festgestellt, dass das Aussehen mit variabler Länge sehr hilfreich ist.

Ersetzung 1 (Wiederholung = nein)

Regex:

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

Ersatz

0$1$2

Vertausche die beiden Zahlen und fülle sie auf, um die gleiche Anzahl führender Nullen zu erhalten.

Ersetzung 2 (Wiederholung = nein)

Regex:

(\d+)

Ersatz:

 $1

Fügen Sie vor jeder Zahl ein Leerzeichen ein

Ersatz 3 (Wiederholung = nein)

$

Ersatz:

&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

Fügen Sie ein Carry-Bit (das &0) sowie die riesige Nachschlagetabelle von hinzu <c> <a> <b> <carry of a+b+c> <last digit of a+b+c>.

Ersetzung 4 (Wiederholung = ja)

Regex:

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

Ersatz:

$4$10

Nimm die letzten Ziffern jeder Zahl und finde ihre (Summe, Übertragen). Geben Sie die Summe am Anfang der Zeichenfolge ein und ersetzen Sie den Übertrag.

Ersatz 5 (Wiederholung = nein)

Regex:

^0*| .*

Ersatz:

<empty>

Aufräumen.

Beispiellauf

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

(Durch das Kombinieren einiger Schritte kann ich 12 bekommen, aber da es ziemlich chaotisch wird und sowieso nicht gewinnt, denke ich, dass ich stattdessen diese elegantere Version beibehalten werde.)


4

Prüfungsergebnis: 50 40 31 21

Vielen Dank für diese hervorragende Herausforderung. Diese Lösung ist nicht sehr elegant, aber angesichts der Einschränkungen konnte ich keine Möglichkeit finden, eine Ziffer generisch in der Ausgabe zu behandeln.

Diese Lösung bietet Erfassungsgruppen, die manchmal nicht übereinstimmen, und setzt voraus, dass sie in diesem Fall leer sind. Dies funktioniert in Perl, erzeugt jedoch normalerweise eine Warnung.

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)

Vollständiges Perl-Codebeispiel mit Erläuterung und Ausdruck der Zwischenergebnisse:

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;

Update: Ich konnte zwei der Regex-Schleifen miteinander kombinieren und 10 sparen.

Update 2: Ich habe es geschafft, die Konvertierung der eingegebenen Ziffern mit einer einzigen Regex zu knacken.

Update 3: Ich habe mich auf eine einzige Regex-Schleife reduziert.


Interessante Lösung. :) Was machen die Klammern in den Ersatzsaiten? Ist ${1}anders $1? Möglicherweise möchten Sie auch die Anzahl der Bytes bei Verbindungen angeben.
Martin Ender

@ MartinBüttner, die geschweiften Klammern trennen den Variablennamen einfach von anderen Zeichen, die sich in einer Variablen befinden könnten.

Ah, das macht Sinn. Vielen Dank.
Martin Ender

@ Martinbüttner, ich habe es geändert um \1usw zu benutzen , stattdessen speichere ich ein paar zeichen .
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.