Додавання чисел з Regex


39

Я хочу спробувати новий тип виклику гольфу на регулярній вигулі, який просить вас вирішити нетривіальні обчислювальні задачі, окрім заміни гегекс. Щоб зробити це більш можливим і менше займатися, вам буде дозволено застосовувати кілька замін, одна за одною.

Змагання

Почнемо з простого: заданий рядок, що містить два натуральних числа, у вигляді десяткових чисел, розділених а ,, виробляє рядок, що містить їх суму, також у вигляді десяткового числа. Отже, дуже просто

47,987

має перетворитися на

1034

Ваша відповідь має працювати для довільних натуральних чисел.

Формат

Кожна відповідь повинна бути послідовністю етапів заміни, кожен етап складається з регулярного вираження та рядка заміни. Необов'язково, для кожного з цих кроків у послідовності ви можете повторити заміну, поки рядок не перестане змінюватися. Ось приклад подання (який не вирішує вищевказану проблему):

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

З огляду на введення 123,456, це подання обробляє введення таким чином: перша заміна застосовується один раз і дає результат:

|123,|456

Тепер друга заміна застосовується в циклі, поки рядок не перестане змінюватися:

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

І нарешті, третя заміна застосовується один раз:

111111

Зауважте, що критерієм завершення циклів є те, чи змінюється рядок, а не те, чи знайшов регулярний вираз. (Тобто це може також припинитися, якщо ви знайдете відповідність, але заміна ідентична відповідності.)

Оцінка балів

Вашим основним балом буде кількість етапів заміни у вашій заяві. Кожна повторна заміна буде рахуватися за 10 кроків. Отже, вищенаведений приклад міг би оцінити 1 + 10 + 1 = 12.

У (не надто маловірогідному) випадку равенства вторинна оцінка - це сума розмірів усіх кроків. Для кожного кроку додайте регулярний вираз ( без роздільників), модифікатори та рядок заміни. Для наведеного вище прикладу це було б (6 + 1 + 3) + (3 + 0 + 2) + (2 + 1 + 0) = 18.

Різні правила

Ви можете використовувати будь-який аромат регексу (який слід вказати), але всі кроки повинні використовувати той самий аромат. Крім того, ви не повинні використовувати будь-які функції мови хосту flavour, як, наприклад, зворотні дзвінки заміни або eмодифікатор Perl , який оцінює код Perl. Всі маніпуляції повинні відбуватися виключно за допомогою підстановки регулярних виразів.

Зауважте, що від вашого смаку та модифікаторів залежить, чи замінює кожна подія всі події або лише одну. Наприклад, якщо ви вибрали аромат ECMAScript, один крок за замовчуванням замінить лише одне явище, якщо ви не використовуєте gмодифікатор. З іншого боку, якщо ви використовуєте аромат .NET, кожен крок завжди замінить усі події.

Для мов, які мають різні способи заміни для одиночної та глобальної заміни (наприклад, Ruby's subvs. gsub), припустимо, що одна заміна є типовою за замовчуванням, і глобальну заміну слід розглядати як gмодифікатор.

Тестування

Якщо обраний вами аромат є .NET або ECMAScript, ви можете використовувати Retina для перевірки подання (мені кажуть, він працює і на Mono). Для інших ароматів вам, ймовірно, доведеться написати невелику програму на мові хоста, яка застосовує заміни в порядку. Якщо ви це зробите, будь ласка, включіть цю програму тестування у свою відповідь.


Якщо у когось є гарна ідея, як викликати цей тип викликів, залиште коментар! :) (На всякий випадок, якщо я буду робити більше цього в майбутньому.)
Мартін Ендер

Тим, кому це подобається, також може сподобатися додавання без додавання та множення без чисел
Toby Speight

Чи правильний вислів Ретіна «аромат» є дійсним поданням? : P (Я досить пишаюся собою, що зумів взагалі додати два числа, не кажучи вже про гольф.)
абсолютнолюдський

@icrieverytim Аромат Retina - це лише аромат .NET.
Мартін Ендер

Але у Retina є функції .NET не має, ні?
повністюлюдський

Відповіді:


32

.NET аромат, оцінка: 2

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

Мені ще не доводиться бавитись в гольф, і xце лише ігнорування пробілів.

Спочатку вставляють 9876543210у кожну позицію, потім видаляють початкові символи та символи, які не є поточною цифрою суми.

Великий регулярний вираз (1346 байт без пробілів та коментарів):

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

