Expression Calc

Addes calculation for expressions + tests, added user input error check for formel functions + tests, small bug fixes
Victor Hans-Georg Waitz 2023-12-27 03:54:24 +01:00
parent 6d8f8e7520
commit f94028b6c8
3 changed files with 298 additions and 37 deletions

View File

@ -1,7 +1,10 @@
package de.hs_mannheim.informatik.spreadsheet; package de.hs_mannheim.informatik.spreadsheet;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner; import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* Part of a simplified spreadsheet system for the PR1 programming lab at Hochschule Mannheim. * Part of a simplified spreadsheet system for the PR1 programming lab at Hochschule Mannheim.
@ -10,14 +13,18 @@ import java.util.Scanner;
*/ */
public class Axel { public class Axel {
static Scanner keyboard = new Scanner(System.in); static Scanner keyboard = new Scanner(System.in);
static Spreadsheet spr = new Spreadsheet(6,8); static Spreadsheet spr = new Spreadsheet(11,11);
public static void main(String[] args) throws FileNotFoundException { public static void main(String[] args) throws FileNotFoundException {
// Spreadsheet spr = new Spreadsheet(6,9); // Spreadsheet spr = new Spreadsheet(6,9);
spr.put("A3", "123"); spr.put("A1", "1");
spr.put("A2", "1"); spr.put("A2", "1");
spr.put("A3", "123");
spr.put("B1", "2");
spr.put("B2", "4");
spr.put("B3", "1");
spr.put("B4", "=41+A2"); spr.put("B4", "=41+A2");
spr.put("C5", "=7*6"); spr.put("C5", "=7*6");
@ -36,6 +43,7 @@ public class Axel {
String userCommandPositionInput = userCommandPositionInput(); String userCommandPositionInput = userCommandPositionInput();
if (userCommandPositionInput.charAt(0) == '*'){ if (userCommandPositionInput.charAt(0) == '*'){
executeCommand(userCommandPositionInput); executeCommand(userCommandPositionInput);
System.out.println(spr);
continue; continue;
} }
String userPositionInput = userCommandPositionInput; String userPositionInput = userCommandPositionInput;
@ -139,11 +147,12 @@ public class Axel {
return true; return true;
} }
//? User input for a value or a formula //? User input for a value or a formula
public static String userValueFormulaInput(){ public static String userValueFormulaInput(){
System.out.println(); System.out.println();
System.out.printf("Input a value of a formula for the selected position.%n"); System.out.printf("Input a value of a formula for the selected position.%n");
System.out.printf("Format for a value: 7 or 1337 %nFormat for a formula: =7*6 or =SUM(A1:A3)%n"); System.out.printf("Format for a value: 7 or 1337 %nFormat for a formula: =7*6 or =SUMME(A1:A3)%n");
System.out.print("Input: "); System.out.print("Input: ");
String userCommandInput = keyboard.nextLine(); String userCommandInput = keyboard.nextLine();
@ -185,17 +194,46 @@ public class Axel {
formulaToCheck = formulaToCheck.toUpperCase().substring(1); formulaToCheck = formulaToCheck.toUpperCase().substring(1);
if (formulaToCheck.startsWith("SUMME(") || formulaToCheck.startsWith("PRODUKT(") || formulaToCheck.startsWith("MITTELWERT(") || formulaToCheck.startsWith("STABW(") || formulaToCheck.startsWith("MIN(") || formulaToCheck.startsWith("MAX(")){ if (formulaToCheck.startsWith("SUMME(") || formulaToCheck.startsWith("PRODUKT(") || formulaToCheck.startsWith("MITTELWERT(") || formulaToCheck.startsWith("STABW(") || formulaToCheck.startsWith("MIN(") || formulaToCheck.startsWith("MAX(")){
return userFormulaFunctionErrorCheck(formulaToCheck);
}
return userFormulaExpressionErrorCheck(formulaToCheck);
}
public static boolean userFormulaFunctionErrorCheck(String functionToCheck){
String[] functionCorners = spr.isolateFunctionCorners(functionToCheck);
String[] functionBlock= spr.wholeFunctionBlock(functionToCheck);
if (functionCorners.length != 2){
return false;
}
for (String functionCorner: functionCorners){
System.out.printf("Corner: %s - Cell test: %s %n", functionCorner, spr.isValueCellName(functionCorner));
if (!(spr.isValueCellName(functionCorner).equals("cellName"))){
return false;
}
if (!userPositionBoundsErrorCheck(functionCorner, spr)){
return false;
}
}
return true; return true;
} }
public static boolean userFormulaExpressionErrorCheck(String expressionToCheck){
return true; return true;
} }
public static boolean userValueErrorCheck(String valueToCheck){ public static boolean userValueErrorCheck(String valueToCheck){
//? true if valid //? true if valid
//? valid inputs are: 7, 1337, 0, ... //? valid inputs are: 7, 1337, 0, , -213,...
String digitCheckRegex = "-?\\d+(\\.\\d+)?"; //? For floating point numbers
// String digitCheckRegex = "-?\\d+(\\.\\d+)?";
//? For integers
String digitCheckRegex = "-?\\d+";
if (!(valueToCheck.matches(digitCheckRegex))){ if (!(valueToCheck.matches(digitCheckRegex))){
return false; return false;
} }
@ -207,5 +245,34 @@ public class Axel {
public static void executeCommand(String command) { public static void executeCommand(String command) {
System.out.printf("Executing command: %s%n", command); System.out.printf("Executing command: %s%n", command);
//TODO ME: //TODO ME:
switch (command){
case "*load":
progLoad();
break;
}
}
private static void progLoad(){
int rows = spr.getRowsLCount();
int cols = spr.getColsCount();
for (int r = 0; r < rows; r++){
for (int c = 0; c < cols; c++){
spr.cells[r][c].setValue("");
}
}
spr.put("A1", "1");
spr.put("A2", "2");
spr.put("A3", "3");
spr.put("A4", "4");
spr.put("B1", "5");
spr.put("B2", "6");
spr.put("B3", "7");
spr.put("B4", "8");
spr.put("K1", "200");
spr.put("K11", "100");
spr.put("J4", "4");
spr.put("I10", "69");
} }
} }

View File

@ -3,6 +3,7 @@ package de.hs_mannheim.informatik.spreadsheet;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -62,18 +63,21 @@ public class Spreadsheet {
} }
public int getRow(String cellName) { public int getRow(String cellName) {
//TODO ME: Implement row numbers with two digits String row = "";
return cellName.charAt(1) - '1'; for (int i = 1; i < cellName.length(); i++){
row += cellName.charAt(i);
}
return Integer.parseInt(row) - 1;
} }
// ----- // -----
// business logic // business logic
/* /**
* A method for reading in data from a CSV file. * A method for reading in data from a CSV file.
* @param path The file to read. * @param path The file to read.
* @param separator The char used to split up the input, e.g. a comma or a semicolon. * @param separator The char used to split up the input, e.g. a comma or a semicolon.
* @param starCellName The upper left cell where data from the CSV file should be inserted. * @param startCellName The upper left cell where data from the CSV file should be inserted.
* @return Nothing. * @return Nothing.
* @exception IOException If path does not exist. * @exception IOException If path does not exist.
*/ */
@ -81,7 +85,7 @@ public class Spreadsheet {
// TODO: implement this // TODO: implement this
} }
/* /**
* A method for saving data to a CSV file. * A method for saving data to a CSV file.
* @param path The file to write. * @param path The file to write.
* @return Nothing. * @return Nothing.
@ -106,16 +110,17 @@ public class Spreadsheet {
out.close(); out.close();
} }
/* /**
* This method does the actual evaluation/calcluation of a specific cell * This method does the actual evaluation/calcluation of a specific cell
* @param cellName the name of the cell to be evaluated * @param col the col of the cell to be evaluated
* @param row the row of the cell to be evaluated
* @return Nothing. * @return Nothing.
*/ */
private void evaluateCell(int row, int col) { private void evaluateCell(int row, int col) {
String formula = cells[row][col].getFormula(); String formula = cells[row][col].getFormula();
String result = ""; String result = "";
isolateFunctionCorners(formula);
if (formula.startsWith("SUMME(")) // e.g. SUMME(A3:A8) if (formula.startsWith("SUMME(")) // e.g. SUMME(A3:A8)
result = "" + sum(formula.substring(6, 8), formula.substring(9, 11)); // TODO adapt to cells with two digits result = "" + sum(formula.substring(6, 8), formula.substring(9, 11)); // TODO adapt to cells with two digits
@ -145,7 +150,43 @@ public class Spreadsheet {
cells[row][col].setValue("" + result); cells[row][col].setValue("" + result);
} }
/* public String[] isolateFunctionCorners(String formula){
//? isolate corners
ArrayList<String> corners = new ArrayList<String>();
Matcher m = Pattern.compile("[A-Z]\\d+").matcher(formula);
while(m.find()) {
String s = m.group();
if (!s.isEmpty()){
corners.add(s);
}
}
return corners.toArray(new String[0]);
}
public String[] wholeFunctionBlock(String formula){
String[] corners = isolateFunctionCorners(formula);
String cornerOne = corners[0];
char colOne = cornerOne.charAt(0);
int rowOne = Integer.parseInt(cornerOne.substring(1));
String cornerTwo = corners[1];
char colTwo = cornerTwo.charAt(0);
int rowTwo = Integer.parseInt(cornerTwo.substring(1));
ArrayList<String> block = new ArrayList<>();
for (int rowInt = rowOne; rowInt <= rowTwo; rowInt++) {
for (char charCol = colOne; charCol <= colTwo; charCol++) {
block.add(String.format("%c%d", charCol, rowInt));
}
}
return block.toArray(new String[0]);
}
/**
* Method for calculating the sum of a rectangular block of cells, such as from A1 to B3. * Method for calculating the sum of a rectangular block of cells, such as from A1 to B3.
* @param startCellName The name of the cell in the upper left corner of the rectangle. * @param startCellName The name of the cell in the upper left corner of the rectangle.
* @param endCellName The name of the cell in the lower right corner of the rectangle. * @param endCellName The name of the cell in the lower right corner of the rectangle.
@ -157,7 +198,7 @@ public class Spreadsheet {
return 0; return 0;
} }
/* /**
* This method calculates the result of a "normal" algebraic expression. It only needs to support * This method calculates the result of a "normal" algebraic expression. It only needs to support
* expressions like =B4 or =2+A3-B2, i.e. only with int numbers and other cells and with plus, * expressions like =B4 or =2+A3-B2, i.e. only with int numbers and other cells and with plus,
* minus, times, split only. An expression always starts with either a number or a cell name. If it * minus, times, split only. An expression always starts with either a number or a cell name. If it
@ -166,24 +207,97 @@ public class Spreadsheet {
* @param formula The expression to be evaluated. * @param formula The expression to be evaluated.
* @return The result calculated. * @return The result calculated.
*/ */
private long calculate(String formula) throws ArithmeticException { public long calculate(String formula) throws ArithmeticException {
Matcher m = Pattern.compile("([A-Z][0-9]*)|[-\\+\\*/]|[0-9]*").matcher(formula);
long res = 0; long res = 0;
// TODO implement // TODO implement
// uncomment the following to see an example how the elements of a formula can be accessed ArrayList<String> formulaElements = extractExpressionElements(formula);
while (m.find()) { // m.find() must always be used before m.group()
String s = m.group(); String firstElement = formulaElements.get(0);
if (!s.isEmpty()) {
System.out.println(s); if (isValueCellName(firstElement).equals("cellName")){
String cellValue = get(firstElement);
res = cellValue.isEmpty() ? 0 : Long.parseLong(cellValue);
} }
else {
res = Long.parseLong(firstElement);
}
for (int i = 1; i < formulaElements.size(); i = i+2) {
res = singleCalculation(res, formulaElements.get(i+1), formulaElements.get(i));
} }
return res; return res;
} }
public static ArrayList<String> extractExpressionElements(String formula){
Matcher m = Pattern.compile("([A-Z][0-9]*)|[-\\+\\*/]|[0-9]*").matcher(formula);
ArrayList<String> formulaElements = new ArrayList<String>();
while (m.find()) {
String s = m.group();
if (!s.isEmpty()) {
formulaElements.add(s);
}
}
return formulaElements;
}
private long singleCalculation(long res, String element, String operator){
String elementType = isValueCellName(element);
if (elementType.equals("cellName")){
element = get(element);
}
long value = element.isEmpty() ? 0 : Long.parseLong(element);
switch (operator) {
case "+":
res += value;
break;
case "-":
res -= value;
break;
case "*":
res *= value;
break;
case "/":
if (value == 0){
throw new ArithmeticException("Division by zero");
}
res /= value;
break;
}
return res;
}
private void emptyCellZero(){
}
public String isValueCellName(String sToCheck){
//? 7 -> "value", A1 -> "cellName", 7A -> "invalid"
//? For floating point numbers
// String digitCheckRegex = "-?\\d+(\\.\\d+)?";
//? For integers
String digitCheckRegex = "-?\\d+";
if (sToCheck.matches(digitCheckRegex)){
return "value";
}
//? For cell names
String cellNameCheckRegex = "[A-Z][0-9]*";
if (sToCheck.matches(cellNameCheckRegex)){
return "cellName";
}
return "invalid";
}
// ----- // -----
public int getRowsLCount() { public int getRowsLCount() {

View File

@ -2,12 +2,14 @@ package de.hs_mannheim.informatik.spreadsheet;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class AxelTest { class AxelTest {
@org.junit.jupiter.api.Test // --------- Class Vars ---------
@org.junit.jupiter.api.DisplayName("User command or position input") Spreadsheet testspr = new Spreadsheet(11,11);
void userCommandPositionComputation() {
}
// --------- Input tests ---------
@org.junit.jupiter.api.Test @org.junit.jupiter.api.Test
@org.junit.jupiter.api.DisplayName("Command input error check") @org.junit.jupiter.api.DisplayName("Command input error check")
@ -24,7 +26,6 @@ class AxelTest {
} }
} }
@org.junit.jupiter.api.Test @org.junit.jupiter.api.Test
@org.junit.jupiter.api.DisplayName("Position input error check") @org.junit.jupiter.api.DisplayName("Position input error check")
void userPositionErrorCheck() { void userPositionErrorCheck() {
@ -56,11 +57,6 @@ class AxelTest {
} }
@org.junit.jupiter.api.Test
@org.junit.jupiter.api.DisplayName("User value or formula input")
void userValueFormulaComputation() {
}
@org.junit.jupiter.api.Test @org.junit.jupiter.api.Test
@org.junit.jupiter.api.DisplayName("Value input error check") @org.junit.jupiter.api.DisplayName("Value input error check")
void userValueErrorCheck() { void userValueErrorCheck() {
@ -80,4 +76,88 @@ class AxelTest {
@org.junit.jupiter.api.DisplayName("Formula input error check") @org.junit.jupiter.api.DisplayName("Formula input error check")
void userFormulaErrorCheck() { void userFormulaErrorCheck() {
} }
// --------- Calculation tests ---------
@org.junit.jupiter.api.Test
@org.junit.jupiter.api.DisplayName("Normal calculation")
void calculate() {
putTestSpr(testspr);
String[] successTestList = {"=2*3", "=2+3", "=5-2", "=6/2", "7/2", "101/2", "=5+6-10*2", "=4-3+50-1/2", "=A1", "=K11", "=A1+A2+A3+A4", "=K11/2/5*2-15", "=a1+A2*a4-6+E7", "=a2-A1+J4-3"};
long[] successTestResult = {6, 5, 3, 3, 3, 50, 2, 25, 1, 100, 10, 5, 6, 2};
String[] failureTestList = {"5+1", "7-4", "6*2", "15/5", "5+4", "10-7", "5*3", "9/3", "=A1", "=K11", "=A1+A3", "=K11-10/2+B1"};
long[] failureTestResult = {5, 7, 10, 4, 8, 4, 16, 2, 2, 200, 3, 40};
for (int i = 0; i < successTestList.length; i++) {
// Uppercasing the formula happens in the code too, just in a different place which is not tested
String formula = successTestList[i].toUpperCase();
assertEquals(successTestResult[i], testspr.calculate(formula));
}
for (int i = 0; i < failureTestList.length; i++) {
String formula = failureTestList[i].toUpperCase();
assertNotEquals(failureTestResult[i], testspr.calculate(failureTestList[i]));
}
}
@org.junit.jupiter.api.Test
@org.junit.jupiter.api.DisplayName("Is CellName")
void isValueCellName(){
String[] successValueTestList = {"7", "0", "1", "420", "424", "23212"};
String[] successCellNameTestList = {"A1", "K11", "B6", "g5", "k11"};
for (String successValue : successValueTestList){
successValue = successValue.toUpperCase();
assertEquals("value", testspr.isValueCellName(successValue));
}
for (String successCellName : successCellNameTestList){
successCellName = successCellName.toUpperCase();
assertEquals("cellName", testspr.isValueCellName(successCellName));
}
}
@org.junit.jupiter.api.Test
@org.junit.jupiter.api.DisplayName("Isolate Corner")
void isolateFunctionCorners(){
String[] successTestList = {"SUMME(A1:B3)", "SUMME(a3:k5)", "PRODUKT(C3:G6)", "PRODUKT(c2:g5)", "MITTELWERT(A2:j9)", "mittelwert(b4:H8)", "STABW(c7:g7)", "MIN(a1:A6)", "max(f4:h7)", "MAX(E2:j7)"};
String[][] successResultList = {{"A1", "B3"}, {"A3", "K5"}, {"C3", "G6"}, {"C2", "G5"}, {"A2", "J9"}, {"B4", "H8"}, {"C7", "G7"}, {"A1", "A6"}, {"F4", "H7"}, {"E2", "J7"}};
for (int i = 0; i < successTestList.length; i++){
String formula = successTestList[i].toUpperCase();
assertArrayEquals(successResultList[i], testspr.isolateFunctionCorners(formula));
}
}
@org.junit.jupiter.api.Test
@org.junit.jupiter.api.DisplayName("Extract whole block")
void wholeFunctionBlock(){
String[] successTestList = {"SUMME(A1:C4)", "summe(F4:H7)", "Max(D4:D7)", "min(a1:d1)"};
String[][] successResultList = {{"A1", "B1", "C1", "A2", "B2", "C2", "A3", "B3", "C3", "A4", "B4", "C4"}, {"F4", "G4", "H4", "F5", "G5", "H5", "F6", "G6", "H6", "F7", "G7", "H7"}, {"D4", "D5", "D6", "D7"}, {"A1", "B1", "C1", "D1"}};
for (int i = 0; i < successTestList.length; i++){
String testElement = successTestList[i].toUpperCase();
assertArrayEquals(successResultList[i], testspr.wholeFunctionBlock(testElement));
}
}
private void putTestSpr(Spreadsheet testspr){
testspr.put("A1", "1");
testspr.put("A2", "2");
testspr.put("A3", "3");
testspr.put("A4", "4");
testspr.put("B1", "5");
testspr.put("B2", "6");
testspr.put("B3", "7");
testspr.put("B4", "8");
testspr.put("K1", "200");
testspr.put("K11", "100");
testspr.put("J4", "4");
testspr.put("I10", "69");
}
} }