Hi everyone! For months now I've been reader of this forum and it's now time for me to register and start contributing. Well, my problem is basically with arrays and loops, in a calculator project..

What I'm trying to do is to write all operations in a line i.e: ( 2 + 1 - 3 + 2 )

Then, when pressing the Equal button, it automatically finds the operators and solves the operation. For this I'm trying to use a loop,so that if indexof("+") is not -1, then it founds the operator, then susbstring the string from the operator to the back, and it0s stored into an array for then executing the normal arithmetic functions.

Here's the code so you can understand what I'm trying to explain

Private Sub btnIgualPrueba_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnIgualPrueba.Click
        Dim i As Integer = 0

        While Not txtOperaciones.Text.IndexOf("-") = -1
            Resultado_Resta(i) = txtOperaciones.Text.Substring(0, txtOperaciones.Text.IndexOf("-"))
            i = i + 1
        End While

    End Sub

There is offcourse a global array dimensioned earlier, but the problem lies in the substring method, where at debugging time, it ejects an error. It's the following:

IndexOutOfRangeException was handled.
Index was outside the bounds of the array

The issue here is that if I make the same code but whithout a loop or array, it works i.e: an if desicion structure..

Could somebody please help me? It's driving me nuts!

Thanks!

Recommended Answers

All 26 Replies

There is offcourse a global array dimensioned earlier

Dimensioned in what way? Like Private Resultado_Resta() As String ?
That creates an empty array.
First, since you don't know beforehand how many elements you insert in to the array, you have to use ReDim Preserve with that array.

Second point (which actually causes the error) is the test While Not txtOperaciones.Text.IndexOf("-") = -1 which is always true if the text contains a "-" character. You end up with an infinite loop and

Resultado_Resta(i) = ...
i = i + 1

will get out of bounds at some point.

Here's how you could fix the loop (with an example of ReDim Preserve if needed):

Dim indexOfOperator As Integer = 0 ' The index to the point in the string where to start searching
Dim i As Integer = 0 ' Loop counter

While Not txtOperaciones.Text.IndexOf("-", indexOfOperator) = -1 ' This prevents finding the same op again
    ' Here's how you'll use Redim Preserve:
    ' ReDim Preserve Resultado_Resta(i + 1)
    Resultado_Resta(i) = txtOperaciones.Text.Substring(0, txtOperaciones.Text.IndexOf("-", indexOfOperator)) ' Get current op
    i = i + 1 ' Increase loop counter

    ' Increase the search starting point over the operator which was just found!
    indexOfOperator = txtOperaciones.Text.IndexOf("-", indexOfOperator) + 1
End While

HTH

Thanks for your reply!

I've got some questions and I hope you or somenone can answer it for me, because I'm kinda new to vb.net. Anyway, my first question is the following, the

Redim Preserve

must be declared inside the procedure, or can it be declared at class or module level?

My second questions is, in the

txtOperaciones.Text.Substring(0, txtOperaciones.Text.IndexOf("-", indexOfOperator))

, when you write both

("-", indexOfOperator)

, what is the result? What is the reason for writing IndexOfOperator?

Hope my questions isn't too boring and stupid for you. Thnks!

Member Avatar for Unhnd_Exception

This will Calculate the value if you havn't already done so.

Private Sub btnCompute_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCompute.Click

        Dim Text As String = "2.3 + 1 - .5 - 2 + 1 / 2 * 3"
        Dim Operators() As Char = {"+"c, "-"c, "/"c, "*"c}
        Dim line As String() = Text.Split(" ")


        Dim value As Double
        Dim nextOperatorIndex As Integer = Integer.MinValue
        Dim OperatorCheck As Boolean

        For i = 0 To UBound(line)

            If OperatorCheck Then
                'Get the next operator
                nextOperatorIndex = Array.IndexOf(operators, CChar(line(i)))
            Else

                If Not IsNumeric(line(i)) Then
                    'Should have been numeric
                    MsgBox("Bad Input")
                    Exit Sub
                End If

                Select Case nextOperatorIndex

                    Case 0
                        '+
                        value += CDbl(line(i))

                    Case 1
                        '-
                        value -= CDbl(line(i))

                    Case 2
                        '/
                        If line(i) = 0 Then
                            MsgBox("Bad Input")
                            Exit Sub
                        End If
                        value /= CDbl(line(i))

                    Case 3
                        '*
                        value *= CDbl(line(i))

                    Case Integer.MinValue
                        'Initialize
                        value = CDbl(line(i))

                    Case Else
                        'Should have been an operator
                        MsgBox("Bad Input")
                        Exit Sub

                End Select
            End If

            'Flip operator check
            OperatorCheck = Not OperatorCheck

        Next

        MsgBox(Math.Round(value, 9))


    End Sub

Anyway, my first question is the following, the
Redim Preserve
must be declared inside the procedure, or can it be declared at class or module level?

Inside the procedure, always. It changes the "dimension" i.e. the number of the elements an array can hold and preserves elements that the array already has. For example:

Dim intArray(2) ' Array for two integers
intArray(0) = 1
intArray(1) = 2
ReDim Preserve intArray(4) ' Now the array can have four integers and the first two integers are not lost
intArray(2) = 3
intArray(3) = 4

My second questions is, in the
txtOperaciones.Text.Substring(0, txtOperaciones.Text.IndexOf("-", indexOfOperator))
when you write both
("-", indexOfOperator)
what is the result? What is the reason for writing IndexOfOperator?

IndexOf([string], [integer]) is overloaded method where [integer] is the index in the string where the search begins. You can think a string as an array of characters where the first character has index of zero and so on. In your code when an operator is found, lets say at position (index) four, you set the variable IndexOfOperator = 5 so that the next search starts after the previous op and you don't "hit" the same operator again.

