Menambahkan Angka dengan Regex

39

Saya ingin mencoba jenis baru tantangan golf regex, yang meminta Anda untuk menyelesaikan tugas komputasi nontrivial dengan apa pun selain penggantian regex. Untuk membuat ini lebih mungkin dan lebih sedikit dari tugas, Anda akan diizinkan untuk menerapkan beberapa pergantian, satu demi satu.

Tantangan

Kami akan mulai dengan sederhana: diberi string yang berisi dua bilangan bulat positif, sebagai angka desimal dipisahkan oleh a ,, menghasilkan string yang berisi jumlah mereka, juga sebagai angka desimal. Jadi, sangat sederhana

47,987

harus berubah menjadi

1034

Jawaban Anda harus bekerja untuk bilangan bulat positif sewenang-wenang.

Format

Setiap jawaban harus merupakan urutan langkah substitusi, setiap langkah yang terdiri dari regex dan string pengganti. Secara opsional, untuk masing-masing langkah dalam urutan, Anda dapat memilih untuk mengulangi substitusi hingga string berhenti berubah. Ini adalah contoh pengiriman (yang tidak menyelesaikan masalah di atas):

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

Diberikan input 123,456, pengajuan ini akan memproses input sebagai berikut: substitusi pertama diterapkan sekali dan menghasilkan:

|123,|456

Sekarang substitusi kedua diterapkan dalam satu lingkaran sampai string berhenti berubah:

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

Dan terakhir, substitusi ketiga diterapkan sekali:

111111

Perhatikan bahwa kriteria terminasi untuk loop adalah apakah string berubah, bukan apakah regex menemukan kecocokan. (Yaitu, itu mungkin juga berakhir jika Anda menemukan kecocokan tetapi penggantinya identik dengan kecocokan.)

Mencetak gol

Skor utama Anda akan menjadi jumlah langkah substitusi dalam kiriman Anda. Setiap penggantian yang berulang akan dihitung untuk 10 langkah. Jadi contoh di atas akan memberi skor 1 + 10 + 1 = 12.

Dalam kasus seri (tidak terlalu tidak mungkin), skor sekunder adalah jumlah dari semua langkah. Untuk setiap langkah tambahkan regex ( tanpa pembatas), pengubah dan string substitusi. Untuk contoh di atas ini akan menjadi (6 + 1 + 3) + (3 + 0 + 2) + (2 + 1 + 0) = 18.

Aturan Lain-lain

Anda dapat menggunakan rasa regex (yang harus Anda tunjukkan), tetapi semua langkah harus menggunakan rasa yang sama. Selain itu, Anda tidak boleh menggunakan fitur apa pun dari bahasa host flavour, seperti callback pengganti atau epengubah Perl , yang mengevaluasi kode Perl. Semua manipulasi harus terjadi secara eksklusif melalui penggantian regex.

Perhatikan bahwa itu tergantung pada rasa dan pengubah Anda apakah setiap penggantian tunggal menggantikan semua kejadian atau hanya satu saja. Misalnya jika Anda memilih aroma ECMAScript, satu langkah secara default hanya akan menggantikan satu kejadian, kecuali jika Anda menggunakan gpengubah. Di sisi lain, jika Anda menggunakan .NET flavor, setiap langkah akan selalu menggantikan semua kejadian.

Untuk bahasa yang memiliki metode substitusi berbeda untuk penggantian tunggal dan global (mis. Ruby subvs. gsub), asumsikan bahwa penggantian tunggal adalah default dan memperlakukan penggantian global seperti gpengubah.

Pengujian

Jika rasa yang Anda pilih adalah .NET atau ECMAScript, Anda dapat menggunakan Retina untuk menguji kiriman Anda (saya diberitahu, ini juga berfungsi pada Mono). Untuk rasa lain, Anda mungkin harus menulis sebuah program kecil dalam bahasa host yang menggunakan substitusi secara berurutan. Jika ya, harap sertakan program pengujian ini dalam jawaban Anda.

Martin Ender
sumber
Jika ada yang tahu apa yang disebut tantangan jenis ini, tinggalkan komentar! :) (Untuk berjaga-jaga jika saya melakukan lebih dari ini di masa depan.)
Martin Ender
Mereka yang suka ini mungkin juga menikmati Tambahkan tanpa penambahan dan Lipatgandakan tanpa angka
Toby Speight
Apakah "rasa" regex Retina adalah pengiriman yang valid? : P (Saya cukup bangga pada diri sendiri karena berhasil menambahkan dua angka sama sekali, apalagi bermain golf.)
totallyhuman
@icrieverytim Rasa Retina hanyalah rasa .NET.
Martin Ender
Tapi Retina punya fitur. NET tidak punya, kan?
totallyhuman

Jawaban:

32

.NET flavor, skor: 2

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

Saya belum terganggu untuk golf itu, dan xhanya untuk mengabaikan ruang putih.

Pertama-tama masukkan 9876543210di setiap posisi, lalu hapus karakter asli dan karakter yang bukan digit jumlah saat ini.

Regex besar (1346 bytes tanpa spasi putih dan komentar):

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

