3
\$\begingroup\$

I'm pretty new to Go, and I do not know the best or idiomatic ways to do most of the stuff, so I'd appreciate feedback on how to make my code better, faster or more idiomatic.

My program is a set of methods/functions to parse an infix notation expression then convert it to reverse polish notation using Dijkstra's Shunting-Yard algorithm and then finally solve it, so far it supports basic operations "+ - / * ^", nested parentheses & basic trig functions.

File 1:

package gocalc

import (
    "strings"
    "unicode"
)

type Token struct {
    Type  TokenType
    Value string
}

type TokenType int

const (
    Number TokenType = iota
    Operator
    LParen
    RParen
    fnction
)

// fnction Parse converts an expression into a stack of tokens
func Parse(input string) (tokens Stack) {
    var cont string
    var fn bool

    for _, v := range input {
        val := string(v)
        if unicode.IsDigit(v) || val == "." {
            cont += val
        } else if unicode.IsLetter(v) || strings.Contains("[]", val) {
            if val == "]" {
                cont += val
                fn = false
            } else {
                cont += val
                fn = true
            }
        } else if strings.Contains("+-*/^", val) {
            if fn {
                cont += val
            } else {
                if cont != "" {
                    if unicode.IsLetter(rune(cont[0])) {
                        tokens.Push(Token{fnction, cont})
                    } else {
                        tokens.Push(Token{Number, cont})
                    }
                    cont = ""
                }
                tokens.Push(Token{Operator, val})
            }
        } else if strings.Contains("()", val) {
            if fn {
                cont += val
            } else {
                if cont != "" {
                    if unicode.IsLetter(rune(cont[0])) {
                        tokens.Push(Token{fnction, cont})
                    } else {
                        tokens.Push(Token{Number, cont})
                    }
                    cont = ""
                }
                if val == "(" {
                    tokens.Push(Token{LParen, val})
                } else {
                    tokens.Push(Token{RParen, val})
                }
            }

        }
    }
    if cont != "" {
        if unicode.IsLetter(rune(cont[0])) {
            tokens.Push(Token{fnction, cont})
        } else {
            tokens.Push(Token{Number, cont})
        }
    }
    return tokens
}

File 2:

package gocalc

import (
    "math"
    "strconv"
    "strings"
)

var oprData = map[string]struct {
    prec int
    asoc bool // true = right // false = left
    fx   func(x, y float64) float64
}{
    "^": {4, true, func(x, y float64) float64 { return math.Pow(x, y) }},
    "*": {3, false, func(x, y float64) float64 { return x * y }},
    "/": {3, false, func(x, y float64) float64 { return x / y }},
    "+": {2, false, func(x, y float64) float64 { return x + y }},
    "-": {2, false, func(x, y float64) float64 { return x - y }},
}

var fnData = map[string]func(x float64) float64{
    "Cos":  func(x float64) float64 { return math.Cos(x) },
    "Sin":  func(x float64) float64 { return math.Sin(x) },
    "Tan":  func(x float64) float64 { return math.Tan(x) },
    "Acos": func(x float64) float64 { return math.Acos(x) },
    "Asin": func(x float64) float64 { return math.Asin(x) },
    "Atan": func(x float64) float64 { return math.Atan(x) },
}

// Function ToRPN converts a stack of tokens from infix notation to Reverse Polish Notation
func ToRPN(tokens Stack) Stack {
    output := Stack{}
    ops := Stack{}
    for _, v := range tokens.Values {
        switch {
        case v.Type == Operator:
            for !ops.IsEmpty() {
                val := v.Value
                top := ops.Peek().Value
                if (oprData[val].prec <= oprData[top].prec && oprData[val].asoc == false) ||
                    (oprData[val].prec < oprData[top].prec && oprData[val].asoc == true) {
                    output.Push(ops.Pop())
                    continue
                }
                break
            }
            ops.Push(v)
        case v.Type == LParen:
            ops.Push(v)
        case v.Type == RParen:
            for i := ops.Length() - 1; i >= 0; i-- {
                if ops.Values[i].Type != LParen {
                    output.Push(ops.Pop())
                    continue
                } else {
                    ops.Pop()
                    break
                }
            }
        default:
            output.Push(v)
        }
    }
    if !ops.IsEmpty() {
        for i := ops.Length() - 1; i >= 0; i-- {
            output.Push(ops.Pop())
        }
    }
    return output
}

// Function SolveRPN returns the result of the Reverse Polish Notation expression
func SolveRPN(tokens Stack) float64 {
    stack := Stack{}
    for _, v := range tokens.Values {
        if v.Type == Number {
            stack.Push(v)
        } else if v.Type == Function {
            stack.Push(Token{Number, SolveFunction(v.Value)})
        } else if v.Type == Operator {
            f := oprData[v.Value].fx
            var x, y float64

            y, _ = strconv.ParseFloat(stack.Pop().Value, 64)
            x, _ = strconv.ParseFloat(stack.Pop().Value, 64)

            result := f(x, y)
            stack.Push(Token{Number, strconv.FormatFloat(result, 'f', 12, 64)})
        }
    }
    out, _ := strconv.ParseFloat(stack.Values[0].Value, 64)
    return out
}

func SolveFunction(input string) string {
    var fArg float64
    fType := strings.TrimRight(input, "[.]0123456789+-/*^()")
    args := strings.TrimLeft(strings.TrimRight(input, "]"), fType+"[")

    if !strings.ContainsAny(args, "+ & * & - & / & ^") {
        fArg, _ = strconv.ParseFloat(args, 64)
    } else {
        stack := Parse(args)
        stack = ToRPN(stack)
        fArg = SolveRPN(stack)
    }

    return strconv.FormatFloat(fnData[fType](fArg), 'f', 12, 64)
}

File 3:

package gocalc

type Stack struct {
    Values []Token
}

// Function Pop removes the top token of the stack and returns its value
func (stack *Stack) Pop() (i Token) {
    if len(stack.Values) == 0 {
        return
    }
    i = stack.Values[len(stack.Values)-1]
    stack.Values = stack.Values[:len(stack.Values)-1]
    return
}

// Fuction Push adds a token to the top of the stack
func (stack *Stack) Push(i ...Token) {
    stack.Values = append(stack.Values, i...)
}

// Function Peek returns the token at the top of the stack
func (stack *Stack) Peek() Token {
    return stack.Values[len(stack.Values)-1]
}

// Function IsEmpty check if there are any tokens in the stack
func (stack *Stack) IsEmpty() bool {
    return len(stack.Values) == 0
}

// Function Length returns the amount of tokens in the stack
func (stack *Stack) Length() int {
    return len(stack.Values)
}

The code can be found on GitHub.

I plan to further develop the code and to eventually add equation solving and more advanced features.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.