Implement the Calculations

Now it’s time to make this really nice-looking calculator work. The way our calculator logic is going to work is not how expressions are usually parsed, but this way will show you how to parse and manipulate strings in Dart, and it will also show you how to do that using regular expressions. It’s not the simplest solution and it’s surely not the one with the lowest computational complexity, but it’s a good example and it is supposed to make you more confident when writing Dart code that operates on strings.

The Calculation class needs to handle a few basic operations:

  1. Add a digit or operator.

  2. Display the current string containing all of the digits and operators that have been added.

  3. Compute the result of the current expression.

  4. Delete a digit or operator.

  5. Delete the entire expression.

Being able to display the current string can also be used for debugging insertion and deletion as it displays the current state of the calculation.

The Calculation class needs to be able to take inputs one character at a time and to compute the result of an expression considering mathematical operator precedence rules.

To do that, we will implement the class in the following way: we will have a List of Strings, each of which will be either an operator or a number to make parsing and computing results simpler.

We will start implementing it by creating a file called calculator.dart inside the lib directory.

At this point we can start defining our Calculation and that data it needs inside calculator.dart:

 class​ Calculation {
  List<​String​> a = [];
 }

The first behavior we’ll define is adding a character to the current calculation.

We will implement a method called add, which will take as an argument a String to be added to the calculation, after checking whether we add a new element to the List or not.

If the String we want to add is a digit and the previous String in the List is a number (it doesn’t contain an operator), we will append it to the last String in the List, otherwise it will have to be added as a new element in the List.

Also, if the List is still empty, we will have to create a new element, unless the character we’re trying to add is an operator, in which case we won’t do anything at all because the expression wouldn’t make sense with an operator as the first element.

To check whether a String contains operators we will use a regular expression to shorten the creation of complex conditions and avoid repeating them.

How to Use Regular Expressions in Dart

Regular expressions in Dart are achieved using the RegExp class.

Its constructor is used in the following way:

 RegExp regExp = RegExp(​"REGULAREXPRESSION"​);

In our case, the regular expression to check if a string contains an operator is:

 final​ RegExp regExp = RegExp(​"[+​​\​​-x÷]"​);

and we can check that a string (called string) matches that regular expression by using:

 bool​ matches = regExp.hasMatch(string)

In the case of the regular expression we saw earlier, it will be true if the string contains an operator character, and false if it doesn’t.

Implement add()

The first thing we will check is whether the List is empty and only adding the character if it’s not an operator:

 void​ ​add​(​String​ added) {
 if​(a.isEmpty) {
 if​(!regExp.hasMatch(added)) {
  a.add(added);
  }
 }
 }

If it’s not empty, we need to check whether the previous element was an operator.

In that case, we need to do the same thing we did for the first element—we can’t add another operator or a dot, but we can add a digit and it has to go into a separate List element:

 else​ ​if​(regExp.hasMatch(a.last)) {
 if​(!RegExp(​"[+​​\​​-x÷.]"​).hasMatch(added)) {
  a.add(added);
  }
 }

If the previous conditions haven’t been met (and that means the previous element was a number) we can add anything, but we need to differentiate betweem operators and digits:

  • An operator will have to go into its own separate List element;
  • A digit will have to be appended to the current last List element.

That can be implemented very simply:

 else​ {
 if​(regExp.hasMatch(added)) {
 if​(!RegExp(​"."​).hasMatch(a.last)) a.last+=​".0"​;
  a.add(added);
  } ​else​ {
  a.last+=added;
  }
 }

So the entire add method, combining all of the possible conditions, is:

 void​ ​add​(​String​ added) {
 if​(a.isEmpty) {
 if​(!regExp.hasMatch(added)) {
  a.add(added);
  }
  }
 else​ ​if​(regExp.hasMatch(a.last)) {
 if​(!RegExp(​"[+​​\​​-x÷.]"​).hasMatch(added)) {
  a.add(added);
  }
  }
 else​ {
 if​(regExp.hasMatch(added)) {
 if​(!RegExp(​"."​).hasMatch(a.last)) a.last+=​".0"​;
  a.add(added);
  } ​else​ {
  a.last+=added;
  }
  }
 }

Implement getString()

This is going to be the simplest method—it’s just going to display the concatenation of the strings in the List:

 String​ ​getString​() {
 String​ str = ​""​;
  a.forEach((​String​ el) {str+=el;});
 return​ str;
 }

Implement Deletion

To delete everything we just need to reset the List to its initial state:

 void​ ​deleteAll​() => a = [];

But deleting one character at a time is more complicated, as we’ll need to check multiple things:

  1. Check if the List is empty, in that case we’ll do nothing.

  2. Check if the last element of the List is longer than one character, in that case we’ll delete the last character of that string.

  3. If the last element is just one character, we’ll delete it completely.

Number 2 is a bit complicated: to delete the last character we actually need to assign to the last element a substring of itself with the last character deleted.

To create substrings, we are going to use the String.substring() method, which is used in the following way:

 var​ substr = string.substring(startIndex, length);

where startIndex is an integer representing the index at which the substring starts (relative to the string from which the substring is created) and length is the length of the substring.

In this case, we need it to start from 0 and to have a length that is equal to the length of the original string with 1 subtracted to it.

