使用正则表达式添加数字


39

我想尝试一种新型的regex高尔夫挑战赛,它要求您用正则表达式替代只解决简单的计算任务。为了使这一切成为可能,减少琐事,您将被允许应用多个替换,一个接一个。

挑战

我们将从简单开始:给定一个包含两个正整数的字符串,用用a分隔的十进制数表示,,产生一个包含它们的总和的字符串,也用十进制数表示。所以,非常简单

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

杂项规则

您可以使用任何正则表达式香料(应指出),但是所有步骤都必须使用相同的香料。此外,您不得使用调味剂的宿主语言的任何功能,例如替换回调或e评估Perl代码的Perl 修饰符。所有操作必须仅通过正则表达式替换进行。

请注意,是否每次替换都替换所有匹配项还是仅替换单个匹配项,取决于您的口味和修饰符。例如,如果您选择ECMAScript风味,则默认情况下,除非使用g修饰符,否则一步仅会替换一个实例。另一方面,如果您使用的是.NET风格,则每个步骤将始终替换所有出现的内容。

对于单次和全局替换具有不同替换方法的语言(例如Ruby's subvs. gsub),请假定默认为单次替换,并将全局替换视为g修饰符。

测试中

如果您选择的风格是.NET或ECMAScript,则可以使用Retina来测试提交(据说,它也可以在Mono上运行)。对于其他口味,您可能必须使用宿主语言编写一个小程序,该程序按顺序应用替换。如果这样做,请在答案中包含此测试程序。


如果有人知道如何称呼这种挑战,请发表评论!:)(以防万一,将来我还会做更多。)
马丁·恩德

那些喜欢这种方式的人可能还会喜欢无加法无数乘法
Toby Speight 2015年

Retina的正则表达式是否“有效”提交?:P(我为自己能将两个数字相加而感到自豪,更不用说打高尔夫了。)
totallyhuman

@icrieverytim Retina的味道只是.NET的味道。
Martin Ender

但是Retina具有.NET所没有的功能,不是吗?
–totalhuman

Answers:


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的最终级别...但是我认为显然不再是“ regular”的.NET regex可以解决PH中的任何问题。这只是L中的一种算法。


4
所有.NET平衡组。
Sp3000 2015年

首先,我认为我的五步过程非常好。然后我看到有人要求长度只有一半的解决方案。那... 这算作正则表达式吗?
John Dvorak 2015年

1
@JanDvorak对于理论上的“正则表达式”,不。对于“正则表达式”,是的,每个人都将其称为正则表达式,几乎每种正则表达式都具有这样的含义。微软仍然正式称它们为“ 正则表达式 ”。
jimmy23013 2015年

哇,这是很棒的工作!
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哈哈,真好!我很确定我的最后两个潜艇也可以合并,但是我已经有足够的一天了……
grc 2015年

所有前导空格是故意的吗?
Optimizer

@Optimizer是的。我应该选择一个更好的角色,对不起。
grc 2015年

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正则表达式,14

不如user23013的解决方案好,但这很有趣。所有替代品都没有修饰符。

.NET正则表达式的原因不是因为一次平衡组-我刚刚对使用.NET的Retina进行了测试,并且我还发现可变长度的lookbehinds很有帮助。

替换1(重复=否)

正则表达式:

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

替代

0$1$2

交换两个数字,填充以具有相同数量的前导零。

替换2(重复=否)

正则表达式:

(\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

添加进位(&0)以及的巨型查找表<c> <a> <b> <carry of a+b+c> <last digit of a+b+c>

替换4(重复=是)

正则表达式:

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

替代:

$4$10

继续取每个数字的最后一位数字,然后找出它们的(总和,进位)。将总和放在字符串的开头,然后替换进位。

替换5(重复=否)

正则表达式:

^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等,以节省一些字符。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.