评估一串简单数学表达式的字符串

76

挑战

这是我的编写的一个挑战(尽管我不会感到惊讶,如果它在网上以前已出现)。

编写一个函数,该函数接受一个单一参数,该参数是一个简单数学表达式的字符串表示形式,并将其评估为浮点值。 "简单表达式" 可以包括以下任何内容:正或负十进制数、+, -, *, /, (, )。表达式使用 (常规) 中缀表示法 。操作符应按照它们出现的顺序进行求值,即不像BODMAS那样,当然,必须正确观察括号。该函数应返回此形式的任何可能表达式的正确结果。但是,该函数不需要处理格式错误的表达式(即具有错误语法的表达式)。

表达式示例:

1 + 3 / -8                            = -0.5       (No BODMAS)
2*3*4*5+99                            = 219
4 * (9 - 4) / (2 * 6 - 2) + 8         = 10
1 + ((123 * 3 - 69) / 100)            = 4
2.45/8.5*9.27+(5*0.0023)              = 2.68...

规则

我预计会有某种形式的“作弊”/聪明才智,所以请允许我提前警告!通过作弊,我指的是在动态语言(例如JavaScript或PHP)中使用eval或等效函数,或者即时编译和执行代码。(然而,我的“禁止BODMAS”规定几乎已经保证了此项。)除此之外,没有任何限制。我预计会有几个正则表达式解决方案,但很高兴看到不仅仅是这样。

现在,我主要关注的是一个C#/.NET解决方案,但任何其他语言也完全可以接受(特别是函数式/mixed方法的F#和Python)。至少对于该语言,我尚未决定是否接受最短或最巧妙的解决方案作为答案,但我欢迎任何语言的任何形式的解决方案,除了上面刚才禁止的东西!

我的解决方案

我现在已经发布了我的C#解决方案here(403 chars)。更新:我的新解决方案使用一些可爱的正则表达式,在294 chars显著打败了旧的版本!我怀疑这将轻松被一些语法更轻的语言(特别是funcional/dynamic ones)所击败,并且已经被证明是正确的,但如果有人仍然可以在C#中击败这个问题,我会感到好奇。

更新

我已经看到了一些非常聪明的解决方案。感谢所有发布过解决方案的人。虽然我还没有测试任何一个,但我会相信人们并假设它们至少能够处理所有给定的示例。

只为记录,重入性(即线程安全)不是该函数的要求,尽管它是一个奖励。


格式

请按以下格式发布所有答案,以便易于比较:

语言

字符数:???

完全混淆的函数:

(code here)

清晰/半模糊的函数:

(code here)

关于算法/巧妙的快捷方式,有何说明。


2
你可能想让你的第一个例子等于0.125(移动小数点),而你的第二个例子左边应该是99(多了一个9)。 - John Y
我也将此设为社区维基(最初忘记这样做了),因为这似乎是这种问题最合适的方式。 - Noldorin
1
你应该添加一个缺少BODMAS的重要示例,例如“1 + 1 * 3 = 6”。 - Ben Blank
3
啊,我一直在想第一次关闭投票会是什么时候。向所有投票者说明:StackOverflow上已经有足够多的开放式代码高尔夫问题了。共识似乎是它们很好——主要只是一些乐趣。 - Noldorin
3
我倾向于同意这很好,特别是因为“维基”。 - Marc Gravell
显示剩余12条评论
43个回答

44

汇编语言

427 字节

代码经过混淆,使用优秀的A86 转换成可执行文件 .com 格式:

dd 0db9b1f89h, 081bee3h, 0e8af789h, 0d9080080h, 0bdac7674h, 013b40286h
dd 07400463ah, 0ccfe4508h, 08ce9f675h, 02fc8000h, 013b0057eh, 0feaac42ah
dd 0bedf75c9h, 0ba680081h, 04de801h, 04874f73bh, 04474103ch, 0e8e8b60fh
dd 08e8a003fh, 0e880290h, 0de0153h, 08b57e6ebh, 0d902a93eh, 046d891dh
dd 08906c783h, 05f02a93eh, 03cffcee8h, 057197510h, 02a93e8bh, 08b06ef83h
dd 05d9046dh, 02a93e89h, 03bc9d95fh, 0ac0174f7h, 074f73bc3h, 0f3cac24h
dd 0eed9c474h, 0197f0b3ch, 07cc4940fh, 074f73b09h, 0103cac09h, 0a3ce274h
dd 0e40a537eh, 0e0d90274h, 02a3bac3h, 021cd09b4h, 03e8b20cdh, 0ff8102a9h
dd 0ed7502abh, 0474103ch, 0e57d0b3ch, 0be02a3bfh, 014d903a3h, 0800344f6h
dd 02db00574h, 0d9e0d9aah, 0d9029f2eh, 0bb34dfc0h, 08a0009h, 01c75f0a8h
dd 020750fa8h, 0b0f3794bh, 021e9aa30h, 0de607400h, 08802990eh, 0de07df07h
dd 0c392ebc1h, 0e8c0008ah, 0aa300404h, 0f24008ah, 04baa3004h, 02eb0ee79h
dd 03005c6aah, 0c0d90ab1h, 0e9defcd9h, 02a116deh, 0e480e0dfh, 040fc8045h
dd 0ede1274h, 0c0d90299h, 015dffcd9h, 047300580h, 0de75c9feh, 0303d804fh
dd 03d80fa74h, 04f01752eh, 0240145c6h, 0dfff52e9h, 0d9029906h, 0f73b025fh
dd 03caca174h, 07fed740ah, 0df07889ah, 0277d807h, 047d9c1deh, 0990ede02h
dd 025fd902h, 03130e0ebh, 035343332h, 039383736h, 02f2b2d2eh, 02029282ah
dd 0e9000a09h, 07fc9f9c1h, 04500000fh, 0726f7272h
db 024h, 0abh, 02h