Combining all of that, the code for the deleteOne method is as follows:

 void​ ​deleteOne​() {
 if​(a.length > 0) {
 if​(a.last.length > 1) {
  a.last = a.last.substring(0, a.last.length-1);
  } ​else​ {
  a.removeLast();
  }
  }
 }

Implement Result Calculation

At this point, all that’s left to do is to calculate the result of the expression.

Before calculating the result, we’ll need to consider the case in which a user accidentally ends the expression with an operator (starting it with an operator is prevented by add()).

We also need to check whether the string ends with a ., and that will require us to check whether a dot is left, a condition for which would be the following:

 a.last.lastIndexOf(​"."​) == a.last.length-1

If that condition is true, we need to take the substring with the last character removed.

Since it might end with both an operator and a dot, we need to check twice separately:

 if​( regExp.hasMatch(a.last) )
  a.removeLast();
 if​(a.last.lastIndexOf(​"."​) == a.last.length-1)
  a.last = a.last.substring(0, a.length-1);

Now we need to parse the calculation and compute its result.

The divisions we did in the rest of the methods are really useful: we just need to check each element in the String and, if it is an operator, we’ll substitute the block composed by it and the adjacent elements with the result of the operation.

We will be implementing this using simple counter-based for loops because we need total control over which elements we’re parsing.

Because of that, after we compute the result of each sub-calculation, we will decrease the counter by 1 so that the next iteration of the for loop will actually be the operator after the newly calculated result, as the following illustration shows:

images/WidgetLayout/calculation.png

As this example shows, the newly calculated number takes the place that was occupied by the number to the left of the operator and the next operator takes the place of the operator we just considered.

Since the counter is currently at the index of the operator, we will have to decrement the loop’s counter so that, when it is incremented again in the next iteration, it is the same value it was in the previous iteration.

Because of mathematical operator precedence rules, we will first check if the List contains multiplications or divisions:

 for​(​int​ i = 0; i < a.length; i++) {
 if​(a[i] == ​"x"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) * double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  } ​else​ ​if​(a[i] == ​"÷"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) / double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  }
 }

and then check for additions or subtractions:

 for​(​int​ i = 0; i < a.length; i++) {
 if​(a[i] == ​"+"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) + double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  } ​else​ ​if​(a[i] == ​"-"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) - double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  }
 }

At the end, the List should contain just one String containing the result of the whole expression.

We’ll return that using:

 return​ ​double​.parse(a[0]);

The getResult method, combining all of these pieces, is:

 double​ ​getResult​() {
 if​( regExp.hasMatch(a.last) )
  a.removeLast();
 if​(a.last.lastIndexOf(​"."​) == a.last.length-1)
  a.last = a.last.substring(0, a.length-1);
 
 for​(​int​ i = 0; i < a.length; i++) {
 if​(a[i] == ​"x"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) * double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  } ​else​ ​if​(a[i] == ​"÷"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) / double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  }
  }
 
 for​(​int​ i = 0; i < a.length; i++) {
 if​(a[i] == ​"+"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) + double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  } ​else​ ​if​(a[i] == ​"-"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) - double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  }
  }
 
 if​(a.length != 1) ​throw​ Error();
 
 return​ ​double​.parse(a[0]);
 }

Wrapping Up the Calculation

The entire calculator.dart should be, at this point, the following:

 class​ Calculation {
  List<​String​> a = [];
 final​ RegExp regExp = RegExp(​"[+​​\​​-x÷]"​);
 
 void​ add(​String​ added) {
 if​(a.isEmpty) {
 if​(!regExp.hasMatch(added)) {
  a.add(added);
  }
  }
 else​ ​if​(regExp.hasMatch(a.last)) {
 if​(!RegExp(​"[+​​\​​-x÷.]"​).hasMatch(added)) {
  a.add(added);
  }
  }
 else​ {
 if​(regExp.hasMatch(added)) {
 if​(!RegExp(​"."​).hasMatch(a.last)) a.last+=​".0"​;
  a.add(added);
  } ​else​ {
  a.last+=added;
  }
  }
 }
 
 String​ ​getString​() {
 String​ str = ​""​;
  a.forEach((​String​ el) {str+=el;});
 return​ str;
 }
 
 double​ ​getResult​() {
 if​( regExp.hasMatch(a.last) )
  a.removeLast();
 if​(a.last.lastIndexOf(​"."​) == a.last.length-1)
  a.last = a.last.substring(0, a.length-1);
 
 for​(​int​ i = 0; i < a.length; i++) {
 if​(a[i] == ​"x"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) * double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  } ​else​ ​if​(a[i] == ​"÷"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) / double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  }
  }
 
 for​(​int​ i = 0; i < a.length; i++) {
 if​(a[i] == ​"+"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) + double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  } ​else​ ​if​(a[i] == ​"-"​) {
  a[i-1] = ​"​​${double.parse(a[i-1]) - double.parse(a[i+1])}​​"​;
  a.removeAt(i);
  a.removeAt(i);
  i--;
  }
  }
 
 if​(a.length != 1) ​throw​ Error();
 
 return​ ​double​.parse(a[0]);
 }
 void​ ​deleteOne​() {
 if​(a.length > 0) {
 if​(a.last.length > 1) {
  a.last = a.last.substring(0, a.last.length-1);
  } ​else​ {
  a.removeLast();
  }
  }
  }
 
 void​ deleteAll() => a = [];
 }

 

 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset