From f94028b6c815174d1df3806b020090b0d1413387 Mon Sep 17 00:00:00 2001 From: 3013050 <3013050@stud.hs-mannheim.de> Date: Wed, 27 Dec 2023 03:54:24 +0100 Subject: [PATCH] Expression Calc Addes calculation for expressions + tests, added user input error check for formel functions + tests, small bug fixes --- .../informatik/spreadsheet/Axel.java | 81 ++++++++- .../informatik/spreadsheet/Spreadsheet.java | 154 +++++++++++++++--- .../informatik/spreadsheet/AxelTest.java | 100 ++++++++++-- 3 files changed, 298 insertions(+), 37 deletions(-) diff --git a/Axel/src/de/hs_mannheim/informatik/spreadsheet/Axel.java b/Axel/src/de/hs_mannheim/informatik/spreadsheet/Axel.java index 8e2ab5d..c54d863 100644 --- a/Axel/src/de/hs_mannheim/informatik/spreadsheet/Axel.java +++ b/Axel/src/de/hs_mannheim/informatik/spreadsheet/Axel.java @@ -1,7 +1,10 @@ package de.hs_mannheim.informatik.spreadsheet; import java.io.FileNotFoundException; +import java.util.ArrayList; 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. @@ -10,15 +13,19 @@ import java.util.Scanner; */ public class Axel { 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 { // Spreadsheet spr = new Spreadsheet(6,9); + spr.put("A1", "1"); + spr.put("A2", "1"); spr.put("A3", "123"); - spr.put("A2", "1"); - + spr.put("B1", "2"); + spr.put("B2", "4"); + spr.put("B3", "1"); + spr.put("B4", "=41+A2"); spr.put("C5", "=7*6"); spr.put("D1", "=3/2"); @@ -36,6 +43,7 @@ public class Axel { String userCommandPositionInput = userCommandPositionInput(); if (userCommandPositionInput.charAt(0) == '*'){ executeCommand(userCommandPositionInput); + System.out.println(spr); continue; } String userPositionInput = userCommandPositionInput; @@ -139,11 +147,12 @@ public class Axel { return true; } + //? User input for a value or a formula public static String userValueFormulaInput(){ System.out.println(); 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: "); String userCommandInput = keyboard.nextLine(); @@ -185,17 +194,46 @@ public class Axel { formulaToCheck = formulaToCheck.toUpperCase().substring(1); if (formulaToCheck.startsWith("SUMME(") || formulaToCheck.startsWith("PRODUKT(") || formulaToCheck.startsWith("MITTELWERT(") || formulaToCheck.startsWith("STABW(") || formulaToCheck.startsWith("MIN(") || formulaToCheck.startsWith("MAX(")){ - return true; + 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; + } + + public static boolean userFormulaExpressionErrorCheck(String expressionToCheck){ + return true; } public static boolean userValueErrorCheck(String valueToCheck){ //? 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))){ return false; } @@ -207,5 +245,34 @@ public class Axel { public static void executeCommand(String command) { System.out.printf("Executing command: %s%n", command); //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"); + } } \ No newline at end of file diff --git a/Axel/src/de/hs_mannheim/informatik/spreadsheet/Spreadsheet.java b/Axel/src/de/hs_mannheim/informatik/spreadsheet/Spreadsheet.java index eecdd09..ed73771 100644 --- a/Axel/src/de/hs_mannheim/informatik/spreadsheet/Spreadsheet.java +++ b/Axel/src/de/hs_mannheim/informatik/spreadsheet/Spreadsheet.java @@ -3,6 +3,7 @@ package de.hs_mannheim.informatik.spreadsheet; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -62,18 +63,21 @@ public class Spreadsheet { } public int getRow(String cellName) { - //TODO ME: Implement row numbers with two digits - return cellName.charAt(1) - '1'; + String row = ""; + for (int i = 1; i < cellName.length(); i++){ + row += cellName.charAt(i); + } + return Integer.parseInt(row) - 1; } // ----- // business logic - /* + /** * A method for reading in data from a CSV file. * @param path The file to read. * @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. * @exception IOException If path does not exist. */ @@ -81,7 +85,7 @@ public class Spreadsheet { // TODO: implement this } - /* + /** * A method for saving data to a CSV file. * @param path The file to write. * @return Nothing. @@ -106,16 +110,17 @@ public class Spreadsheet { out.close(); } - /* + /** * 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. */ private void evaluateCell(int row, int col) { String formula = cells[row][col].getFormula(); String result = ""; - + isolateFunctionCorners(formula); 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 @@ -145,7 +150,43 @@ public class Spreadsheet { cells[row][col].setValue("" + result); } - /* + public String[] isolateFunctionCorners(String formula){ + //? isolate corners + ArrayList corners = new ArrayList(); + + 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 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. * @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. @@ -157,7 +198,7 @@ public class Spreadsheet { return 0; } - /* + /** * 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, * 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. * @return The result calculated. */ - private long calculate(String formula) throws ArithmeticException { - Matcher m = Pattern.compile("([A-Z][0-9]*)|[-\\+\\*/]|[0-9]*").matcher(formula); - + public long calculate(String formula) throws ArithmeticException { long res = 0; - // TODO implement - // uncomment the following to see an example how the elements of a formula can be accessed - while (m.find()) { // m.find() must always be used before m.group() - String s = m.group(); - if (!s.isEmpty()) { - System.out.println(s); - } + ArrayList formulaElements = extractExpressionElements(formula); + + String firstElement = formulaElements.get(0); + + 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; } + public static ArrayList extractExpressionElements(String formula){ + Matcher m = Pattern.compile("([A-Z][0-9]*)|[-\\+\\*/]|[0-9]*").matcher(formula); + + ArrayList formulaElements = new ArrayList(); + + 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() { diff --git a/Axel/tests/de/hs_mannheim/informatik/spreadsheet/AxelTest.java b/Axel/tests/de/hs_mannheim/informatik/spreadsheet/AxelTest.java index 474394b..48ccaba 100644 --- a/Axel/tests/de/hs_mannheim/informatik/spreadsheet/AxelTest.java +++ b/Axel/tests/de/hs_mannheim/informatik/spreadsheet/AxelTest.java @@ -2,12 +2,14 @@ package de.hs_mannheim.informatik.spreadsheet; import static org.junit.jupiter.api.Assertions.*; + class AxelTest { - @org.junit.jupiter.api.Test - @org.junit.jupiter.api.DisplayName("User command or position input") - void userCommandPositionComputation() { - } + // --------- Class Vars --------- + Spreadsheet testspr = new Spreadsheet(11,11); + + + // --------- Input tests --------- @org.junit.jupiter.api.Test @org.junit.jupiter.api.DisplayName("Command input error check") @@ -24,7 +26,6 @@ class AxelTest { } } - @org.junit.jupiter.api.Test @org.junit.jupiter.api.DisplayName("Position input error check") 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.DisplayName("Value input error check") void userValueErrorCheck() { @@ -80,4 +76,88 @@ class AxelTest { @org.junit.jupiter.api.DisplayName("Formula input error check") 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"); + } } \ No newline at end of file