Evaluating a string of simple mathematical expressions [closed]
Solution 1:
Assembler
427 bytes
Obfuscated, assembled with the excellent A86 into a .com executable:
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
EDIT: Unobfuscated source:
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
Solution 2:
Perl (no eval)
Number of characters: 167 106 (see below for the 106 character version)
Fully obfuscated function: (167 characters if you join these three lines into one)
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){}$_}
Clear/deobfuscated version:
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
$_
}
I had misread the rules initially, so I'd submitted a version with "eval". Here's a version without it.
The latest bit of insight came when I realized that the last octal digit in the character codes for +
, -
, /
, and *
is different, and that ord(undef)
is 0. This lets me set up the dispatch table @a
as an array, and just invoke the code at the location 7 & ord($3)
.
There's an obvious spot to shave off one more character - change q""
into ''
- but that would make it harder to cut-and-paste into the shell.
Even shorter
Number of characters: 124 106
Taking edits by ephemient into account, it's now down to 124 characters: (join the two lines into one)
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;$_}
Shorter still
Number of characters: 110 106
The ruby solution down below is pushing me further, though I can't reach its 104 characters:
sub e{($_)=@_;$n='( *-?[.\d]++ *)';
s:\($n\)|$n(.)$n:(($1,$2-$4,$4&&$2/$4,$2*$4,$2+$4)x9)[.8*ord$3]:e?e($_):$_}
I had to give in and use ''
. That ruby send
trick is really useful for this problem.
Squeezing water from a stone
Number of characters: 106
A small contortion to avoid the divide-by-zero check.
sub e{($_)=@_;$n='( *-?[.\d]++ *)';
s:\($n\)|$n(.)$n:($1,0,$2*$4,$2+$4,0,$2-$4)[7&ord$3]//$2/$4:e?e($_):$_}
Here's the test harness for this function:
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'
Solution 3:
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
This is a non-recursive version of The Wicked Flea's solution. Parenthesized sub-expressions are evaluated bottom-up instead of top-down.
Edit: Converting the 'while' to a conditional + tail recursion has saved a few characters, so it is no longer non-recursive (though the recursion is not semantically necessary.)
Edit: Borrowing Daniel Martin's idea of merging the regexps saves another 11 characters!
Edit: That recursion is even more useful than I first thought! x.to_f
can be rewritten as e(x)
, if x
happens to contain a single number.
Edit: Using 'or
' instead of '||
' allows a pair of parentheses to be dropped.
Long version:
# 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
Solution 4:
C (VS2005)
Number of Characters: 1360
Abuse of preprocessor and warnings for fun code layout (scroll down to see):
#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";}
Solution 5:
Visual Basic.NET
Number of characters: 9759
I'm more of a bowler myself.
NOTE: does not take nested parentheses into account. Also, untested, but I'm pretty sure it works.
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