编辑: 未进行混淆的源代码:

        mov [bx],bx
        finit
        mov si,81h
        mov di,si
        mov cl,[80h]
        or cl,bl
        jz ret
    l1:
        lodsb
        mov bp,d1
        mov ah,19
    l2:
        cmp al,[bp]
        je l3
        inc bp
        dec ah
        jne l2
        jmp exit
    l3:
        cmp ah,2
        jle l4
        mov al,19
        sub al,ah
        stosb
    l4:
        dec cl
        jnz l1
        mov si,81h
        push done

    decode:
    l5:
        call l7
    l50:
        cmp si,di
        je ret
        cmp al,16
        je ret
        db 0fh, 0b6h, 0e8h ; movzx bp,al
        call l7
        mov cl,[bp+op-11]
        mov byte ptr [sm1],cl
        db 0deh
    sm1:db ?
        jmp l50

    open:
        push di
        mov di,word ptr [s]
        fstp dword ptr [di]
        mov [di+4],bp
        add di,6
        mov word ptr [s],di
        pop di
        call decode
        cmp al,16
        jne ret
        push di
        mov di,word ptr [s]
        sub di,6
        mov bp,[di+4]
        fld dword ptr [di]
        mov word ptr [s],di
        pop di
        fxch st(1)
        cmp si,di
        je ret
        lodsb
        ret



    l7: cmp si,di
        je exit
        lodsb
        cmp al,15
        je open
        fldz
        cmp al,11
        jg exit
        db 0fh, 94h, 0c4h ; sete ah 
        jl l10
    l9:
        cmp si,di
        je l12
        lodsb
        cmp al,16
        je ret
    l10:
        cmp al,10
        jle l12i

    l12:
        or ah,ah
        je l13
        fchs
    l13:
        ret

    exit:
        mov dx,offset res
        mov ah,9
        int 21h
        int 20h

    done:
        mov di,word ptr [s]
        cmp di,(offset s)+2
        jne exit
        cmp al,16
        je ok
        cmp al,11
        jge exit
    ok:
        mov di,res
        mov si,res+100h
        fst dword ptr [si]
        test byte ptr [si+3],80h
        jz pos
        mov al,'-'
        stosb
        fchs
    pos:
        fldcw word ptr [cw]
        fld st(0)
        fbstp [si]
        mov bx,9
    l1000:
        mov al,[si+bx]
        test al,0f0h
        jne startu
        test al,0fh
        jne startl
        dec bx
        jns l1000
        mov al,'0'
        stosb
        jmp frac

    l12i:
        je l11
        fimul word ptr [d3]
        mov [bx],al
        fild word ptr [bx]
        faddp
        jmp l9
        ret

    startu:
        mov al,[si+bx]
        shr al,4
        add al,'0'
        stosb
    startl:
        mov al,[si+bx]
        and al,0fh
        add al,'0'
        stosb
        dec bx
        jns startu

    frac:
        mov al,'.'
        stosb
        mov byte ptr [di],'0'
        mov cl,10
        fld st(0)
        frndint
    frac1:  
        fsubp st(1)
        ficom word ptr [zero]
        fstsw ax
        and ah,045h
        cmp ah,040h
        je finished
        fimul word ptr [d3]
        fld st(0)
        frndint
        fist word ptr [di]
        add byte ptr [di],'0'
        inc di
        dec cl
        jnz frac1

    finished:   
        dec di
        cmp byte ptr [di],'0'
        je finished
        cmp byte ptr [di],'.'
        jne f2
        dec di
    f2:
        mov byte ptr [di+1],'$'
    exit2:
        jmp exit


    l11:
        fild word ptr [d3]
        fstp dword ptr [bx+2]
    l111:
        cmp si,di
        je ret
        lodsb
        cmp al,10
        je exit2
        jg ret
        mov [bx],al
        fild word ptr [bx]
        fdiv dword ptr [bx+2]
        faddp
        fld dword ptr [bx+2]
        fimul word ptr [d3]
        fstp dword ptr [bx+2]
        jmp l111


    d1: db '0123456789.-+/*()', 32, 9
    d3: dw 10
    op: db 0e9h, 0c1h, 0f9h, 0c9h
    cw: dw 0f7fh
    zero: dw 0
    res:db 'Error$'
    s:  dw (offset s)+2

1
汇编语言 - 这才是真正的编程! - Andreas Rejbrand
我曾经看到过一个完整的俄罗斯方块游戏,只有64字节。 - BlueRaja - Danny Pflughoeft

36

Perl(无 eval)

字符数: 167 106 (下面有 106 个字符版本的内容)

完全混淆的函数:(如果将这三行连接成一行,共 167 个字符)