You could also check Unhnd_Exception's code. It has few weaknesses but as a school assignment (?) you don't need 100% robust code (just make yourself clear what could cause the code to crash :) ).

HTH

Dim Terminos() As String
        Dim i As Short = 0
        Dim OperatorIndex As Short = 0

        For Each Letter In txtOperaciones.Text.ToCharArray

            Select Case Letter

                Case "+"

                    ReDim Preserve Terminos(i)
                    Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, txtOperaciones.Text.IndexOf("+"))
                    OperatorIndex = txtOperaciones.Text.IndexOf("+") + 1
                    MessageBox.Show(Terminos(i))
                    i = i + 1
                    Exit Select


                Case "-"

                    ReDim Preserve Terminos(i)
                    Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, txtOperaciones.Text.IndexOf("-"))
                    OperatorIndex = txtOperaciones.Text.IndexOf("-") + 1
                    MessageBox.Show(Terminos(i))
                    i = i + 1


                Case "*"

                    ReDim Preserve Terminos(i)
                    Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, txtOperaciones.Text.IndexOf("*"))
                    OperatorIndex = txtOperaciones.Text.IndexOf("*") + 1
                    i = i + 1
                    Exit Select

                Case "/"

                    ReDim Preserve Terminos(i)
                    Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, txtOperaciones.Text.IndexOf("/"))
                    OperatorIndex = txtOperaciones.Text.IndexOf("/") + 1
                    i = i + 1
                    Exit Select
            End Select


        Next

Here is kinda what you said, but I've got a problem... When I have as an input different operators, the terms in each array are badly saved. i.e: 2+2+2 makes the array likes this Terminos(0) = 2 ; Terminos(1) = 2 and so on. But with diferent operators ( 2 + 3 - 1 ) the arrays is saved like this: Terminos (0) = 2 ; Terminos(1) = 3-1.

Any idea why? Hope I'm not too annoying

Any idea why?

txtOperaciones.Text.IndexOf("+") returns the index of the first occurrence of "+" character.

So, with input 2+2+2, IndexOf("+") returns 1 in the first time. Second time, when the variable Letter = "+", IndexOf("+") returns 1 again and that's the same plus sign as previously.

You have to skip over the operators you've already handled. That's why I suggested using IndexOf("+", OperatorIndex) where OperatorIndex points to the next character after "+"-operator:

Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, txtOperaciones.Text.IndexOf("+", OperatorIndex))
OperatorIndex = txtOperaciones.Text.IndexOf("+", OperatorIndex) + 1

and the same correction with "-", "*" and "/" operator handling.

    Dim Terminos() As String
    Dim i As Short = 0
    Dim OperatorIndex As Short = 0
    Dim OperatorOrder() As String

    For Each Letter In txtOperaciones.Text.ToCharArray

        Select Case Letter

            Case "+"

                ReDim Preserve Terminos(i)
                Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, (txtOperaciones.Text.IndexOf("+", OperatorIndex) - OperatorIndex))
                OperatorIndex = txtOperaciones.Text.IndexOf("+", OperatorIndex) + 1
                ReDim Preserve OperatorOrder(i)
                OperatorOrder(i) = OperatorOrder(i) + "+"
                MessageBox.Show(Terminos(i))
                i = i + 1

            Case "-"
                ReDim Preserve Terminos(i)
                Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, (txtOperaciones.Text.IndexOf("-", OperatorIndex) - OperatorIndex))
                OperatorIndex = txtOperaciones.Text.IndexOf("-", OperatorIndex) + 1
                ReDim Preserve OperatorOrder(i)
                OperatorOrder(i) = OperatorOrder(i) + "-"
                MessageBox.Show(Terminos(i))
                i = i + 1

            Case "*"

                ReDim Preserve Terminos(i)
                Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, (txtOperaciones.Text.IndexOf("*", OperatorIndex) - OperatorIndex))
                OperatorIndex = txtOperaciones.Text.IndexOf("*", OperatorIndex) + 1
                ReDim Preserve OperatorOrder(i)
                OperatorOrder(i) = OperatorOrder(i) + "*"
                MessageBox.Show(Terminos(i))
                i = i + 1

            Case "/"

                ReDim Preserve Terminos(i)
                Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, (txtOperaciones.Text.IndexOf("/", OperatorIndex) - OperatorIndex))
                OperatorIndex = txtOperaciones.Text.IndexOf("/", OperatorIndex) + 1
                ReDim Preserve OperatorOrder(i)
                OperatorOrder(i) = OperatorOrder(i) + "/"
                MessageBox.Show(Terminos(i))
                i = i + 1
        End Select

    Next[/CODE]

Okay, so I managed to correct the mistakes I had in the array and loop things, now everything in separating the terms from the operators is ok. Now the final problem ( I hope..), how can I solve it? I mean, I have the terms in order, and I guess I could also in another array make the operators go there, and then do something like..

if firstarray is = "+" then
    TemporalResult = Termino(o) + Termino 1

if secondarray = "+" then
    TemporalResult = TemporalResult + Termino(2)

and so on... Would it be ok like this? Off course, the if..then estructures would be more complete, with else if and all of that.. Would that work? Thanks fro all the help!

Would that work?

No it won't work. Operators have certain precedence. Think for example "2 + 6 / 2 - 5" ->
"2 + 3 - 5" -> "5 - 5" -> "0" but if you simply go from left to right (which is wrong of course) you get "2 + 6 / 2 - 5" -> "8 / 2 - 5" -> "4 - 5" -> "-1" and that's incorrect answer.

You could try some googling. Examples I found were a bit "overkill" but you could try to simplify code. Arithmetic expressions, especially if there's parentheses included, are solved with expression trees (here's an example).

I wouldn't say that your approach doesn't work. You just have to make two loops with arrays. First loop to evaluate "/" and "*" operators and replace results to array or create a new array. The second loop to evaluate lower precedence "+" and "-" operators. So you're on the right track with the idea of having another array for the operators.

HTH

Dim Terminos() As String
        Dim i As Short = 0
        Dim OperatorIndex As Short = 0
        Dim OperatorOrder() As String
        Dim TemporalResult As String = 0
        Dim u As Short = 0
        Dim OperatorUsed() As Boolean
        Dim TerminosUsed() As Boolean
        Dim LastTerm As String


        ' Converts the string input into an array of characters, so it can analise them one by one.
        For Each Letter In txtOperaciones.Text.ToCharArray

            ' Start lookink for operators in the array
            Select Case Letter

                'Initial value of Operatorindex is 0, if not, the first search for operator would return error
                ' If the "+" character is found, select from the previous character index (Operatorindex) to the character before the next operator ( "+" , "-" , "*" , "/")
                Case "+"

                    ReDim Preserve Terminos(i)
                    Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, (txtOperaciones.Text.IndexOf("+", OperatorIndex) - OperatorIndex))
                    OperatorIndex = txtOperaciones.Text.IndexOf("+", OperatorIndex) + 1
                    ReDim Preserve OperatorOrder(i)
                    OperatorOrder(i) = "+"
                    i = i + 1

                    ' Same but for the "-" character
                Case "-"
                    ReDim Preserve Terminos(i)
                    Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, (txtOperaciones.Text.IndexOf("-", OperatorIndex) - OperatorIndex))
                    OperatorIndex = txtOperaciones.Text.IndexOf("-", OperatorIndex) + 1
                    ReDim Preserve OperatorOrder(i)
                    OperatorOrder(i) = "-"
                    i = i + 1

                    ' Same but for the "+" character
                Case "*"

                    ReDim Preserve Terminos(i)
                    Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, (txtOperaciones.Text.IndexOf("*", OperatorIndex) - OperatorIndex))
                    OperatorIndex = txtOperaciones.Text.IndexOf("*", OperatorIndex) + 1
                    ReDim Preserve OperatorOrder(i)
                    OperatorOrder(i) = "*"
                    i = i + 1

                    ' Same but for the "/" character
                Case "/"

                    ReDim Preserve Terminos(i)
                    Terminos(i) = txtOperaciones.Text.Substring(OperatorIndex, (txtOperaciones.Text.IndexOf("/", OperatorIndex) - OperatorIndex))
                    OperatorIndex = txtOperaciones.Text.IndexOf("/", OperatorIndex) + 1
                    ReDim Preserve OperatorOrder(i)
                    OperatorOrder(i) = "/"
                    i = i + 1
            End Select

            ' As the Case structure only selects the terms before the operator, the last term, after the last operator, would not be saved into an array
            'ERROR IS RETURNED HERE! PLEASE HELP! OTHER METHOD OF DOING THIS? OR ANY OBVIOUS MISTAKES I HAVEN'T SEEEN?
            LastTerm = (UBound(Terminos) + 1)

            ReDim Preserve Terminos(LastTerm)
            Select Case UBound(OperatorOrder)

                ' If the last operator is "+", then it selects from the last index of this one to the length of the string
                Case "+"

                    Terminos(LastTerm) = txtOperaciones.Text.Substring(txtOperaciones.Text.LastIndexOf(("+") + 1), txtOperaciones.Text.Length)

                    ' Same for the "-" operator
                Case "-"

                    Terminos(LastTerm) = txtOperaciones.Text.Substring(txtOperaciones.Text.LastIndexOf(("+") + 1), txtOperaciones.Text.Length)

                    ' Same for the "*" operator
                Case "*"

                    Terminos(LastTerm) = txtOperaciones.Text.Substring(txtOperaciones.Text.LastIndexOf(("+") + 1), txtOperaciones.Text.Length)

                    ' Same for the "/" operator
                Case "/"

                    Terminos(LastTerm) = txtOperaciones.Text.Substring(txtOperaciones.Text.LastIndexOf(("+") + 1), txtOperaciones.Text.Length)

            End Select

        Next

        ' Final resolution starts here
        For i = 0 To UBound(Terminos)

            ' As operators have priorities, a loop here is made to check the operators in each value of the array
            ' If the value is "*", then the array OperatorsUsed() is checked (Boolean).
            ' It also solves the term after and before the operator
            ' The array ( TerminosUsed() ) also takes place here as a boolean, to mark the terms that have already been solved in this loop
            ' This way, on the next loop there is no mistakes and errors
            ' I BELIEVE THERE IS GONNA BE AN ERROR HERE TOO, ANY MISTAKES VISIBLE AT FIRST SIGHT WOULD BE APPREACIATED!
            If OperatorOrder(i) = "*" Then

                ' Looks for operator "*"
                MessageBox.Show(OperatorOrder(i))
                TemporalResult = Terminos(i - 1) * Terminos(i + 1)
                ReDim Preserve OperatorUsed(i)
                OperatorUsed(i) = True
                TerminosUsed(i - 1) = True
                TerminosUsed(1 + 1) = True

                ' Looks for operator "/"
            ElseIf OperatorOrder(i) = "/" Then

                TemporalResult = Terminos(i - 1) / Terminos(i + 1)
                ReDim Preserve OperatorUsed(i)
                OperatorUsed(i) = True
                ReDim Preserve TerminosUsed(i)
                TerminosUsed(i - 1) = True
                TerminosUsed(1 + 1) = True

                ' For the rest of operators, the array is turned into false (Boolean)
            Else

                ReDim Preserve OperatorUsed(i)
                OperatorUsed(i) = False

            End If

        Next


        'For all the operators whose array ( OperatorUsed() ) = False, it starts solving the terms before and after of the operator.
        'It also checks for the array ( TerminosUsed() ) = False, it it was true, the term would have already been used on the loop before.
        For i = 0 To UBound(Terminos)

            If OperatorUsed(i) = False Then

                Select Case OperatorOrder(i)

                    Case "+"

                        If TerminosUsed(i) = False Then

                            TemporalResult = TemporalResult + Terminos(i)

                        End If

                    Case "-"

                        If TerminosUsed(i) = False Then

                            TemporalResult = TemporalResult - Terminos(i)

                        End If

                End Select

            End If

        Next

Any help now? I've added comments so the code is easier to understand and read. I also pointed out where the error was given, it's a null reference error, but I don't know what's wrong. Is the term (Ubound(array) + 1) valid? Or am I just making that up? Is there another way of doing this?

Thanks for all the help!

Is there another way of doing this?

I hope so. This shouldn't be that hard puzzle :)

Did you try Unhnd_Exception's code he posted? I didn't test it but it looks ok to me. Not sure how it handles the operator precedence I mentioned earlier. Give it a try anyway and let me know what happens.

HTH

Alright, I'm gonna try to use the unhandled exception code, but I like to understand it before actually using it, so, can you explain me what the function of the "c" is in the array of characters? I can't figure it out! Thanks!

can you explain me what the function of the "c" is

Unhnd_Exception declares an array of characters (i.e. array of type Char): Dim Operators() As Char. You can initialize arrays by giving comma separated values when you declare the array: Dim Operators() As Char = {"+"c, "-"c, "/"c, "*"c} . This declares an array of type Char and the array size is four items.

Since "+" is of type string, not of type Char, you must use postfix operator c to cast the string to type Char.

HTH

Member Avatar for Unhnd_Exception

Heres a more complex solution.

This code recursively calculates all text inside () first.

It then calculates Supperior, Primary, Secondary operators next, In order.

I made the terms up.

Supperior: Any operator that has a number to the right. Cos, tan

Primary: * / ^

Secondary: + -


Input:
3 + 4 * 5 returns 23

2 * (4 + 5) / (2^2) returns 4.5

2 + (sin(45) - (tan~1(.5) + pi))* cos(4) returns -26.928895634


Just make sure your calculator is set to degrees when checking trig functions.

Power of needs to be in () like above or change code.

If you have any errors with this let me know.

If you don't want () or supperior operators then step 1 and step 2 can be removed.
At that point the code will only do the primary and secondary operators.