Це змусило мене думати про кінцевий рівень Manufactoria ... Але я думаю, що .NET регулярний вираз, який, очевидно, більше не є "регулярним", може вирішити будь-які проблеми в PH. І це лише алгоритм Л.


4
Усі врівноважувальні групи .NET.
Sp3000

Спочатку я подумав, що мій п’ятиступеньковий процес був досить хорошим. Потім я побачив, як хтось вимагає рішення з половиною довжини. Потім ... це. Це навіть вважається регулярним виразом?
Джон Дворак

1
@JanDvorak Для теоретичного "регулярного висловлювання" немає. Що стосується "регулярного вираження", так, всі називають це регулярним виразом, і майже кожен аромат регулярного вираження має щось подібне. Microsoft все ще офіційно називає їх " регулярними виразами ".
jimmy23013

Ого, це дивовижна робота!
user230910

6

Оцінка: 24

Я думаю, що це працює ...

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

Я ще не витрачав багато часу на гольф за окремими регулярними виразами. Я спробую опублікувати пояснення незабаром, але зараз вже пізно. Тим часом, ось результат між кожним кроком:

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

Повна програма 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";

Це дуже схоже на мій власний доказ концепції. :) У мене було 7 замінників без циклу, але я не дуже намагався їх утримати.
Мартін Ендер

@ MartinBüttner haha ​​приємно! Я впевнений, що і мої останні два абонементи можуть бути об'єднані, але мені вистачило на один день ...
grc

Усі провідні простори навмисні?
Оптимізатор

@Optimizer так. Я повинен був обрати кращого персонажа, вибачте.
grc

5

Будь-який аромат регексу, 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

Спробуємо одинарний. dслужить для цифрового роздільника порядку, xзберігає значення. Спочатку ми скасовуємо кожну цифру, потім стискаємо множники x10 ліворуч, потім скидаємо всі роздільники, потім вставляємо множники назад, а потім перетворюємо кожне замовлення назад у цифри.


5

.NET Regex, 14

Не так добре, як рішення користувача23013, але це було весело. Жодна із замін не має модифікаторів.

Причина для .ge regex не в тому, що один раз балансували групи - я щойно тестував Retina , який використовує .NET, і я також виявив, що параметри змінної довжини дуже допомогли.

Заміна 1 (повтор = ні)

Regex:

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

Заміна

0$1$2

Поміняйте місцями два числа, наклавши на них однакову кількість перших нулів.

Заміна 2 (повтор = ні)

Regex:

(\d+)

Заміна:

 $1

Додайте пробіл перед кожним номером

Заміна 3 (повтор = ні)

$

Заміна:

&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

Додайте біт для перенесення (the &0), а також гігантську таблицю пошуку <c> <a> <b> <carry of a+b+c> <last digit of a+b+c>.

Заміна 4 (повтор = так)

Regex:

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

Заміна:

$4$10

Продовжуйте брати останні цифри кожного числа і знаходити їх (суму, нести). Покладіть суму на початок рядка і замініть перенос.

Заміна 5 (повтор = ні)

Regex:

^0*| .*

Заміна:

<empty>

Прибирати.

Приклад виконання

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

(Комбінуючи декілька етапів, я можу отримати 12, але оскільки він стає досить безладним і все одно не виграє, я думаю, що замість цього я буду тримати цю більш елегантну версію.)


4

Оцінка: 50 40 31 21

Дякую за цей чудовий виклик. Це рішення не дуже елегантне, але, враховуючи обмеження, я не міг бачити жодного способу обробити цифру в загальному висновку.

Це рішення містить групи захоплення, які іноді не відповідають, і покладається на те, що вони будуть порожніми, коли це відбувається. Це працює в Perl, хоча зазвичай створює попередження.

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)

Повний зразок коду Perl з поясненням та друком проміжних результатів:

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;

Оновлення: мені вдалося поєднати два циклічних реджекси разом, заощадивши 10.

Оновлення 2: Мені вдалося зламати перетворення вхідної цифри одним регексом.

Оновлення 3: я зменшився до одного контурного циклу.


Цікаве рішення. :) Що дужки роблять у замінних рядках? Чи ${1}відрізняється від $1? Також ви можете включити кількість байтів у разі зв’язків.
Мартін Ендер

@ MartinBüttner, дужки просто відокремлюють назву змінної від інших символів, які можуть бути в змінній.

Ах, це має сенс. Спасибі.
Мартін Ендер

@ MartinBüttner, я змінив його на використання \1тощо, замість цього, збережи кілька символів.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.