sub e{my$_="($_[0])";s/\s//g;$n=q"(-?\d++(\.\d+)?+)";
@a=(sub{$1},1,sub{$3*$6},sub{$3+$6},4,sub{$3-$6},6,sub{$3/$6});
while(s:\($n\)|(?<=\()$n(.)$n:$a[7&ord$5]():e){}$_}

精简/去混淆版:

sub e {
  my $_ = "($_[0])";
  s/\s//g;
  $n=q"(-?\d++(\.\d+)?+)"; # a regex for "number", including capturing groups
                           # q"foo" in perl means the same as 'foo'
                           # Note the use of ++ and ?+ to tell perl
                           # "no backtracking"

  @a=(sub{$1},             # 0 - no operator found
      1,                   # placeholder
      sub{$3*$6},          # 2 - ord('*') = 052
      sub{$3+$6},          # 3 - ord('+') = 053
      4,                   # placeholder
      sub{$3-$6},          # 5 - ord('-') = 055
      6,                   # placeholder
      sub{$3/$6});         # 7 - ord('/') = 057

  # The (?<=... bit means "find a NUM WHATEVER NUM sequence that happens
  # immediately after a left paren", without including the left
  # paren.  The while loop repeatedly replaces "(" NUM WHATEVER NUM with
  # "(" RESULT and "(" NUM ")" with NUM.  The while loop keeps going
  # so long as those replacements can be made.

  while(s:\($n\)|(?<=\()$n(.)$n:$a[7&ord$5]():e){}

  # A perl function returns the value of the last statement
  $_
}

我最初误读了规则,所以提交了一个包含"eval"的版本。这是一个没有它的版本。

当我意识到+-/*的字符编码中最后一个八进制数字不同,并且ord(undef)为0时,我得出了最新的想法。这让我将分派表@a设置为一个数组,并在位置7 & ord($3)处调用代码。

有一个明显的地方可以再减少一个字符,就是将q""改成'',但这将使它更难以剪切和粘贴到shell中。

更短

字符数:124 106

考虑到ephemient的编辑,现在已经缩减到124个字符:(将两行合并成一行)

sub e{$_=$_[0];s/\s//g;$n=q"(-?\d++(\.\d+)?+)";
1while s:\($n\)|$n(.)$n:($1,1,$3*$6,$3+$6,4,$3-$6,6,$6&&$3/$6)[7&ord$5]:e;$_}

更短的方法

字符数: 110 106

下面的 Ruby 解决方案进一步简化了我,但是我无法达到它的 104 个字符:

sub e{($_)=@_;$n='( *-?[.\d]++ *)';
s:\($n\)|$n(.)$n:(($1,$2-$4,$4&&$2/$4,$2*$4,$2+$4)x9)[.8*ord$3]:e?e($_):$_}

我不得不妥协并使用''。那个 Ruby 的 send 技巧对于这个问题非常有用。

从石头里榨水

字符数:106

为了避免除以零的检查而进行了一些小的扭曲。

sub e{($_)=@_;$n='( *-?[.\d]++ *)';
s:\($n\)|$n(.)$n:($1,0,$2*$4,$2+$4,0,$2-$4)[7&ord$3]//$2/$4:e?e($_):$_}

以下是该函数的测试框架:

perl -le 'sub e{($_)=@_;$n='\''( *-?[.\d]++ *)'\'';s:\($n\)|$n(.)$n:($1,0,$2*$4,$2+$4,0,$2-$4)[7&ord$3]//$2/$4:e?e($_):$_}' -e 'print e($_) for @ARGV' '1 + 3' '1 + ((123 * 3 - 69) / 100)' '4 * (9 - 4) / (2 * 6 - 2) + 8' '2*3*4*5+99' '2.45/8.5*9.27+(5*0.0023) ' '1 + 3 / -8'

3
Perl 的体积能力很惊人,我修改了我的答案以尽可能压缩 Ruby 实现的大小,结果仅用了 170 个字符。但是只用了 124 个字符?太神奇了! - Robert K
1
我没有注意到还没有人提到,但是这个解决方案需要 Perl 5.10。为了与 5.8 兼容,请使用 (-?(?>\d+(.\d+)?)),它比原来多两个字符。 - ephemient
7
@Epaga,不用担心,我看到你的错别字了:perl. is. awesome.(珍珠语言真的很棒)。 - Danny
1
将“$=$[0]”缩短1个字符变为“($_)=@_”。 - Chris Lutz
1
因为它无条件地执行算术运算(稍后选择正确的结果),所以它确实需要避免除以零。 - ephemient
显示剩余12条评论

29

Ruby

Number of characters: 103

N='( *-?[\d.]+ *)'
def e x
x.sub!(/\(#{N}\)|#{N}([^.\d])#{N}/){$1or(e$2).send$3,e($4)}?e(x):x.to_f
end

这是邪恶跳蚤方案的非递归版本。括号子表达式从下往上计算而不是从上往下。

编辑:将'while'转换为条件+尾递归可以节省一些字符,因此它不再是非递归的(虽然递归在语义上并不必要)。

编辑:借鉴Daniel Martin合并正则表达式的想法可以再节省11个字符!

编辑:那个递归比我最初想象的更有用!如果x包含一个单独的数字,则x.to_f可以重写为e(x)

编辑:使用'or'代替'||'可以省略一对括号。

长版:

# Decimal number, as a capturing group, for substitution
# in the main regexp below.
N='( *-?[\d.]+ *)'

# The evaluation function
def e(x)
  matched = x.sub!(/\(#{N}\)|#{N}([^\d.])#{N}/) do
    # Group 1 is a numeric literal in parentheses.  If this is present then
    # just return it.
    if $1
      $1
    # Otherwise, $3 is an operator symbol and $2 and $4 are the operands
    else
      # Recursively call e to parse the operands (we already know from the
      # regexp that they are numeric literals, and this is slightly shorter
      # than using :to_f)
      e($2).send($3, e($4))
      # We could have converted $3 to a symbol ($3.to_s) or converted the
      # result back to string form, but both are done automatically anyway
    end
  end
  if matched then
    # We did one reduction. Now recurse back and look for more.
    e(x)
  else
    # If the string doesn't look like a non-trivial expression, assume it is a
    # string representation of a real number and attempt to parse it
    x.to_f
  end
end

我差点以为这是新的领袖,直到我看到Perl的已经被编辑得更短了!不管怎样,干得好。 - Noldorin
1
去掉“e=readline.chomp;...;p e.to_f”并像其他解决方案一样使用“def q(e);...;e.to_f;end”可以节省10个字符。然而,它不能像问题中那样使q(“1 + 3 / -8”)== -0.5。 - ephemient
@ephemient 这是你发现的一个错误 - 它无法处理负数。 - finnw
1
我的代码中的gsub!('--','')是为了括号参数的工作方式,如果否定。 如果否定括号内部的结果为负,则语句外部的减号仍然存在:例如--7.0。 但是,支持它会花费我24个字符,仍然比你多19个字符。 我不知道我能否缩小它比我从你那里学到的技巧更多。 (但对于第二次尝试来说,我做得很好!) - Robert K
1
使用“send”函数真的接近违反“no eval”规则。但是将空格合并到数字正则表达式中的技巧很棒。使用这个技巧和另一个技巧,我的Perl解决方案缩短到了119个字符。 - Daniel Martin
Perl现在降到了107,但仍然是4 + 103... - ephemient

29

C (VS2005)

字符数:1360

滥用预处理器和警告,制造有趣的代码布局(向下滚动查看):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define b main
#define c(a) b(a,0)
#define d -1
#define e -2
#define g break
#define h case
#define hh h
#define hhh h
#define w(i) case i
#define i return
#define j switch
#define k float
#define l realloc
#define m sscanf
#define n int _
#define o char
#define t(u) #u
#define q(r) "%f" t(r)  "n"
#define s while
#define v default
#define ex exit
#define W printf
#define x fn()
#define y strcat
#define z strcpy
#define Z strlen

char*p    =0    ;k    *b    (n,o**    a){k*f
;j(_){    hh   e:     i*    p==40?    (++p,c
(d        ))  :(      f=        l(        0,
4)        ,m (p       ,q        (%        ),
f,&_),    p+=_        ,f       );        hh
d:f=c(    e);s        (1      ){        j(
    *p    ++ ){       hh     0:        hh
    41    :i  f;      hh    43        :*
f+=*c(    e)   ;g     ;h    45:*f=    *f-*c(
e);g;h    42    :*    f=    *f**c(    e);g;h

47:*f      /=*c      (e);     g;   v:    c(0);}
}w(1):    if(p&&    printf    (q  ((     "\\"))
,*  c(    d)  ))    g;  hh    0: ex      (W
(x  ))    ;v  :p    =(        p?y:       z)(l(p
,Z(1[     a]  )+    (p        ?Z(p           )+
1:1))     ,1  [a    ])  ;b    (_ -1          ,a
+1  );    g;  }i    0;};fn    ()  {n     =42,p=
43  ;i     "Er"      "ro"     t(   r)    "\n";}

25

Haskell

字符数:182

没有尝试过聪明,只是一些压缩:4行,312字节。

(Haskell是一种函数式编程语言,注重于表达式和函数的定义。它被用于许多领域,包括科学计算、金融、人工智能等等。)

import Data.Char;import Text.ParserCombinators.Parsec
q=either(error.show)id.runParser t id"".filter(' '/=);t=do
s<-getState;a<-fmap read(many1$oneOf".-"<|>digit)<|>between(char '('>>setState id)(char ')'>>setState s)t
option(s a)$choice(zipWith(\c o->char c>>return(o$s a))"+-*/"[(+),(-),(*),(/)])>>=setState>>t

现在,真正进入高尔夫精神,3行182字节:

q=snd.(`e`id).filter(' '/=)
e s c|[(f,h)]<-readsPrec 0 s=g h(c f);e('(':s)c=g h(c f)where(')':h,f)=e s id
g('+':h)=e h.(+);g('-':h)=e h.(-);g('*':h)=e h.(*);g('/':h)=e h.(/);g h=(,)h

分解的:

-- Strip spaces from the input, evaluate with empty accumulator,
-- and output the second field of the result.
q :: String -> Double
q = snd . flip eval id . filter (not . isSpace)

-- eval takes a string and an accumulator, and returns
-- the final value and what’s left unused from the string.
eval :: (Fractional a, Read a) => String -> (a -> a) -> (String, a)

-- If the beginning of the string parses as a number, add it to the accumulator,
-- then try to read an operator and further.
eval str accum | [(num, rest)] <- readsPrec 0 str = oper rest (accum num)

-- If the string starts parentheses, evaluate the inside with a fresh
-- accumulator, and continue after the closing paren.
eval ('(':str) accum = oper rest (accum num) where (')':rest, num) = eval str id

-- oper takes a string and current value, and tries to read an operator
-- to apply to the value.  If there is none, it’s okay.
oper :: (Fractional a, Read a) => String -> a -> (String, a)

-- Handle operations by giving eval a pre-seeded accumulator.
oper ('+':str) num = eval str (num +)
oper ('-':str) num = eval str (num -)
oper ('*':str) num = eval str (num *)
oper ('/':str) num = eval str (num /)

-- If there’s no operation parsable, just return.
oper str num = (str, num)

我怀疑在凌晨3点,可能还能把它压到225以下,但这已经是最远的距离了。 - ephemient
这似乎是一个相当优雅的函数式解决方案。(如果没有注释,我肯定不会理解,所以感谢那些注释。)此外,你现在只比Dave的Python解决方案略领先,所以你似乎处于领先地位!我很好奇F#解决方案是否能够匹配甚至超越这个解决方案。 - Noldorin
有趣的是,对我来说 Parsec 的解决方案(parser combinators = generalized regex + more)即使尝试进行了最小化,也无法接近手写的解析。我不认为 F# 的语法可以像 Haskell 一样简洁,但我也欢迎一些竞争 :) - ephemient
@ephemient:我尝试了在Python中使用正则表达式和解析器模块。很快就将字符数降到了300以下,但是没有看到获得竞争优势的机会。问题在于导入和函数调用占用了太多空间。这对大多数语言都是适用的(Perl除外)。顺便说一句,你不必自己解析就可以将字符数显著降低到300以下,正如我的解决方案所示。 - stephan
2
我认为你对字符计数过于严格了。问题要求一个String->Double函数,因此你应该通过将“main=interact$show.”替换为“q=”来计算字符,这样可以增加17个字符,使你的计数达到209。 - Daniel Martin

25

Visual Basic.NET

字符数:9759

我更喜欢打保龄球。

注意:不考虑嵌套括号。此外,未经测试,但我相信它可以工作。

Imports Microsoft.VisualBasic
Imports System.Text
Imports System.Collections.Generic
Public Class Main
Public Shared Function DoArithmaticFunctionFromStringInput(ByVal MathematicalString As String) As Double
    Dim numberList As New List(Of Number)
    Dim operationsList As New List(Of IOperatable)
    Dim currentNumber As New Number
    Dim currentParentheticalStatement As New Parenthetical
    Dim isInParentheticalMode As Boolean = False
    Dim allCharactersInString() As Char = MathematicalString.ToCharArray
    For Each mathChar In allCharactersInString
        If mathChar = Number.ZERO_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.ONE_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.TWO_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.THREE_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.FOUR_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.FIVE_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.SIX_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.SEVEN_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.EIGHT_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.NINE_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Number.DECIMAL_POINT_STRING_REPRESENTATION Then
            currentNumber.UpdateNumber(mathChar)
        ElseIf mathChar = Addition.ADDITION_STRING_REPRESENTATION Then
            Dim addition As New Addition

            If Not isInParentheticalMode Then
                operationsList.Add(addition)
                numberList.Add(currentNumber)
            Else
                currentParentheticalStatement.AllNumbers.Add(currentNumber)
                currentParentheticalStatement.AllOperators.Add(addition)
            End If

            currentNumber = New Number
        ElseIf mathChar = Number.NEGATIVE_NUMBER_STRING_REPRESENTATION Then
            If currentNumber.StringOfNumbers.Length > 0 Then
                currentNumber.UpdateNumber(mathChar)

                Dim subtraction As New Addition
                If Not isInParentheticalMode Then
                    operationsList.Add(subtraction)
                    numberList.Add(currentNumber)
                Else
                    currentParentheticalStatement.AllNumbers.Add(currentNumber)
                    currentParentheticalStatement.AllOperators.Add(subtraction)
                End If

                currentNumber = New Number
            Else
                currentNumber.UpdateNumber(mathChar)
            End If
        ElseIf mathChar = Multiplication.MULTIPLICATION_STRING_REPRESENTATION Then
            Dim multiplication As New Multiplication

            If Not isInParentheticalMode Then
                operationsList.Add(multiplication)
                numberList.Add(currentNumber)
            Else
                currentParentheticalStatement.AllNumbers.Add(currentNumber)
                currentParentheticalStatement.AllOperators.Add(multiplication)
            End If
            currentNumber = New Number
        ElseIf mathChar = Division.DIVISION_STRING_REPRESENTATION Then
            Dim division As New Division

            If Not isInParentheticalMode Then
                operationsList.Add(division)
                numberList.Add(currentNumber)
            Else
                currentParentheticalStatement.AllNumbers.Add(currentNumber)
                currentParentheticalStatement.AllOperators.Add(division)
            End If
            currentNumber = New Number
        ElseIf mathChar = Parenthetical.LEFT_PARENTHESIS_STRING_REPRESENTATION Then
            isInParentheticalMode = True
        ElseIf mathChar = Parenthetical.RIGHT_PARENTHESIS_STRING_REPRESENTATION Then
            currentNumber = currentParentheticalStatement.EvaluateParentheticalStatement
            numberList.Add(currentNumber)
            isInParentheticalMode = False
        End If
    Next

    Dim result As Double = 0
    Dim operationIndex As Integer = 0
    For Each numberOnWhichToPerformOperations As Number In numberList
        result = operationsList(operationIndex).PerformOperation(result, numberOnWhichToPerformOperations)
        operationIndex = operationIndex + 1
    Next

    Return result

End Function
Public Class Number
    Public Const DECIMAL_POINT_STRING_REPRESENTATION As Char = "."
    Public Const NEGATIVE_NUMBER_STRING_REPRESENTATION As Char = "-"
    Public Const ZERO_STRING_REPRESENTATION As Char = "0"
    Public Const ONE_STRING_REPRESENTATION As Char = "1"
    Public Const TWO_STRING_REPRESENTATION As Char = "2"
    Public Const THREE_STRING_REPRESENTATION As Char = "3"
    Public Const FOUR_STRING_REPRESENTATION As Char = "4"
    Public Const FIVE_STRING_REPRESENTATION As Char = "5"
    Public Const SIX_STRING_REPRESENTATION As Char = "6"
    Public Const SEVEN_STRING_REPRESENTATION As Char = "7"
    Public Const EIGHT_STRING_REPRESENTATION As Char = "8"
    Public Const NINE_STRING_REPRESENTATION As Char = "9"

    Private _isNegative As Boolean
    Public ReadOnly Property IsNegative() As Boolean
        Get
            Return _isNegative
        End Get
    End Property
    Public ReadOnly Property ActualNumber() As Double
        Get
            Dim result As String = ""
            If HasDecimal Then
                If DecimalIndex = StringOfNumbers.Length - 1 Then
                    result = StringOfNumbers.ToString
                Else
                    result = StringOfNumbers.Insert(DecimalIndex, DECIMAL_POINT_STRING_REPRESENTATION).ToString
                End If
            Else
                result = StringOfNumbers.ToString
            End If
            If IsNegative Then
                result = NEGATIVE_NUMBER_STRING_REPRESENTATION & result
            End If
            Return CType(result, Double)
        End Get
    End Property
    Private _hasDecimal As Boolean
    Public ReadOnly Property HasDecimal() As Boolean
        Get
            Return _hasDecimal
        End Get
    End Property
    Private _decimalIndex As Integer
    Public ReadOnly Property DecimalIndex() As Integer
        Get
            Return _decimalIndex
        End Get
    End Property
    Private _stringOfNumbers As New StringBuilder
    Public ReadOnly Property StringOfNumbers() As StringBuilder
        Get
            Return _stringOfNumbers
        End Get
    End Property
    Public Sub UpdateNumber(ByVal theDigitToAppend As Char)
        If IsNumeric(theDigitToAppend) Then
            Me._stringOfNumbers.Append(theDigitToAppend)
        ElseIf theDigitToAppend = DECIMAL_POINT_STRING_REPRESENTATION Then
            Me._hasDecimal = True
            Me._decimalIndex = Me._stringOfNumbers.Length
        ElseIf theDigitToAppend = NEGATIVE_NUMBER_STRING_REPRESENTATION Then
            Me._isNegative = Not Me._isNegative
        End If
    End Sub
    Public Shared Function ConvertDoubleToNumber(ByVal numberThatIsADouble As Double) As Number
        Dim numberResult As New Number
        For Each character As Char In numberThatIsADouble.ToString.ToCharArray
            numberResult.UpdateNumber(character)
        Next
        Return numberResult
    End Function
End Class
Public MustInherit Class Operation
    Protected _firstnumber As New Number
    Protected _secondnumber As New Number
    Public Property FirstNumber() As Number
        Get
            Return _firstnumber
        End Get
        Set(ByVal value As Number)
            _firstnumber = value
        End Set
    End Property
    Public Property SecondNumber() As Number
        Get
            Return _secondnumber
        End Get
        Set(ByVal value As Number)
            _secondnumber = value
        End Set
    End Property
End Class
Public Interface IOperatable
    Function PerformOperation(ByVal number1 As Double, ByVal number2 As Number) As Double
End Interface
Public Class Addition
    Inherits Operation
    Implements IOperatable
    Public Const ADDITION_STRING_REPRESENTATION As String = "+"
    Public Sub New()

    End Sub
    Public Function PerformOperation(ByVal number1 As Double, ByVal number2 As Number) As Double Implements IOperatable.PerformOperation
        Dim result As Double = 0
        result = number1 + number2.ActualNumber
        Return result
    End Function
End Class
Public Class Multiplication
    Inherits Operation
    Implements IOperatable
    Public Const MULTIPLICATION_STRING_REPRESENTATION As String = "*"
    Public Sub New()

    End Sub
    Public Function PerformOperation(ByVal number1 As Double, ByVal number2 As Number) As Double Implements IOperatable.PerformOperation
        Dim result As Double = 0
        result = number1 * number2.ActualNumber
        Return result
    End Function
End Class
Public Class Division
    Inherits Operation
    Implements IOperatable
    Public Const DIVISION_STRING_REPRESENTATION As String = "/"
    Public Const DIVIDE_BY_ZERO_ERROR_MESSAGE As String = "I took a lot of time to write this program. Please don't be a child and try to defile it by dividing by zero. Nobody thinks you are funny."
    Public Sub New()

    End Sub
    Public Function PerformOperation(ByVal number1 As Double, ByVal number2 As Number) As Double Implements IOperatable.PerformOperation
        If Not number2.ActualNumber = 0 Then
            Dim result As Double = 0
            result = number1 / number2.ActualNumber
            Return result
        Else
            Dim divideByZeroException As New Exception(DIVIDE_BY_ZERO_ERROR_MESSAGE)
            Throw divideByZeroException
        End If
    End Function
End Class
Public Class Parenthetical
    Public Const LEFT_PARENTHESIS_STRING_REPRESENTATION As String = "("
    Public Const RIGHT_PARENTHESIS_STRING_REPRESENTATION As String = ")"
    Private _allNumbers As New List(Of Number)
    Public Property AllNumbers() As List(Of Number)
        Get
            Return _allNumbers
        End Get
        Set(ByVal value As List(Of Number))
            _allNumbers = value
        End Set
    End Property
    Private _allOperators As New List(Of IOperatable)
    Public Property AllOperators() As List(Of IOperatable)
        Get
            Return _allOperators
        End Get
        Set(ByVal value As List(Of IOperatable))
            _allOperators = value
        End Set
    End Property
    Public Sub New()

    End Sub
    Public Function EvaluateParentheticalStatement() As Number
        Dim result As Double = 0
        Dim operationIndex As Integer = 0
        For Each numberOnWhichToPerformOperations As Number In AllNumbers
            result = AllOperators(operationIndex).PerformOperation(result, numberOnWhichToPerformOperations)
            operationIndex = operationIndex + 1
        Next

        Dim numberToReturn As New Number
        numberToReturn = Number.ConvertDoubleToNumber(result)
        Return numberToReturn
    End Function
End Class
End Class

2
如果不是太晚了,我也可能达到了10k个字符 :) - Jason
你知道越少的字符越好吗?这样他们就永远不会认为vb.net是个好东西了。 - Ikke
11
“@ikke - it was supposed to be as FEW characters as possible? oh dear... someone seems to have missed the point” 的意思是:“@ikke - 原本应该尽可能少地使用字符吗?哦,亲爱的...好像有人误解了重点。” - Jason
3
ZERO_STRING_REPRESENTATION看起来像是属于thedailywtf网站上的内容。 - erikkallen
@lkke - 我想那就是重点。请看finnw的评论。 - phkahler
1
+1 这比 Stack Overflow 上的任何其他答案都更让我笑。真的 - R.. GitHub STOP HELPING ICE

23

Python

字符数:237

完全混淆的函数:

from operator import*
def e(s,l=[]):
 if s:l+=list(s.replace(' ','')+')')
 a=0;o=add;d=dict(zip(')*+-/',(0,mul,o,sub,div)));p=l.pop
 while o:
  c=p(0)
  if c=='(':c=e(0)
  while l[0]not in d:c+=p(0)
  a=o(a,float(c));o=d[p(0)]
 return a

清晰/半模糊的函数:

import operator

def calc(source, stack=[]):
    if source:
        stack += list(source.replace(' ', '') + ')')

    answer = 0

    ops = {
        ')': 0,
        '*': operator.mul,
        '+': operator.add,
        '-': operator.sub,
        '/': operator.div,
    }

    op = operator.add
    while op:
        cur = stack.pop(0)

        if cur == '(':
            cur = calc(0)

        while stack[0] not in ops:
            cur += stack.pop(0)

        answer = op(answer, float(cur))
        op = ops[stack.pop(0)]

    return answer

注意:当前版本无法处理像-(1.0)这样的输入,尽管它可以正确处理负字面量。从规范中并不清楚是否需要处理这种情况。 - Dave
通过将l放入e的参数列表中,可以免费使其成为非全局变量。但是仍然不具备线程安全性。 - Dave
非常狡猾。这真的值得解释的努力。 :) - Nick Johnson
@Dave:我的程序在-(1.0)上也失败了,所以别担心!我会澄清问题。不管怎样,这个解决方案似乎非常聪明 - 我仍然在努力弄清楚它是如何工作的(因为我并不完全了解Python)。如果你能添加一个简要说明,那将不胜感激。 - Noldorin

20

Fortran 77(使用gfortran方言,现在支持g77)

字符数:2059

混淆版本:

      function e(c)
      character*99 c
      character b
      real f(24)                
      integer i(24)             
      nf=0                      
      ni=0                      
 20   nf=kf(0.0,nf,f)
      ni=ki(43,ni,i)         
 30   if (isp(c).eq.1) goto 20
      h=fr(c)
 31   g=fp(nf,f)
      j=ip(ni,i)
      select case(j)
      case (40) 
         goto 20
      case (42)                 
         d=g*h
      case (43)                 
         d=g+h
      case (45)                 
         d=g-h
      case (47)                 
         d=g/h
      end select
 50   nf=kf(d,nf,f)
 60   j=nop(c)
      goto (20, 70, 75, 75, 60, 75, 60, 75) (j-39)
 65   e=fp(nf,f)
      return
 70   h=fp(nf,f)              
      goto 31
 75   ni=ki(j,ni,i)
      goto 30
      end
      function kf(v,n,f)
      real f(24)
      kf=n+1
      f(n+1)=v
      return
      end
      function ki(j,n,i)
      integer i(24)
      ki=n+1
      i(n+1)=j
      return
      end
      function fp(n,f)
      real f(24)
      fp=f(n)
      n=n-1
      return
      end
      function ip(n,i)
      integer i(24)
      ip=i(n)
      n=n-1
      return
      end
      function nop(s)
      character*99 s
      l=1
      do while(s(l:l).eq." ".and.l.lt.99)
         l=l+1
      enddo
      nop=ichar(s(l:l))
      s(l:l)=" "
      return
      end
      function isp(s)
      character*99 s
      isp=0
      l=1
      do while(s(l:l).eq." ".and.l.lt.99)
         l=l+1
      enddo
      isp=41-ichar(s(l:l))
      if (isp.eq.1) s(l:l)=" "
      return
      end
      function fr(s)
      character*99 s
      m=1                      
      n=1                      
      i=1
      do while(i.le.99)
         j=ichar(s(i:i))
         if (j.eq.32) goto 90   
         if (j.ge.48.and.j.lt.58) goto 89
         if (j.eq.43.or.j.eq.45) goto (89,80) m
         if (j.eq.46) goto (83,80) n
 80      exit
 83      n=2
 89      m=2
 90      i=i+1
      enddo
      read(s(1:i-1),*) fr
      do 91 j=1,i-1
         s(j:j)=" "
 91   continue
      return 
      end