Public Class Calculator
    'Command Containers
    Private SupperiorOperators As Dictionary(Of Char, String)
    Private PrimaryOperators() As Char = {"*"c, "/"c, "^"c}
    Private SecondaryOperators() As Char = {"p"c, "m"c} 'p + m -

    'Trig Helpers
    Private Const ToRadians As Double = 0.0174532925
    Private Const ToDegrees As Double = 57.29577951

    Sub New()

        'Fill the Supperior Operator Container with user commands
        'and internal character codes.
        SupperiorOperators = New Dictionary(Of Char, String)
        SupperiorOperators.Add("x", "cos~1")
        SupperiorOperators.Add("y", "sin~1")
        SupperiorOperators.Add("z", "tan~1")
        SupperiorOperators.Add("c", "cos")
        SupperiorOperators.Add("s", "sin")
        SupperiorOperators.Add("t", "tan")

    End Sub

    Public Function Calculate(ByVal Text As String) As Double
        If String.IsNullOrEmpty(Text) Then Return 0

        'Replace all blank spaces with ""
        Text = Text.Replace(" ", "")

        'Replace the user pi command to its numeric
        Text = Text.Replace("pi", Math.PI.ToString)

        'Replace the + and - commands to our internal character values.
        'solved issue with negative numbers
        Text = Text.Replace("+", "p")
        Text = Text.Replace("-", "m")

        'Replace all the supperior commands with our internal character values.
        For Each SO As KeyValuePair(Of Char, String) In SupperiorOperators
            Text = Text.Replace(SO.Value, SO.Key)
        Next

        'Do it
        Text = CalculateText(Text)

        'If the text is not numeric an error occured.
        If IsNumeric(Text) Then
            Return Math.Round(CDbl(Text), 9)
        Else
            MsgBox(Text)
            Return 0
        End If


    End Function


    Private Function CalculateText(ByVal Text As String) As String

        'Step 1 recursivley calculate all text within ()
        Do While Text.Contains("(")
            Dim startIndex As Integer = -1
            Dim stopIndex As Integer = -1

            For i = 0 To Text.Length - 1
                Select Case Text(i)
                    Case "("
                        startIndex = i
                    Case ")"
                        If startIndex <> -1 Then
                            stopIndex = i
                            Exit For
                        Else
                            'Error
                            Return "Expected ( @" & Text
                        End If
                End Select
            Next
            If stopIndex = -1 Then
                Return "Expected ) @" & Text
            End If

            Dim newText As String = CalculateText(Text.Substring(startIndex + 1, stopIndex - startIndex - 1))

            If Not IsNumeric(newText) Then
                'an error occured
                'pass back the provided error.
                Return newText
            Else
                Text = Text.Replace(Text.Substring(startIndex, stopIndex - startIndex + 1), newText)
            End If

        Loop

        'Do Breaker
        Dim found As Boolean


        'At this point the Text has no ().

        'Step 2. Calculate the supperior operators.

        'The supperior ops should have a number to the right of them
        'once a supperior op is found keep going through the text until
        'we hit another supperior, primary, or secondary op or the end
        'of the Text
        'between those two indexes should be a number.  If not an error
        'has occured

        Do While True
            found = False
            For i = 0 To Text.Length - 1
                If SupperiorOperators.ContainsKey(Text(i)) Then
                    'sup op found.

                    found = True
                    Dim stopIndex As Integer = -1
                    Dim num1, value As Double

                    'look for the next operator
                    For j = i + 1 To Text.Length - 1
                        If SupperiorOperators.ContainsKey(Text(j)) Then
                            stopIndex = j
                            Exit For
                        ElseIf PrimaryOperators.Contains(CChar(Text(j))) Then
                            stopIndex = j
                            Exit For
                        ElseIf SecondaryOperators.Contains(CChar(Text(j))) Then
                            stopIndex = j
                            Exit For
                        End If
                    Next

                    'no op found.  set the stop index to the end of the text.
                    If stopIndex = -1 Then stopIndex = Text.Length

                    If IsNumeric(Text.Substring(i + 1, stopIndex - i - 1)) Then
                        num1 = CDbl(Text.Substring(i + 1, stopIndex - i - 1))
                    Else
                        Return "Expected Number @" & Text
                    End If

                    'Data good.  perform the calc.
                    Select Case Text(i)
                        Case "x" ' "cos~1"
                            value = Math.Acos(num1) * ToDegrees
                        Case "y" ' "sin~1"
                            value = Math.Asin(num1) * ToDegrees
                        Case "z" ' "tan~1"
                            value = Math.Atan(num1) * ToDegrees
                        Case "c" ' "cos"
                            value = Math.Cos(num1 * ToRadians)
                        Case "s" ' "sin"
                            value = Math.Sin(num1 * ToRadians)
                        Case "t" ' "tan"
                            value = Math.Tan(num1 * ToRadians)

                    End Select

                    'replace the new value in the existing text.
                    Text = Text.Replace(Text.Substring(i, stopIndex - i), value)
                    Exit For
                End If
                'check for another supperior op.
            Next

            'No more supperior's.  Kick out
            If Not found Then Exit Do
        Loop

        'At this point there are no sperior operators or () in the text

        'Step 3. Search and calculate for multiplication, division, and powerof

        'The primary ops.

        'Primary ops should have a number to the left and right of them.  Once a
        'primary op is found.  Go backwards until the next primary or secondary or
        'beging of text is found.  Next go forwards until the next primary or 
        'secondary or end of text is found.  the values inbetween should be
        'numeric
        Do While True
            found = False
            For i = 0 To Text.Length - 1
                If PrimaryOperators.Contains(CChar(Text(i))) Then
                    'primary op found

                    found = True
                    Dim stopIndex As Integer = -1
                    Dim startIndex As Integer = -1
                    Dim num1, num2, value As Double

                    'go backwards and check for next primary or secondary op
                    For j = i - 1 To 0 Step -1
                        If PrimaryOperators.Contains(CChar(Text(j))) Then
                            startIndex = j + 1
                            Exit For
                        ElseIf SecondaryOperators.Contains(CChar(Text(j))) Then
                            startIndex = j + 1
                            Exit For
                        End If
                    Next

                    If startIndex = -1 Then startIndex = 0

                    'Validate the first value.
                    If IsNumeric(Text.Substring(startIndex, i - startIndex)) Then
                        num1 = CDbl(Text.Substring(startIndex, i - startIndex))
                    Else
                        Return "Expected Number @" & Text
                    End If

                    'first number good.  go forwards and get the next 
                    'primary or secondary op
                    stopIndex = -1
                    For j = i + 1 To Text.Length - 1
                        If PrimaryOperators.Contains(CChar(Text(j))) Then
                            stopIndex = j
                            Exit For
                        ElseIf SecondaryOperators.Contains(CChar(Text(j))) Then
                            stopIndex = j
                            Exit For
                        End If
                    Next

                    If stopIndex = -1 Then stopIndex = Text.Length

                    If IsNumeric(Text.Substring(i + 1, stopIndex - i - 1)) Then
                        num2 = CDbl(Text.Substring(i + 1, stopIndex - i - 1))
                    Else
                        Return "Expected Number @" & Text
                    End If

                    'Both numbers good.  Perform the calc.
                    Select Case Text(i)
                        Case "*"
                            value = num1 * num2
                        Case "/"
                            If num2 = 0 Then
                                Return "Can't Divide by 0 @" & Text
                            End If
                            value = num1 / num2
                        Case "^"
                            value = Math.Pow(num1, num2)
                    End Select

                    'Replace the new value inside the text.
                    Text = Text.Replace(Text.Substring(startIndex, stopIndex - startIndex), value)

                    Exit For

                End If
                'Check for another primary op
            Next

            'No ops found.  Get the f out.
            If Not found Then Exit Do
        Loop

        'At this point the text contains only Add or Minus

        'Final Step. Search and calculate + and -

        'Identical procedure as above.  Only this time were only looking for 
        'secondary + and -
        Do While True
            found = False
            For i = 0 To Text.Length - 1
                If SecondaryOperators.Contains(CChar(Text(i))) Then
                    found = True
                    Dim stopIndex As Integer = -1
                    Dim startIndex As Integer = -1
                    Dim num1, num2, value As Double

                    startIndex = -1
                    For j = i - 1 To 0 Step -1
                        If SecondaryOperators.Contains(CChar(Text(j))) Then
                            startIndex += j + 1
                            Exit For
                        End If
                    Next

                    If startIndex = -1 Then startIndex = 0

                    If IsNumeric(Text.Substring(startIndex, i - startIndex)) Then
                        num1 = CDbl(Text.Substring(startIndex, i - startIndex))
                    Else
                        Return "Expected Number @" & Text
                    End If

                    stopIndex = -1
                    For j = i + 1 To Text.Length - 1
                        If SecondaryOperators.Contains(CChar(Text(j))) Then
                            stopIndex = j
                            Exit For
                        End If
                    Next

                    If stopIndex = -1 Then stopIndex = Text.Length

                    If IsNumeric(Text.Substring(i + 1, stopIndex - i - 1)) Then
                        num2 = CDbl(Text.Substring(i + 1, stopIndex - i - 1))
                    Else
                        Return "Expected Number @" & Text
                    End If

                    Select Case Text(i)
                        Case "p"
                            value = num1 + num2
                        Case "m"
                            value = num1 - num2
                    End Select

                    Text = Text.Replace(Text.Substring(startIndex, stopIndex - startIndex), value)
                    Exit For
                End If
            Next
            If Not found Then Exit Do
        Loop

        'Calculation complete.  This text should be numeric.  If not more error
        'checking needs to be performed.
        Return Text

    End Function
End Class

Unhnd_Exception declares an array of characters (i.e. array of type Char): Dim Operators() As Char. You can initialize arrays by giving comma separated values when you declare the array: Dim Operators() As Char = {"+"c, "-"c, "/"c, "*"c} . This declares an array of type Char and the array size is four items.

Since "+" is of type string, not of type Char, you must use postfix operator c to cast the string to type Char.

HTH

Alright, I understand that part now, thanks. But regarding to the conversion to type Char, wouldn't it work to do the following: cchar{("+"),cchar("-"),cchar("*"),cchar("/")}. Would it be the same, or when you declare arrays only the postfix would work? Just to clear a doubt here! Thanks!

I'll be working on unhandled_exception code, when I've tested it I will edit this post! Looks nice btw! Thanks!

Member Avatar for Unhnd_Exception

cchar and c would be equivalent.

If you want to see the point of it all. Type in Option Strict On at the top of the class. That code will now have 22 errors. 20 of them from not converting the strings to characters with c or cchar, 2 of them from not converting integers to strings with cstr or .ToString. When you don't cast your variables to what they should be it causes late binding to what they should be and would perform worse than using early binding with the cchar or other conversions.

Option Strict On

Public Class Calculator    
    'Command Containers    
     Private SupperiorOperators As Dictionary(Of Char, String)

cchar and c would be equivalent.

If you want to see the point of it all. Type in Option Strict On at the top of the class. That code will now have 22 errors. 20 of them from not converting the strings to characters with c or cchar, 2 of them from not converting integers to strings with cstr or .ToString. When you don't cast your variables to what they should be it causes late binding to what they should be and would perform worse than using early binding with the cchar or other conversions.

Option Strict On

Public Class Calculator    
    'Command Containers    
     Private SupperiorOperators As Dictionary(Of Char, String)

Thanks, I think I will now turn on option strict fro coding practice.. Anyway, could you explain me how the type Dictionary works? Any special characteristics or something? Hope it's not too much of a problem.. Thanks.

how the type Dictionary works? Any special characteristics or something?

Not really. Dictionary is used to look up an value by its associated key (both generic types: Dictionary(Of TKey, TValue)). For example, SupperiorOperators As Dictionary(Of Char, String) takes a character as a key and a string as the associated value.

First you usually add items to the dictionary and after that you look up values by the key.

Think for example a phonebook:

Dim PhoneBook As Dictionary(Of Integer, String)
PhoneBook.Add(5551234, "Smith")
PhoneBook.Add(5556677, "Jones")
' Who's number is 5556677?
If PhoneBook.ContainsKey(5556677) Then
  MsgBox("Number belongs to: " & PhoneBook.Item(5556677))
Else
  MsgBox("Unknown number")
End If

HTH

Alright, know I've got clear what can I do with the dictionary, I ain't copying Unhandled_exception code, I'm writing mine based on his, I've learnt I learn much more this way. I've come to a problem I can't seem to solve. Here's the code.

