Take an array of strings in RPN and evaluate it.
This is all about managing the stack. I started trying to code a Stack class, but dumped that for simply using append and slicing to manage the stack. Performance was okay: 73% runtime, 41% memory; probably good enough for a pass.
func evalRPN(tokens []string) int {
stack := []int{}
for _,token := range(tokens) {
switch token {
case "+":
op2 := stack[len(stack)-1]
op1 := stack[len(stack)-2]
stack = stack[:len(stack)-1]
stack[len(stack)-1] = op1 + op2
case "-":
op2 := stack[len(stack)-1]
op1 := stack[len(stack)-2]
stack = stack[:len(stack)-1]
stack[len(stack)-1] = op1 - op2
case "/":
op2 := stack[len(stack)-1]
op1 := stack[len(stack)-2]
stack = stack[:len(stack)-1]
stack[len(stack)-1] = op1 / op2
case "*":
op2 := stack[len(stack)-1]
op1 := stack[len(stack)-2]
stack = stack[:len(stack)-1]
stack[len(stack)-1] = op1 * op2
default:
i, err := strconv.Atoi(token)
if err != nil {
panic(fmt.Sprintf("unparseable token %s", token))
}
stack = append(stack, i)
}
}
return stack[len(stack)-1]
}
My guess is that if I used a real stack pointer to manage the stack, it’d be faster.
func evalRPN(tokens []string) int {
stack := make([]int, 10000)
sp := -1
for _,token := range(tokens) {
switch token {
case "+":
op2 := stack[sp]
op1 := stack[sp-1]
sp--
stack[sp] = op1 + op2
case "-":
op2 := stack[sp]
op1 := stack[sp-1]
sp--
stack[sp] = op1 - op2
case "/":
op2 := stack[sp]
op1 := stack[sp-1]
sp--
stack[sp] = op1 / op2
case "*":
op2 := stack[sp]
op1 := stack[sp-1]
sp--
stack[sp] = op1 * op2
default:
i, err := strconv.Atoi(token)
if err != nil {
panic(fmt.Sprintf("unparseable token %s", token))
}
sp++
stack[sp] = i
}
}
return stack[sp]
}
And no, it is not! Exact same runtime percentile, and used a little less memory (35th percentile).
Couple of quite elegant idea in the solutions; I really liked this one:
func evalRPN(tokens []string) int {
stack := make([]int, 10000)
sp := -1
ops := map[string]func(int,int) int{
"+": func(i, j int) int { return i + j},
"-": func(i, j int) int { return i - j},
"/": func(i, j int) int { return i / j},
"*": func(i, j int) int { return i * j},
}
for _,token := range(tokens) {
i, err := strconv.Atoi(token)
if err != nil {
// operator
stack[sp-1] = ops[token](stack[sp-1], stack[sp])
sp--
} else {
sp++
stack[sp] = i
}
}
return stack[sp]
}
We get rid of the switch altogether using the fact that the tokens are either valid numbers or operators, and we make the operators into anonymous functions in a map. If the token fails to convert to a number, then we look it up and call the appropriate function. This is far more fragile though, since any bad token will result in a “no such key” error on the ops
map…and we only gain 3% in the runtime. I’ll settle for the first one as good enough for a pass.
Leave a Reply
You must be logged in to post a comment.