清晰版:(包含脚手架的3340个字符)

      program infixeval
      character*99 c
      do while (.true.)
         do 10 i=1,99
            c(i:i)=" "
 10      continue
         read(*,"(A99)") c
         f=e(c)
         write(*,*)f
      enddo
      end

      function e(c)
      character*99 c
      character b
      real f(24)                ! value stack
      integer i(24)             ! operator stack
      nf=0                      ! number of items on the value stack
      ni=0                      ! number of items on the operator stack
 20   nf=pushf(0.0,nf,f)
      ni=pushi(43,ni,i)         ! ichar(+) = 43
D     write (*,*) "'",c,"'"
 30   if (isp(c).eq.1) goto 20
      h=fr(c)
D     write (*,*) "'",c,"'"
 31   g=fpop(nf,f)
      j=ipop(ni,i)
D     write(*,*) "Opperate ",g," ",char(j)," ",h
      select case(j)
      case (40) 
         goto 20
      case (42)                 ! "*" 
         d=g*h
      case (43)                 ! "+"
         d=g+h
      case (45)                 ! "-"
         d=g-h
      case (47)                 ! "*"
         d=g/h
      end select
 50   nf=pushf(d,nf,f)
 60   j=nop(c)
D     write(*,*) "Got op: ", char(j)
      goto (20, 70, 75, 75, 60, 75, 60, 75) (j-39)
 65   e=fpop(nf,f)
      return
 70   h=fpop(nf,f)              ! Encountered a "("
      goto 31
 75   ni=pushi(j,ni,i)
      goto 30
      end

