package de.hs_mannheim.informatik.spreadsheet; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.ArrayList; /** * A simplified spreadsheet class for the PR1 programming lab at Hochschule Mannheim. * One aspect worth mentioning is that it only supports long numbers, not doubles. *p * @author Oliver Hummel */ public class Spreadsheet { Cell[][] cells; /** * Constructor that creates a Spreadsheet of size rows * cols. * @param rows number of rows * @param cols number of columns */ public Spreadsheet(int rows, int cols) { // TODO limit the maximum size on 99 (1..99) rows and 26 (A..Z) columns if (rows > 99) { rows = 99; } else if (rows <= 0) { rows = 1; } if (cols > 26) { cols = 26; } else if (cols <= 0) { cols = 1; } cells = new Cell[rows][cols]; for (int r = 0; r < rows; r++) for (int c = 0; c < cols; c++) cells[r][c] = new Cell(); } // ----- // retrieve or change values of cells private String get(int row, int col) { return cells[row][col].getValue(); } public String get(String cellName) { cellName = cellName.toUpperCase(); return get(getRow(cellName), getCol(cellName)); } private void put(int row, int col, String value) { if (!value.startsWith("=")) cells[row][col].setValue(value); else { cells[row][col].setFormula(value); evaluateCell(row, col); } } public void put(String cellName, String value) { cellName = cellName.toUpperCase(); put(getRow(cellName), getCol(cellName), value); } private int getCol(String cellName) { int merker = 0; for (int i = 0; i < cellName.length(); i++) { if (cellName.charAt(i) >= 'A' && cellName.charAt(i) <= 'Z') { merker = (merker * 10) + (cellName.charAt(i) - ('A'-1)); } else { break; } } return merker-1; } private int getRow(String cellName) { int merker = 0; for (int i = 0; i < cellName.length(); i++) { if (!(cellName.charAt(i) >= 'A' && cellName.charAt(i) <= 'Z')) { merker = (merker * 10) + (cellName.charAt(i) - '0'); } } return merker-1; } /** * Hier wird überprüft, ob die Zelle existiert. * @param a Name der Zelle * @return true = Zelle existiert, false = Zelle existent nicht (zumindest nicht in der Tabelle) */ public boolean zelleneingabe_pruefen(String a) { if (a.length() < 2) { //es soll eine einzelne Zelle angesprochen werde, also muss dafür die länge >2 sein return false; } if (getRow(a) < 0 || getRow(a) >= cells.length) { //überprüfung, ob die eingabe in die rows länge passt return false; } if (getCol(a) < 0 || getCol(a) >= cells[0].length) { //überprüfung, ob die eingabe in die Columns länge passt return false; } return true; } // ----- // 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 startCellName The upper left cell where data from the CSV file should be inserted. * @return Nothing. * @exception IOException If path does not exist. */ public void readCsv(String path, char separator, String startCellName) throws FileNotFoundException { //implement this ArrayList lines = readFile(path); String[] einzelnde_lines = new String[lines.size()]; int row_startcell = getRow(startCellName); int col_startcell = getCol(startCellName); String[][] zwischenspeicher = new String[cells.length - row_startcell][cells[0].length - col_startcell]; for (int i = 0; i < einzelnde_lines.length; i++) { zwischenspeicher[i] = lines.get(i).split(separator+""); // +"" um aus dem Char ein String zu machen for (int j = 0; j < zwischenspeicher[i].length; j++) { put(row_startcell+i,col_startcell+j,zwischenspeicher[i][j]); } } } /** * Liest eine csb Datei ein und speichert sie in eine ArrayList * @param path von der Datei * @return Liste mit den gespeicherten Zeilen von der csv Datei * @throws FileNotFoundException */ public static ArrayList readFile(String path) throws FileNotFoundException { ArrayList lines = new ArrayList<>(); Scanner sc = new Scanner(new File(path)); while (sc.hasNextLine()) { lines.add(sc.nextLine()); } sc.close(); return lines; } /** * A method for saving data to a CSV file. * @param path The file to write. * @return Nothing. * @exception IOException If path does not exist. */ public void saveCsv(String path) throws FileNotFoundException { PrintWriter out = new PrintWriter(path); for (Cell[] row : cells) { for (Cell cell : row) { if (!cell.getFormula().isEmpty()) out.print("=" + cell.getFormula()); else out.print(cell.getValue()); if (cell != row[cells[0].length-1]) // "[0]" -> nimmt sonst die falsche Länge! out.print(","); } out.println(); } out.close(); } /** * This method does the actual evaluation/calcluation of a specific cell * @param cellName the name of the cell to be evaluated * @return Nothing. */ private void evaluateCell(int row, int col) { String formula = cells[row][col].getFormula(); String result = ""; 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 else if (formula.startsWith("PRODUKT(")) // e.g. PRODUKT(A3:B9) result = "" + pro(formula.substring(8, 10), formula.substring(11, 13)); else if (formula.startsWith("MITTELWERT(")) // e.g. MITTELWERT(A3:A5) result = "" + mittelwert(formula.substring(11, 13), formula.substring(14, 16)); else if (formula.startsWith("STABW(")) // e.g. STABW(C6:D8) -> Standardabweichung result = "" + stabw(formula.substring(6, 8), formula.substring(9, 11)); else if (formula.startsWith("MIN(")) // e.g. MIN(C13:H13) -> kleinster Wert result = "" + min(formula.substring(4, 6), formula.substring(7, 9)); else if (formula.startsWith("MAX(")) // e.g. MAX(A1:A10) -> größter Wert result = "" + max(formula.substring(4, 6), formula.substring(7, 9)); else if (!formula.isEmpty()) { try { result = "" + calculate(formula); //geht nicht? } catch(ArithmeticException ae) { result = "exc."; } } cells[row][col].setValue("" + result); } /** * Berechnet die Standardabweichung * @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. * @return Ergebnis bzw. die Standardabweichung */ private int stabw(String startCellName, String endCellName) { int startCellRow = getRow(startCellName); int startCellCol = getCol(startCellName); int endCellRow = getRow(endCellName); int endCellCol = getCol(endCellName); int res = 0; int merker = 0; int anzahl = 0; int mittelwert = (int) (mittelwert(startCellName, endCellName)); //System.out.println("Mittelwert: "+mittelwert); for (int i = startCellRow; i <= endCellRow; i++) { for (int j = startCellCol; j <= endCellCol; j++) { merker = Integer.parseInt(cells[i][j].getValue()); if (merker <= mittelwert) { res = res + (mittelwert-merker)*(mittelwert-merker); //System.out.println((mittelwert-merker)); } else { res = res + (merker - mittelwert)*(merker - mittelwert); //System.out.println((merker - mittelwert)); } anzahl++; } } //System.out.println(res); //System.out.println(anzahl); res = res/(anzahl-1); // oder: res = res/(anzahl-1); res = (int) Math.sqrt(res); return res; } /** * 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. * @return The sum calculated. */ private long sum(String startCellName, String endCellName) { int startCellRow = getRow(startCellName); int startCellCol = getCol(startCellName); int endCellRow = getRow(endCellName); int endCellCol = getCol(endCellName); long res = 0; for (int i = startCellRow; i <= endCellRow; i++) { for (int j = startCellCol; j <= endCellCol; j++) { res = res + (Integer.parseInt(cells[i][j].getValue())); } } return res; } /** * Rechnet das Produkt aus den Zellen von einem Viereck. * @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. * @return Das Ergebnis. */ private long pro(String startCellName, String endCellName) { int startCellRow = getRow(startCellName); int startCellCol = getCol(startCellName); int endCellRow = getRow(endCellName); int endCellCol = getCol(endCellName); long res = 0; for (int i = startCellRow; i <= endCellRow; i++) { for (int j = startCellCol; j <= endCellCol; j++) { if (res == 0) { res = Integer.parseInt(cells[i][j].getValue()); } else { res = res * (Integer.parseInt(cells[i][j].getValue())); } } } return res; } /** * Gibt die kleinste Zahl aus den Zellen von einem Viereck. * @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. * @return Kleinste Zahl */ private long min(String startCellName, String endCellName) { int startCellRow = getRow(startCellName); int startCellCol = getCol(startCellName); int endCellRow = getRow(endCellName); int endCellCol = getCol(endCellName); long res = Long.MAX_VALUE; long merker; for (int i = startCellRow; i <= endCellRow; i++) { for (int j = startCellCol; j <= endCellCol; j++) { merker = Integer.parseInt(cells[i][j].getValue()); if (merker < res) { res = merker; } } } return res; } /** * Gibt die größte Zahl aus den Zellen von einem Viereck. * @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. * @return größte Zahl */ private long max(String startCellName, String endCellName) { int startCellRow = getRow(startCellName); int startCellCol = getCol(startCellName); int endCellRow = getRow(endCellName); int endCellCol = getCol(endCellName); long res = Long.MIN_VALUE; long merker; for (int i = startCellRow; i <= endCellRow; i++) { for (int j = startCellCol; j <= endCellCol; j++) { merker = Integer.parseInt(cells[i][j].getValue()); if (merker > res) { res = merker; } } } return res; } /** * Berechnet den Durchschnitt aus den Zellen von einem Viereck. (Summe von allen geteilt durch die Anzahl der Zellen) * @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. * @return Durchschnitt */ private long mittelwert(String startCellName, String endCellName) { int startCellRow = getRow(startCellName); int startCellCol = getCol(startCellName); int endCellRow = getRow(endCellName); int endCellCol = getCol(endCellName); long anzahl = 0; long res = 0; for (int i = startCellRow; i <= endCellRow; i++) { for (int j = startCellCol; j <= endCellCol; j++) { res = res + (Integer.parseInt(cells[i][j].getValue())); anzahl++; } } res = res/anzahl; return res; } /** * 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 * continues, it is guaranteed that this is followed by an operator and either a number or a * cell name again. It is NOT required to implement dot before dash or parentheses in formulas. * @param formula The expression to be evaluated. * @return The result calculated. */ public long calculate(String formula) throws ArithmeticException { Matcher m = Pattern.compile("([A-Z][0-9]*)|[-\\+\\*/]|[0-9]*").matcher(formula); long res = 0; // TODO implement int merker = 0; int zaehler = 0; int neue_zahl = 0; // 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); zaehler++; switch (zaehler){ case 1: try { res = Integer.parseInt(s); }catch (Exception ignored){ res = Integer.parseInt(get(s)); } break; case 2: switch(s) { case "+": merker = 1; break; case "-": merker = 2; break; case "*": merker = 3; break; case "/": merker = 4; break; default: System.out.println("ERROR"); break; } break; case 3: try { neue_zahl = Integer.parseInt(s); }catch (Exception ignored){ neue_zahl = Integer.parseInt(get(s)); } res = rechne(res, merker, neue_zahl); zaehler = 1; break; } } } return res; } /** * Berechnet 2 beliebige Zahlen, plus, minus, mal oder geteilt. (muss man angeben) * @param auswahl hiermit wird bestimmt welche Rechenart benutzt werden soll. * @param a erste Zahl * @param b zweite Zahl * @return Ergebnis */ private long rechne(long a, int auswahl, int b) { switch(auswahl){ case 1: return a + b; case 2: return a - b; case 3: return a * b; case 4: return a / b; default: System.out.println("ERROR"); break; } return 0; } // ----- public String toString() { StringBuilder sb = new StringBuilder(); sb.append(" "); for (int i = 0; i < cells[0].length; i++) { // "[0]" -> nimmt sonst die falsche Länge! sb.append(" " + (char)('A'+ i) + " | "); } int rc = 1; for (Cell[] r : cells) { sb.append(System.lineSeparator()); sb.append(String.format("%2s", rc++) + ": "); for (Cell c : r) { sb.append(c + " | "); } } return sb.toString(); } }