Dim i As Short = 0
        Dim OperatorsIndex() As Short
        Dim TermsToOperate() As Integer
        Dim IndexOfLastOperator As Short = 0
        Dim OperatorsPrecedence() As Boolean
        Dim OperatorsOrder() As String
        Dim TemporalResult As Integer

        Text = Text.Replace(" ", "")

        For Each Letter In Text.ToCharArray
            Text.Replace(" ", "")
            Select Case Letter
                Case CChar("+")
                    ReDim Preserve TermsToOperate(i)
                    TermsToOperate(i) = CInt(Text.Substring(IndexOfLastOperator, Text.IndexOf("+", IndexOfLastOperator + 1) - IndexOfLastOperator))
                    IndexOfLastOperator = CShort(Text.IndexOf("+", IndexOfLastOperator + 1))
                    ReDim Preserve OperatorsIndex(i)
                    OperatorsIndex(i) = CShort(Text.IndexOf("+", (IndexOfLastOperator + 1)))
                    ReDim Preserve OperatorsPrecedence(i)
                    OperatorsPrecedence(i) = False
                    ReDim Preserve OperatorsOrder(i)
                    OperatorsOrder(i) = "+"
                    MessageBox.Show(CStr(OperatorsOrder(i)))
                    MessageBox.Show(CStr(OperatorsIndex(UBound(OperatorsIndex))))
                Case CChar("-")
                    ReDim Preserve TermsToOperate(i)
                    TermsToOperate(i) = CInt(Text.Substring(IndexOfLastOperator, Text.IndexOf("-", IndexOfLastOperator + 1) - IndexOfLastOperator))
                    IndexOfLastOperator = CShort(Text.IndexOf("-", IndexOfLastOperator + 1))
                    ReDim Preserve OperatorsIndex(i)
                    OperatorsIndex(i) = CShort(Text.IndexOf("-", (IndexOfLastOperator + 1)))
                    ReDim Preserve OperatorsPrecedence(i)
                    OperatorsPrecedence(i) = False
                    ReDim Preserve OperatorsOrder(i)
                    OperatorsOrder(i) = "-"
                    MessageBox.Show(CStr(OperatorsOrder(i)))
                Case CChar("*")
                    ReDim Preserve TermsToOperate(i)
                    TermsToOperate(i) = CInt(Text.Substring(IndexOfLastOperator, Text.IndexOf("*", IndexOfLastOperator + 1) - IndexOfLastOperator))
                    IndexOfLastOperator = CShort(Text.Substring("*", IndexOfLastOperator + 0.5))
                    ReDim Preserve OperatorsIndex(i)
                    OperatorsIndex(i) = CShort(Text.IndexOf("*", (IndexOfLastOperator + 1)))
                    OperatorsPrecedence(i) = True
                    ReDim Preserve OperatorsOrder(i)
                    OperatorsOrder(i) = "*"
                    MessageBox.Show(CStr(TermsToOperate(i)))
                Case CChar("/")
                    ReDim Preserve TermsToOperate(i)
                    TermsToOperate(i) = CInt(Text.Substring(IndexOfLastOperator, Text.IndexOf("/", IndexOfLastOperator + 1) - IndexOfLastOperator))
                    IndexOfLastOperator = CShort(Text.Substring("/", IndexOfLastOperator))
                    ReDim Preserve OperatorsIndex(i)
                    OperatorsIndex(i) = CShort(Text.IndexOf("/", (IndexOfLastOperator + 1)))
                    OperatorsPrecedence(i) = True
                    ReDim Preserve OperatorsOrder(i)
                    OperatorsOrder(i) = "/"
                    MessageBox.Show(CStr(TermsToOperate(i)))
            End Select
        Next

        ReDim Preserve TermsToOperate((UBound(TermsToOperate) + 1))
        TermsToOperate(UBound(TermsToOperate)) = (Text.Substring(OperatorsIndex(UBound(OperatorsIndex)), (Text.Length - OperatorsIndex(UBound(OperatorsIndex)))))
        MessageBox.Show(TermsToOperate(UBound(TermsToOperate)))

It turns aout that in the messagebox that shows me the operatorsindex array, the last term of that array is -1, like if the operator isn't there, but it is there. For example, 2+2+2+2+2.. operators index would be as following, 1,3,5,-1. That last one is always -1, when it should be 7. Could you please highlight me the wrong part of the code? Thanks!

Member Avatar for Unhnd_Exception

Looks like you needed to move the line that assigns the IndexofLastOperator down a couple of lines.

I also compressed your select statement.

Dim i As Short = 0
 Dim OperatorsIndex() As Short
 Dim TermsToOperate() As Integer
 Dim IndexOfLastOperator As Short = 0
 Dim OperatorsPrecedence() As Boolean
 Dim OperatorsOrder() As String

 Text = " 2+2+2+2+2"
 Text = Text.Replace(" ", "")

 For Each Letter In Text.ToCharArray

   Select Case Letter
      Case "+"c, "*"c, "-"c, "/"c

         ReDim Preserve TermsToOperate(i)
         ReDim Preserve OperatorsIndex(i)
         ReDim Preserve OperatorsPrecedence(i)
         ReDim Preserve OperatorsOrder(i)

         TermsToOperate(i) = CInt(Text.Substring(IndexOfLastOperator, Text.IndexOf(Letter, IndexOfLastOperator + 1) - IndexOfLastOperator))

         OperatorsIndex(i) = CShort(Text.IndexOf(Letter, (IndexOfLastOperator + 1)))

         IndexOfLastOperator = CShort(Text.IndexOf(Letter, IndexOfLastOperator + 1))

         If Letter = "*"c Or Letter = "/"c Then OperatorsPrecedence(i) = True

         OperatorsOrder(i) = Letter

         MessageBox.Show(CStr(OperatorsOrder(i)))
         MessageBox.Show(CStr(OperatorsIndex(UBound(OperatorsIndex))))

         i += 1
              
    End Select
  Next

  ReDim Preserve TermsToOperate((UBound(TermsToOperate) + 1))
  TermsToOperate(UBound(TermsToOperate)) = i + 1
  MessageBox.Show(TermsToOperate(UBound(TermsToOperate)))

TermsToOperate(UBound(TermsToOperate)) = i + 1 I can't get why you did this? In this line, you are declaring the last term of the array to 1 + 2? Why would you do that? Or is it that I'm not seeing something? Anyway, thanks for the compression on the select case, really obvious when you think about it, but smart for thinking about it.. haha.

Another problem, I have managed to do what I wanted, here is the code.