c     push onto a real stack
c     OB as kf
      function pushf(v,n,f)
      real f(24)
      pushf=n+1
      f(n+1)=v
D     write(*,*) "Push ", v
      return
      end

c     push onto a integer stack
c     OB as ki
      function pushi(j,n,i)
      integer i(24)
      pushi=n+1
      i(n+1)=j
D     write(*,*) "Push ", char(j)
      return
      end

c     pop from real stack
c     OB as fp
      function fpop(n,f)
      real f(24)
      fpop=f(n)
      n=n-1
D      write (*,*) "Pop ", fpop
      return
      end

c     pop from integer stack
c     OB as ip
      function ipop(n,i)
      integer i(24)
      ipop=i(n)
      n=n-1
D      write (*,*) "Pop ", char(ipop)
      return
      end

c     Next OPerator: returns the next nonws character, and removes it
c     from the string
      function nop(s)
      character*99 s
      l=1
      do while(s(l:l).eq." ".and.l.lt.99)
         l=l+1
      enddo
      nop=ichar(s(l:l))
      s(l:l)=" "
      return
      end

c     IS an open Paren: return 1 if the next non-ws character is "("
c     (also overwrite it with a space. Otherwise return not 1
      function isp(s)
      character*99 s
      isp=0
      l=1
      do while(s(l:l).eq." ".and.l.lt.99)
         l=l+1
      enddo
      isp=41-ichar(s(l:l))
      if (isp.eq.1) s(l:l)=" "
      return
      end

c     Float Read: return the next real number in the string and removes the
c     character
      function fr(s)
      character*99 s
      m=1                      ! No sign (Minus or plus) so far
      n=1                      ! No decimal so far
      i=1
      do while(i.le.99)
         j=ichar(s(i:i))
         if (j.eq.32) goto 90   ! skip spaces
         if (j.ge.48.and.j.lt.58) goto 89
         if (j.eq.43.or.j.eq.45) goto (89,80) m
         if (j.eq.46) goto (83,80) n
c     not part of a number
 80      exit
 83      n=2
 89      m=2
 90      i=i+1
      enddo
      read(s(1:i-1),*) fr
      do 91 j=1,i-1
         s(j:j)=" "
 91   continue
      return 
      end

:这个修改版本比我的第一次尝试更加邪恶。算法相同,但现在使用了一些可怕的goto代码。我放弃了协程,但现在使用了几种计算分支的方法。所有错误检查和报告都已经被移除了,但这个版本将会悄悄地从输入中的某些意外字符类别中恢复过来。这个版本也可以与g77编译。

主要的限制仍然是Fortran的严格格式、长而普遍的关键字以及简单的基元。


14
天哪,伙计!你一定是今天很无聊吧。;) - gnovice
2
呵呵,我从来没有想到会有Fortran的解决方案!我想我们可以得出结论,这种语言不太适合进行代码高尔夫吗?不过还是点赞了,因为你付出了巨大的努力,并使用了一种过时的语言。 :) - Noldorin
我发现在Fortran中进行这种琐碎的字节操作很啰嗦、笨拙,但实际上并不难。另一方面,编写非结构化代码并使用那些计算出的分支感觉有点奇怪。 - dmckee --- ex-moderator kitten
做得很好,但是一个2000多字符的Fortran版本怎么比我的短小的Ruby1.9版本获得更多的投票呢?哈哈 - Daniel Huckstep
@darkhelmet:我不知道。我只是出于兴趣做了这件事,本来只期望得到一两个投票以示支持和反叛。我对这个可怕的东西感到非常自豪,但这也太离谱了... - dmckee --- ex-moderator kitten