Ini membuat saya berpikir tentang tingkat akhir Manufactoria ... Tapi saya pikir .NET regex, yang jelas tidak lagi "biasa", dapat menyelesaikan masalah dalam PH. Dan ini hanya sebuah algoritma dalam L.

jimmy23013
sumber
4
Semua hujan es. Grup penyeimbang NET.
Sp3000
Pertama saya pikir proses lima langkah saya cukup bagus. Lalu saya melihat seseorang mengklaim solusi dengan panjang setengahnya. Lalu ini. Apakah ini bahkan dianggap sebagai regex?
John Dvorak
1
@JanDvorak Untuk "ekspresi reguler" teoretis, no. Untuk "regex", ya, semua orang menyebutnya regex, dan hampir setiap rasa regex memiliki sesuatu seperti ini. Microsoft masih menyebut mereka " ekspresi reguler " secara resmi.
jimmy23013
Wow, ini pekerjaan luar biasa!
user230910
6

Nilai: 24

Saya pikir ini bekerja ...

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

Saya belum menghabiskan banyak waktu bermain golf dengan ekspresi reguler individu. Saya akan mencoba memposting penjelasan segera, tetapi sudah terlambat sekarang. Sementara itu, inilah hasil antara setiap langkah:

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

Program perl penuh:

$_ = <>;
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
sumber
Ini sangat mirip dengan bukti konsep saya sendiri. :) Saya punya 7 pengganti non-loop, tapi saya tidak berusaha sangat keras untuk membuatnya tetap rendah.
Martin Ender
@ MartinBüttner haha ​​bagus! Saya cukup yakin dua kapal selam terakhir saya dapat digabungkan juga, tetapi saya sudah cukup untuk satu hari ...
grc
Semua ruang terdepan disengaja?
Pengoptimal
@Optimizer ya. Aku seharusnya memilih karakter yang lebih baik, maaf.
grc
5

Setiap rasa 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

Mari kita coba unary. dberfungsi untuk pemisah urutan digit, xmenyimpan nilai. Pertama kita menghapus setiap digit, kemudian kita menekan pengali x10 ke kiri, lalu jatuhkan semua pemisah, lalu masukkan kembali pengali, lalu konversikan setiap urutan kembali ke angka.

John Dvorak
sumber
5

.NET Regex, 14

Tidak sebagus solusi user23013, tapi itu menyenangkan. Tidak ada pengganti yang memiliki pengubah.

Alasan untuk. NET regex bukan karena menyeimbangkan kelompok untuk sekali - saya baru saja menguji dengan Retina , yang menggunakan .NET, dan saya juga menemukan bahwa panjang variabel terlihat sangat membantu.

Penggantian 1 (ulangi = tidak)

Regex:

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

Penggantian

0$1$2

Tukar dua angka, padding untuk memiliki angka nol terkemuka yang sama.

Penggantian 2 (ulangi = tidak)

Regex:

(\d+)

Penggantian:

 $1

Tambahkan spasi sebelum setiap nomor

Penggantian 3 (ulangi = tidak)

$

Penggantian:

&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

Tambahkan carry bit (the &0) serta tabel pencarian raksasa<c> <a> <b> <carry of a+b+c> <last digit of a+b+c> .

Penggantian 4 (ulangi = ya)

Regex:

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

Penggantian:

$4$10

Terus ambil digit terakhir dari setiap angka, dan temukan (jumlah, bawa). Masukkan jumlah pada awal string dan ganti carry.

Penggantian 5 (ulangi = tidak)

Regex:

^0*| .*

Penggantian:

<empty>

Membersihkan.

Contoh dijalankan

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

(Dengan menggabungkan beberapa langkah saya bisa mendapatkan 12, tetapi karena itu menjadi sangat berantakan dan tidak akan menang, saya pikir saya akan terus versi yang lebih elegan ini.)

Sp3000
sumber
4

Nilai: 50 40 31 21

Terima kasih atas tantangan luar biasa ini. Solusi ini tidak terlalu elegan, tetapi, mengingat batasannya, saya tidak bisa melihat cara untuk menangani angka secara umum di output.

Solusi ini menampilkan grup tangkap yang terkadang tidak cocok dan mengandalkan mereka yang kosong ketika itu terjadi. Ini berfungsi di Perl, meskipun biasanya menghasilkan peringatan.

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)

Contoh kode Perl lengkap, dengan penjelasan dan pencetakan hasil antara:

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;

Pembaruan: Saya dapat menggabungkan dua regex pengulangan bersama, menghemat 10.

Pembaruan 2: Saya berhasil memecahkan konversi digit input dengan regex tunggal.

Pembaruan 3: Saya dikurangi menjadi regex perulangan tunggal.


sumber
Solusi menarik. :) Apa yang dilakukan kawat gigi dalam string pengganti? Apakah ${1}berbeda dari $1? Juga, Anda mungkin ingin memasukkan jumlah byte jika ada hubungan.
Martin Ender
@ MartinBüttner, kawat gigi hanya memisahkan nama variabel dari karakter lain yang bisa dalam variabel.
Ah, itu masuk akal. Terima kasih.
Martin Ender
@ MartinBüttner, saya mengubahnya untuk digunakan \1, dll, sebagai gantinya, menyimpan beberapa karakter.