Dim i As Short = 0
        Dim TermsToOperate() As Integer
        Dim IndexOfLastOperator As Short = 0
        Dim OperatorsPrecedence() As Boolean
        Dim OperatorsOrder() As String
        Dim TemporalResult As Integer


        'Spaces are errased for calculation purposes.
        Text = Text.Replace(" ", "")
        'Start looking Character By Character in a text string converted to an array.
        For Each Letter In Text.ToCharArray
            Select Case Letter
                'If it founds the caracters below, it would do the following.
                Case CChar("+"), CChar("-"), CChar("*"), CChar("/")
                    'The 4 statementes below redimension the array to the variable i, which will increase at the end of the loop, redimensioning the array 1 number at the time.
                    ReDim Preserve TermsToOperate(i)
                    ReDim Preserve OperatorsPrecedence(i)
                    ReDim Preserve OperatorsOrder(i)

                    'This takes the numbers between the operators and saves it into the array, being the terms that later will be operated.
                    TermsToOperate(i) = CInt(Text.Substring(IndexOfLastOperator, Text.IndexOf(Letter, IndexOfLastOperator + 1) - IndexOfLastOperator))

                    'This is the index(Position) of the last operator(+,-,*,/). With this, the calculator is able to know the space between the last and the new operator.
                    IndexOfLastOperator = CShort(Text.IndexOf(Letter, IndexOfLastOperator + 1))

                    'If the characters are * or /, then boolean is true. If boolean is true, this means that these operators have more importance than + and -, so the will be resolved first.
                    If Letter = CChar("*") Or Letter = CChar("/") Then OperatorsPrecedence(i) = True

                    'This keeps track of the Operators in the array of characters. This way, calculations can be made after having the order from left to right.
                    OperatorsOrder(i) = Letter

                    MessageBox.Show(TermsToOperate(i))
                    MessageBox.Show(IndexOfLastOperator)
                    'Increase variable 1 unit more, this way, the arrays can increment their dimensions.
                    i += 1

            End Select

        Next
        'Makes the array one dimension longer, so it won't subscribe the dimension before.
        ReDim Preserve TermsToOperate((UBound(TermsToOperate) + 1))
        'As the termselection only works when an operator is found, the last term isn't stored into the array. As the array has a new dimension, it will save the last term in there.
        TermsToOperate(UBound(TermsToOperate)) = Text.Substring(IndexOfLastOperator, (Text.Length - IndexOfLastOperator))
        MessageBox.Show(TermsToOperate(UBound(TermsToOperate)))

After the loop has finished, the arrays won't be empty again? So the values stored wouldn't be erased to? How can I make the values of the arrays stay there? Thanks!

Take a copy of the array(s):

Dim Backup(UBound(TermsToOperate)) As Integer
Array.Copy(TermsToOperate, Backup, UBound(TermsToOperate))

HTH

Thanks, really helpfull. But, what if i want to delete items from an array? Not clear them, I want to delete the first 2 items from an array, so that item index 2 would now be index 0 and so on. Is there a possible way to do that?

I want to delete the first 2 items from an array, so that item index 2 would now be index 0 and so on. Is there a possible way to do that?

Yes. Here's the common way to move elements (loop & move):

' This is the "basic" method of moving items in an array
For i As Integer = 0 To TermsToOperate.GetUpperBound(0)
  TermsToOperate(i) = TermsToOperate(i + 2)
Next i
ReDim Preserve TermsToOperate(TermsToOperate.GetUpperBound(0) - 2) ' If you want to shrink the array

You can also use Copy methods:

' Using Copy and a temp array
Dim TempArr(TermsToOperate.GetUpperBound(0)) As Integer

Array.ConstrainedCopy(TermsToOperate, 2, TempArr, 0, TermsToOperate.GetUpperBound(0) - 2) ' Copy to helper array
ReDim Preserve TermsToOperate(TermsToOperate.GetUpperBound(0) - 2) ' If you want to shrink the array
Array.Copy(TempArr, TermsToOperate, TermsToOperate.GetUpperBound(0)) ' Copy back to the original array

N.B. I did not check the bounds, beware of "off-by-one" error(s) ;)

HTH

As i'm doing this for the only intention of practice, i don't really care if At the end the calculator works or not.. But anyway, I was thinking, would it work if I made my own class ( properties and all that stuff ) and then applied, wouldn't it be better coding and easier and well, good for practice, or are there any reason why I shouldn't do it this way? thanks!

would it work if I made my own class ( properties and all that stuff ) and then applied, wouldn't it be better coding and easier and well, good for practice

Absolutely it would be better. As a class, you can use it in you current applications. Later, if you change it to handle more complex expressions, functions or parenthesis, and the interface doesn't change, your applications still work.

I would declare first an own exception (CalculatorException class) for the class. This example only throws an exception for empty input but you could handle also malformed input, division by zero etc.

Option Strict On
Option Explicit On

Public Class CalculatorException
    Inherits System.Exception

    Public Sub New()

    End Sub

    Public Sub New(ByVal Message As String)
        MyBase.New(Message)
    End Sub

    Public Sub New(ByVal Message As String, ByVal Inner As Exception)
        MyBase.New(Message, Inner)
    End Sub

End Class

Public Class Calculator

    Private _Expression As String

    Public Sub New()
        ' Assign the default values.
        _Expression = String.Empty
    End Sub

    Public Sub New(ByVal Expression As String)
        ' Assign the property values.
        _Expression = Expression
    End Sub

    Property Expression() As String
        Get
            Return _Expression
        End Get
        Set(ByVal value As String)
            _Expression = value
        End Set
    End Property

    Public Function Eval() As Double
        ' Check for empty input
        If String.IsNullOrEmpty(_Expression) Then
            Throw New CalculatorException("Empty expression")
        End If

        '*****************
        ' Here would be calculator code or a call to
        ' separate private function containing the code
        '*****************

        Return 0 ' Dummy
    End Function

    Public Function Eval(ByVal Expression As String) As Double
        _Expression = Expression
        Return Eval()
    End Function

End Class

Now you could use the Calculator class:

MsgBox(New Calculator("1 + 2 - 4"))

or

Dim Calc As New Calculator()
MsgBox(Calc.Eval("1 + 2 - 4"))

or

Dim Calc As New Calculator()
Calc.Expression = "1 + 2 - 4"
MsgBox(Calc.Eval())

or

Dim Calc As New Calculator()
Try
  MsgBox(Calc.Eval(""))
Catch (Ex As CalculatorException)
  MsgBox(Ex.Message)
End Try

HTH

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.