17

C99

字符数:239(但请参见下面的209

压缩函数:

#define S while(*e==32)++e
#define F float
F strtof();char*e;F v();F g(){S;return*e++-40?strtof(e-1,&e):v();}F v(){F b,a=g();for(;;){S;F o=*e++;if(!o|o==41)return a;b=g();a=o==43?a+b:o==45?a-b:o==42?a*b:a/b;}}F f(char*x){e=x;return v();}

解压缩函数:

float strtof();

char* e;
float v();

float g() {
    while (*e == ' ') ++e;
    return *e++ != '(' ? strtof(e-1, &e) : v();
}

float v() {
    float b, a = g();
    for (;;) {
        while (*e == ' ') ++e;
        float op = *e++;
        if (op == 0 || op == ')') return a;
        b = g();
        a = op == '+' ? a + b : op == '-' ? a - b : op == '*' ? a * b : a / b;
    }
}

float eval(char* x) {
    e = x;
    return v();
}

函数不可重入。

Chris Lutz的编辑说明:我不想破坏别人的代码,但这里有一个209个字符的版本:

#define S for(;*e==32;e++)
#define X (*e++-40?strtof(e-1,&e):v())
float strtof();char*e;float v(){float o,a=X;for(;;){S;o=*e++;if(!o|o==41)return a;S;a=o-43?o-45?o-42?a/X:a*X:a-X:a+X;}}
#define f(x) (e=x,v())

易读(虽然实际上不是非常易读,但已经解压缩):

float strtof();
char *e;
float v() {
    float o, a = *e++ != '(' ? strtof(e - 1, &e) : v();
    for(;;) {
        for(; *e == ' '; e++);
        o = *e++;
        if(o == 0 || o==')') return a;
        for(; *e == ' '; e++);
        // I have no idea how to properly indent nested conditionals
        // and this is far too long to fit on one line.
        a = o != '+' ?
          o != '-' ?
            o != '*' ?
              a / (*e++ != '(' ? strtof(e - 1, &e) : v()) :
              a * (*e++ != '(' ? strtof(e - 1, &e) : v()) :
            a - (*e++ != '(' ? strtof(e - 1, &e) : v()) :
          a + (*e++ != '(' ? strtof(e - 1, &e) : v());
      }
}
#define f(x) (e = x, v())

是的,f() 是一个宏而不是函数,但它可以工作。可读版本重新编写了一些逻辑但未重新排序(例如 o != '+' 而不是 o - '+'),但否则只是另一个缩进和预处理的版本。我一直在尝试将 if(!o|o==41)return a;部分简化为for()循环,但它从未变得更短。我仍然相信它可以做到,但我已经完成了高尔夫游戏。如果我继续处理这个问题,那么将使用不能被命名的语言


谢谢你的建议,Adam。使用那个以及其他一些(丑陋的)技巧,我将它进一步缩小了一点。 - Ferruccio
糟糕!我不再需要W宏了! - Ferruccio
通过将“op”更改为浮点数而不是字符,我减少了一些字符。这让我感到有点不舒服,但它起作用了。 - Ferruccio
@Nosredna:我也考虑过这个,但实际上它会增加几个字符的大小。再多按一次回车就可以了。 - Ferruccio
209,我已经发布并解放了自己。我应该编辑这篇帖子并添加我的解决方案还是创建一个新的帖子? - Chris Lutz
显示剩余19条评论

13

通用语言(Common Lisp)

(SBCL)
字符数量:251

(defun g(e)(if(numberp e)e(let((m (g (pop e)))(o(loop for x in e by #'cddr collect x))(n(loop for x in (cdr e)by #'cddr collect (g x))))(mapcar(lambda(x y)(setf m(apply x(list m y))))o n)m)))(defun w(e)(g(read-from-string(concatenate'string"("e")"))))

Proper version (387 chars):

(defun wrapper (exp) (golf-eval (read-from-string (concatenate 'string "(" exp ")"))))

(defun golf-eval (exp)
 (if (numberp exp)
     exp
   (let ((mem (golf-eval (pop exp)))
     (op-list (loop for x in exp by #'cddr collect x))
     (num-list (loop for x in (cdr exp) by #'cddr collect (golf-eval x))))
    (mapcar (lambda (x y) (setf mem (apply x (list mem y)))) op-list num-list)
    mem)))

输入是表单w(),它接受一个字符串参数。它使用的技巧是数字/操作数在模式N O N O N ...中,并递归地评估所有操作数,因此得到嵌套非常便宜。 ;)


聪明的解决方案。尽管如此,我不太确定它是否完全有效,因为规范要求函数接受一个字符串对象。 - Noldorin
抱歉,已修复! - user110763
没问题。没想到转换如此简单。不过还是个好解决方案! - Noldorin
哇,太漂亮了。 :) - Emil H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接