GRAMMATICHE, ESPRESSIONI REGOLARI BFN, RPN Integrazione alle pagine del libro Appunti di informatica per la classe 4° - articolazione informatica Prof. G. Malafronte Espressioni Regolari ........................................................................................................................................... 3 Teoria ............................................................................................................................................................. 3 Strumenti per il testing (on line) ................................................................................................................... 3 Strumenti per il testing (locale) ..................................................................................................................... 3 Links utili sulle espressioni regolai in Java ..................................................................................................... 3 Regex Quick reference................................................................................................................................... 3 Espressioni regolari in Java ............................................................................................................................ 4 Common matching symbols ...................................................................................................................... 4 Meta characters......................................................................................................................................... 4 Quantifier................................................................................................................................................... 5 Grouping and back reference .................................................................................................................... 6 Negative look ahead .................................................................................................................................. 7 Specifying modes inside the regular expression ....................................................................................... 7 Backslashes in Java .................................................................................................................................... 7 Using Regular Expressions with String.matches() ..................................................................................... 7 Pattern and Matcher ................................................................................................................................. 9 Java Regex Examples ................................................................................................................................... 10 Or ............................................................................................................................................................. 10 Phone number ......................................................................................................................................... 11 Check for a certain number range ........................................................................................................... 11 Email validation ........................................................................................................................................... 12 Esercizi ......................................................................................................................................................... 13 BNF .................................................................................................................................................................. 13 Teoria ........................................................................................................................................................... 13 Esempi di Notazioni BNF per il linguaggio Java ........................................................................................... 13 variable_declaration ................................................................................................................................ 13 variable_declarator ................................................................................................................................. 14 Ciclo for .................................................................................................................................................... 14 Un esempio di parser che riconosce una grammatica BNF ......................................................................... 14 RPN .................................................................................................................................................................. 16 Teoria ........................................................................................................................................................... 16 Dijkstra’s “shunting yard” algorithm ........................................................................................................... 16 The Shunting Yard.................................................................................................................................... 17 Examples .............................................................................................................................................. 17 Summary of the Rules ......................................................................................................................... 21 Shunting yard algorithm - pseudocode ............................................................................................... 22 Operator Associativity ......................................................................................................................... 23 // Classe ReversePolishNotation ................................................................................................................. 23 // Classe RPNCalc......................................................................................................................................... 25 Un Parser complesso ............................................................................................................................... 28 https://github.com/uklimaschewski/EvalEx............................................................................................ 28 Calcoli scientifici e monetari di elevata precisione in Java: classe BigDecimal ........................................... 28 Links sui BigDecimals ............................................................................................................................... 28 Perché BigDecimal? ................................................................................................................................. 28 Esempio 1 ............................................................................................................................................ 28 A tutorial on BigDecimal .......................................................................................................................... 29 Rounding and Scaling .......................................................................................................................... 30 Enum Constant Summary .................................................................................................................... 30 MathContext........................................................................................................................................ 31 Immutability and Arithmetic ............................................................................................................... 32 Comparison.......................................................................................................................................... 32 Progetto: Una calcolatrice scientifica .............................................................................................................. 33 Le funzioni di base della calcolatrice di Windows ....................................................................................... 35 Il grafico di funzione .................................................................................................................................... 35 Metodo diretto ........................................................................................................................................ 35 Utilizzo della classe Draw di Sedgewick e Wayne ................................................................................... 37 Interfaccia DrawListener.java .............................................................................................................. 37 Classe Draw.java .................................................................................................................................. 37 Gestione dei tasti della tastiera ................................................................................................................... 40 Links di riferimento .................................................................................................................................. 40 Esempio applicativo ................................................................................................................................. 41 Valutazione .................................................................................................................................................. 46 Espressioni Regolari Teoria Da pag. 55 a pag. 62 del Libro AQA A2 Come visto sul libro di teoria un’espressione regolare1 è un linguaggio che un automa a stati fini è in grado di accettare. https://en.wikipedia.org/wiki/Regular_expression On line sono presenti diversi strumenti che permettono di studiare e gestire espressioni regolari anche complesse. Ad esempio: Strumenti per il testing (on line) http://www.regexlib.com/ : qui andando sulla sezione “browse” si possono trovare diverse espressioni regolari utili. https://myregextester.com/index.php https://regex101.com/ Con questi link si può testare un’espressione regolare in diversi linguaggi di programmazione. Ad esempio per verificare una email ben formata si può utilizzare l’espressione regolare: ^[_A-Za-z0-9-\+]+(\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9]+)*(\.[A-Za-z]{2,})$ Strumenti per il testing (locale) Per testare le espressioni regolari si può usare anche il programma Notepad++ Links utili sulle espressioni regolai in Java http://www.tutorialspoint.com/java/java_regular_expressions.htm http://www.vogella.com/tutorials/JavaRegularExpressions/article.html http://www.journaldev.com/634/java-regular-expression-tutorial-with-examples https://docs.oracle.com/javase/tutorial/essential/regex/index.html http://www.mkyong.com/regular-expressions/how-to-validate-email-address-with-regular-expression/ http://stackoverflow.com/questions/25030857/java-code-for-regex-of-email-validation http://stackoverflow.com/questions/8204680/java-regex-email Regex Quick reference https://msdn.microsoft.com/en-us/library/az24scfc(v=vs.110).aspx 1 Le espressioni regolari sono talvolta erroneamente confuse con le regex, tuttavia la maggior parte delle librerie regex disponibili nei principali linguaggi di programmazione sono ben più potenti delle espressioni regolari (https://en.wikipedia.org/wiki/Regular_expression#Patterns_for_non-regular_languages ). Le librerie regex che studieremo permettono di descrivere pattern grammaticali più complessi di quelli descrivibili da un automa a stati finiti (deterministico o non deterministico). http://www.greenend.org.uk/rjk/tech/regexp.html http://www.rexegg.com/regex-quickstart.html Espressioni regolari in Java Esempi tratti da http://www.vogella.com/tutorials/JavaRegularExpressions/article.html Common matching symbols Regular Expression Description . Matches any character except newline ^regex Finds regex that must match at the beginning of the line. regex$ Finds regex that must match at the end of the line. [abc] Set definition, can match the letter a or b or c. [abc][vz] Set definition, can match a or b or c followed by either v or z. [^abc] When a caret appears as the first character inside square brackets, it negates the pattern. This ccontent/an match any character except a or b or c. [a-d1-7] Ranges: matches a letter between a and d and figures from 1 to 7, but not d1. X|Z Finds X or Z. XZ Finds X directly followed by Z. $ Checks if a line end follows. Meta characters The following meta characters have a pre-defined meaning and make certain common patterns easier to use, e.g., \d instead of [0..9] . Regular Expression Description \d Any digit, short for [0-9] \D A non-digit, short for [^0-9] \s A whitespace character, short for [ \t\n\x0b\r\f] \S A non-whitespace character, short for [^\s] \w A word character, short for [a-zA-Z_0-9] \W A non-word character [^\w] \S+ Several non-whitespace characters \b Matches a word boundary where a word character is [a-zA-Z0-9_] . Quantifier Regular Expression Description Examples * Occurs zero or more times, is short for {0,} X* finds no or several letter X, .* finds any character sequence + Occurs one or more times, is short for {1,} X+ - Finds one or several letter X ? Occurs no or one times, ? is short for {0,1} . X? finds no or exactly one letter X {X} Occurs X number of times, {} describes the order of the preceding liberal \d{3} searches for three digits, .{10} for any {X,Y} *? Occurs between X and Y times, character sequence of length 10. \d{1,4} means \d must occur at least once and at a maximum of four. ? after a quantifier makes it a reluctant quantifier. It tries to find the smallest match. This makes the regular expression stop at the first match. Grouping and back reference You can group parts of your regular expression. In your pattern you group elements with round brackets, e.g., () . This allows you to assign a repetition operator to a complete group. In addition these groups also create a back reference to the part of the regular expression. This captures the group. A back reference stores the part of the String which matched the group. This allows you to use this part in the replacement. Via the $ you can refer to a group. $1 is the first group, $2 the second, etc. Let's, for example, assume you want to replace all whitespace between a letter followed by a point or a comma. This would involve that the point or the comma is part of the pattern. Still it should be included in the result. // Removes whitespace between a word character and . or , String pattern = "(\\w)(\\s+)([\\.,])"; System.out.println(EXAMPLE_TEST.replaceAll(pattern, "$1$3")); This example extracts the text between a title tag. // Extract the text between the two title elements pattern = "(?i)(<title.*?>)(.+?)(</title>)"; String updated = EXAMPLE_TEST.replaceAll(pattern, "$2"); Negative look ahead Negative look ahead provides the possibility to exclude a pattern. With this you can say that a string should not be followed by another string. Negative look ahead are defined via (?!pattern) . For example, the following will match "a" if "a" is not followed by "b". a(?!b) Specifying modes inside the regular expression You can add the mode modifiers to the start of the regex. To specify multiple modes, simply put them together as in (?ismx). (?i) makes the regex case insensitive. (?s) for "single line mode" makes the dot match all characters, including line breaks. (?m) for "multi-line mode" makes the caret and dollar match at the start and end of each line in the subject string. Backslashes in Java The backslash \ is an escape character in Java Strings. That means backslash has a predefined meaning in Java. You have to use double backslash \\ to define a single backslash. If you want to define \w, then you must be using \\w in your regex. If you want to use backslash as a literal, you have to type \\\\ as \ is also an escape character in regular expressions. Using Regular Expressions with String.matches() Strings in Java have built-in support for regular expressions. Strings have four built-in methods for regular expressions, i.e., the matches() , split()) , replaceFirst() and replaceAll() methods. The replace() method does NOT support regular expressions. These methods are not optimized for performance. We will later use classes which are optimized for performance. Method Description s.matches("regex") Evaluates if "regex" matches s . Returns only true if the WHOLE string can be matched. s.split("regex") Creates an array with substrings of s divided at occurrence of "regex" . "regex" is not included in the result. s.replaceFirst("regex"), "replacement" Replaces first occurance of "regex" with "replacement . s.replaceAll("regex"), "replacement" Replaces all occurances of "regex" with "replacement . Create for the following example the Java project de.vogella.regex.test . package de.vogella.regex.test; public class RegexTestStrings { public static final String EXAMPLE_TEST = "This is my small example " + "string which I'm going to " + "use for pattern matching."; public static void main(String[] args) { System.out.println(EXAMPLE_TEST.matches("\\w.*")); String[] splitString = (EXAMPLE_TEST.split("\\s+")); System.out.println(splitString.length);// should be 14 for (String string : splitString) { System.out.println(string); } // replace all whitespace with tabs System.out.println(EXAMPLE_TEST.replaceAll("\\s+", "\t")); } } Pattern and Matcher For advanced regular expressions java.util.regex.Matcher classes are used. the java.util.regex.Pattern and You first create a Pattern object which defines the regular expression. This Pattern object allows you to create a Matcher object for a given string. This Matcher object then allows you to do regex operations on a String . package de.vogella.regex.test; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegexTestPatternMatcher { public static final String EXAMPLE_TEST = "This is my small example string which I'm going to use for pattern matching."; public static void main(String[] args) { Pattern pattern = Pattern.compile("\\w+"); // in case you would like to ignore case sensitivity, // you could use this statement: // Pattern pattern = Pattern.compile("\\s+", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(EXAMPLE_TEST); // check all occurance while (matcher.find()) { System.out.print("Start index: " + matcher.start()); System.out.print(" End index: " + matcher.end() + " "); System.out.println(matcher.group()); } // now create a new pattern and matcher to replace whitespace with tabs Pattern replace = Pattern.compile("\\s+"); Matcher matcher2 = replace.matcher(EXAMPLE_TEST); System.out.println(matcher2.replaceAll("\t")); } } Output: Start index: 0 End index: 4 This Start index: 5 End index: 7 is Start index: 8 End index: 10 my Start index: 11 End index: 16 small Start index: 17 End index: 24 example Start index: 25 End index: 31 string Start index: 32 End index: 37 which Start index: 38 End index: 39 I Start index: 40 End index: 41 m Start index: 42 End index: 47 going Start index: 48 End index: 50 to Start index: 51 End index: 54 use Start index: 55 End index: 58 for Start index: 59 End index: 66 pattern Start index: 67 End index: 75 matching This is my small pattern matching. example string which I'm going to use for Java Regex Examples The following lists typical examples for the usage of regular expressions. I hope you find similarities to your real-world problems. Or Task: Write a regular expression which matches a text line if this text line contains either the word "Joe" or the word "Jim" or both. Create a project de.vogella.regex.eitheror and the following class. package de.vogella.regex.eitheror; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class EitherOrCheck { @Test public void testSimpleTrue() { String s = "humbapumpa jim"; assertTrue(s.matches(".*(jim|joe).*")); s = "humbapumpa jom"; assertFalse(s.matches(".*(jim|joe).*")); s = "humbaPumpa joe"; assertTrue(s.matches(".*(jim|joe).*")); s = "humbapumpa joe jim"; assertTrue(s.matches(".*(jim|joe).*")); } } Phone number Task: Write a regular expression which matches any phone number. A phone number in this example consists either out of 7 numbers in a row or out of 3 number, a (white)space or a dash and then 4 numbers. package de.vogella.regex.phonenumber; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class CheckPhone { @Test public void testSimpleTrue() { String pattern = "\\d\\d\\d([,\\s])?\\d\\d\\d\\d"; String s= "1233323322"; assertFalse(s.matches(pattern)); s = "1233323"; assertTrue(s.matches(pattern)); s = "123 3323"; assertTrue(s.matches(pattern)); } } Check for a certain number range The following example will check if a text contains a number with 3 digits. Create the Java project de.vogella.regex.numbermatch and the following class. package de.vogella.regex.numbermatch; import java.util.regex.Matcher; import java.util.regex.Pattern; public class CheckNumber { public static boolean test (String s){ Pattern pattern = Pattern.compile("\\d{3}"); Matcher matcher = pattern.matcher(s); if (matcher.find()){ return true; } return false; } } Email validation http://www.mkyong.com/regular-expressions/how-to-validate-email-address-with-regular-expression/ package com.mkyong.regex; import java.util.regex.Matcher; import java.util.regex.Pattern; public class EmailValidator { private Pattern pattern; private Matcher matcher; private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; public EmailValidator() { pattern = Pattern.compile(EMAIL_PATTERN); } /** * Validate hex with regular expression * * @param hex * hex for validation * @return true valid hex, false invalid hex */ public boolean validate(final String hex) { matcher = pattern.matcher(hex); return matcher.matches(); } } Esercizi Esercizio 1: Scrivere un programma Java che trasforma i numeri di telefono con spazi, oppure con il – oppure con il punto in numeri. BNF Teoria Da pag. 62 a pag. 65 del libro AQA A2. Esempi di Notazioni BNF per il linguaggio Java A titolo di esempio si consideri qualche costrutto del linguaggio Java e lo si definisca in notazione BFN. Ad esempio (da http://cui.unige.ch/isi/bnf/JAVA/BNFindex.html): variable_declaration variable_declaration ::= { modifier } type variable_declarator { "," variable_declarator } ";" variable_declarator variable_declarator ::= identifier { "[" "]" } [ "=" variable_initializer ] Ciclo for http://cui.unige.ch/isi/bnf/JAVA/for_statement.html for_statement ::= "for" "(" ( variable_declaration | ( expression ";" ) | ";" ) [ expression ] ";" [ expression ] ";" ")" statement Un esempio di parser che riconosce una grammatica BNF Un parser di espressioni aritmetiche sviluppato in maniera ricorsiva a partire dalla grammatica: // expression = term | expression `+` term | expression `-` term // term = factor | term `*` factor | term `/` factor | term brackets // factor = brackets | number | factor `^` factor // brackets = `(` expression `)` // http://stackoverflow.com/questions/3422673/evaluating-a-math-expression-given-in-string-form/ /** * * http://stackoverflow.com/questions/3422673/evaluating-a-math-expressiongiven-in-string-form/ */ public class MathParser { public static double eval(final String str) { class Parser { int pos = -1, c; void eatChar() { c = (++pos < str.length()) ? str.charAt(pos) : -1; } void eatSpace() { while (Character.isWhitespace(c)) { eatChar(); } } double parse() { eatChar(); double v = parseExpression(); if (c != -1) { throw new RuntimeException("Unexpected: " + (char) c); } return v; } // Grammar: // expression = term | expression `+` term | expression `-` term // term = factor | term `*` factor | term `/` factor | term brackets // factor = brackets | number | factor `^` factor // brackets = `(` expression `)` double parseExpression() { double v = parseTerm(); for (;;) { eatSpace(); if (c == '+') { // addition eatChar(); v += parseTerm(); } else if (c == '-') { // subtraction eatChar(); v -= parseTerm(); } else { return v; } } } double parseTerm() { double v = parseFactor(); for (;;) { eatSpace(); if (c == '/') { // division eatChar(); v /= parseFactor(); } else if (c == '*' || c == '(') { // multiplication if (c == '*') { eatChar(); } v *= parseFactor(); } else { return v; } } } double parseFactor() { double v; boolean negate = false; eatSpace(); if (c == '+' || c == '-') { // unary plus & minus negate = c == '-'; eatChar(); eatSpace(); } if (c == '(') { // brackets eatChar(); v = parseExpression(); if (c == ')') { eatChar(); } } else { // numbers StringBuilder sb = new StringBuilder(); while ((c >= '0' && c <= '9') || c == '.') { sb.append((char) c); eatChar(); } if (sb.length() == 0) { throw new RuntimeException("Unexpected: " + (char) c); } v = Double.parseDouble(sb.toString()); } eatSpace(); if (c == '^') { // exponentiation eatChar(); v = Math.pow(v, parseFactor()); } if (negate) { v = -v; // unary minus is applied after exponentiation; e.g. -3^2=-9 } return v; } } return new Parser().parse(); } /** * @param args the command line arguments */ public static void main(String[] args) { // TODO code application logic here System.out.println(eval("2^3 - 3 + 1 + 3 * ((4+4*4)/2) / 5 + -5")); } } RPN Teoria Da pag. 67 a pag. 68 del libro AQA A2, e in aggiunta gli algoritmi Dijkstra’s “Shunting yard” algorithm e “RPN Postfix algorithm”. Dijkstra’s “shunting yard” algorithm https://en.wikipedia.org/wiki/Shunting-yard_algorithm The Shunting Yard Tratto da: http://www.oxfordmathcenter.com/drupal7/node/628 Edsger Dijkstra developed his "Shunting Yard" algorithm to convert an infix expression into a postfix expression. It uses a stack; but in this case, the stack is used to hold operators rather than numbers. The purpose of the stack is to reverse the order of the operators in the expression. It also serves as a storage structure, since no operator can be printed until both of its operands have appeared. In this algorithm, all operands are printed (or sent to output) when they are read. There are more complicated rules to handle operators and parentheses. Examples 1. A * B + C becomes A B * C + The order in which the operators appear is not reversed. When the '+' is read, it has lower precedence than the '*', so the '*' must be printed first. We will show this in a table with three columns. The first will show the symbol currently being read. The second will show what is on the stack and the third will show the current contents of the postfix string. The stack will be written from left to right with the 'bottom' of the stack to the left. A * B + C --> A B * C + Current Symbol Operator Stack Postfix String 1 A A 2 * * A 3 B * A B 4 + + A B * (pop and print * before pushing +) 5 C + 6 A B * C A B * C + The rule used in lines 1, 3 and 5 is to print an operand when it is read. The rule for line 2 is to push an operator onto the stack if it is empty. The rule for line 4 is if the operator on the top of the stack has higher precedence than the one being read, pop and print the one on top and then push the new operator on. The rule for line 6 is that when the end of the expression has been reached, pop the operators on the stack one at a time and print them. 2. A + B * C becomes A B C * + Here the order of the operators must be reversed. The stack is suitable for this, since operators will be popped off in the reverse order from that in which they were pushed. A + B * C --> A B C * + Current Symbol Operator Stack Postfix String 1 A A 2 + + A 3 B + A B 4 * + * A B 5 C + * A B C 6 A B C * + In line 4, the '*' sign is pushed onto the stack because it has higher precedence than the '+' sign which is already there. Then when the are both popped off in lines 6 and 7, their order will be reversed. 3. A * (B + C) becomes A B C + * A subexpression in parentheses must be done before the rest of the expression. A * ( B + C ) --> Current Symbol A B C + * Operator Stack Postfix String 1 A A 2 * * A 3 ( * ( A B 4 B * ( A B 5 + * ( + A B 6 C * ( + A B C 7 ) * A B C + 8 A B C + * Since expressions in parentheses must be done first, everything on the stack is saved and the left parenthesis is pushed to provide a marker. When the next operator is read, the stack is treated as though it were empty and the new operator (here the '+' sign) is pushed on. Then when the right parenthesis is read, the stack is popped until the corresponding left parenthesis is found. Since postfix expressions have no parentheses, the parentheses are not printed. 4. A - B + C becomes A B - C + When operators have the same precedence, we must consider association. Left to right association means that the operator on the stack must be done first, while right to left association means the reverse. A - B + C --> A B - C + Current Symbol Operator Stack Postfix String 1 A A 2 - - A 3 B - A B 4 + + A B - 5 C + A B - C 6 A B - C + In line 4, the '-' will be popped and printed before the '+' is pushed onto the stack. Both operators have the same precedence level, so left to right association tells us to do the first one found before the second. 5. A * B ^ C + D becomes A B C ^ * D + Here both the exponentiation and the multiplication must be done before the addition. A * B ^ C + D --> A B C ^ * D + Current Symbol Operator Stack Postfix String 1 A A 2 * * A 3 B * A B 4 ^ * ^ A B 5 C * ^ A B C 6 + + A B C ^ * 7 D + A B C ^ * D 8 A B C ^ * D + When the '+' is encountered in line 6, it is first compared to the '^' on top of the stack. Since it has lower precedence, the '^' is popped and printed. But instead of pushing the '+' sign onto the stack now, we must compare it with the new top of the stack, the '*'. Since the operator also has higher precedence than the '+', it also must be popped and printed. Now the stack is empty, so the '+' can be pushed onto the stack. 6. A * (B + C * D) + E becomes A B C D * + * E + 7. A * ( B + C * D ) + E --> A B C D * + * E + 8. 9. Current Symbol Operator Stack Postfix String 10. 1 A 11. 2 * * A 12. 3 ( * ( A 13. 4 B * ( A B 14. 5 + * ( + A B 15. 6 C * ( + A B C 16. 7 * * ( + * A B C 17. 8 D * ( + * A B C D 18. 9 ) * A B C D * + 19. 10 + + A B C D * + * 20. 11 E + A B C D * + * E 21. 12 A A B C D * + * E + Summary of the Rules 1. Print operands as they arrive. 2. If the stack is empty or contains a left parenthesis on top, push the incoming operator onto the stack. 3. If the incoming symbol is a left parenthesis, push it on the stack. 4. If the incoming symbol is a right parenthesis, pop the stack and print the operators until you see a left parenthesis. Discard the pair of parentheses. 5. If the incoming symbol has higher precedence than the top of the stack, push it on the stack. 6. If the incoming symbol has equal precedence with the top of the stack, use association. If the association is left to right, pop and print the top of the stack and then push the incoming operator. If the association is right to left, push the incoming operator. 7. If the incoming symbol has lower precedence than the symbol on the top of the stack, pop the stack and print the top operator. Then test the incoming operator against the new top of stack. 8. At the end of the expression, pop and print all operators on the stack. (No parentheses should remain.) Shunting yard algorithm - pseudocode http://andreinc.net/2010/10/05/converting-infix-to-rpn-shunting-yard-algorithm/ In order to parse and convert a given infix mathematical expression to RPN we will use the shuntingyard algorithm . Just like the evaluation of RPN, the algorithm is stack-based . For the conversion we will use two buffers (one for input, and one for output). Additionally we will use a stack for operators that haven’t been yet added to the output. A simplified version of the Shunting-yard algorithm (complete version) For all the input tokens [S1]: o Read the next token [S2]; o If token is an operator (x) [S3]: While there is an operator (y) at the top of the operators stack and either (x) is left-associative and its precedence is less or equal to that of (y), or (x) is right-associative and its precedence is less than (y) [S4]: Pop (y) from the stack [S5]; Add (y) output buffer [S6]; Push (x) on the stack [S7]; o Else If token is left parenthesis, then push it on the stack [S8]; o Else If token is a right parenthesis [S9]: Until the top token (from the stack) is left parenthesis, pop from the stack to the output buffer [S10]; Also pop the left parenthesis but don’t include it in the output buffer [S11]; o Else add token to output buffer [S12]. While there are still operator tokens in the stack, pop them to output [S13] Note: [SN] Relate with code. Operator Associativity https://en.wikipedia.org/wiki/Operator_associativity In programming languages, the associativity (or fixity) of an operator is a property that determines how operators of the sameprecedence are grouped in the absence of parentheses. Associativity is only needed when the operators in an expression have the same precedence. Usually + and - have the same precedence. Consider the expression 7 − 4 + 2 . The result could be either (7 − 4) + 2 = 5 or 7 − (4 + 2) = 1 . The former result corresponds to the case when + and − are left-associative, the latter to when + and - are right-associative. In order to reflect normal usage, addition, subtraction, multiplication, and division operators are usually left-associative[1] while an exponentiation operator (if present) is right-associative;[1] this applies to the uparrow operator as well. Any assignment operators are also typically right-associative. To prevent cases where operands would be associated with two operators, or no operator at all, operators with the same precedence must have the same associativity. Consider the expression 5^4^3^2 , in which ^ represents exponentiation. A parser reading the tokens from left to right would apply the associativity rule to a branch, because of the right-associativity of ^ , in the following way 5^(4^(3^2)) .The evaluator steps up the tree to the root expression and evaluates as: 5262144 ≈ 6.2060699 × 10183230 A left-associative evaluation would have resulted in the parse tree ((5^4)^3)^2 and the completely different results 625, 244140625 and finally ~5.9604645 × 1016. // Classe ReversePolishNotation /* * * * * To change this license header, choose License Headers in Project Properties. To change this template file, choose Tools | Templates and open the template in the editor. http://andreinc.net/2010/10/05/converting-infix-to-rpn-shunting-yard- algorithm/ */ import import import import java.util.ArrayList; java.util.HashMap; java.util.Map; java.util.Stack; public class ReversePolishNotation { // Associativity constants for operators private static final int LEFT_ASSOC = 0; private static final int RIGHT_ASSOC = 1; // Supported operators private static final Map<String, int[]> OPERATORS = new HashMap<String, int[]>(); static { // Map<"token", []{precendence, associativity}> OPERATORS.put("+", new int[] { 0, LEFT_ASSOC }); OPERATORS.put("-", new int[] { 0, LEFT_ASSOC }); OPERATORS.put("*", new int[] { 5, LEFT_ASSOC }); OPERATORS.put("/", new int[] { 5, LEFT_ASSOC }); OPERATORS.put("%", new int[] { 5, LEFT_ASSOC }); OPERATORS.put("^", new int[] { 10, RIGHT_ASSOC }); } /** * Test if a certain is an operator . * @param token The token to be tested . * @return True if token is an operator . Otherwise False . */ private static boolean isOperator(String token) { return OPERATORS.containsKey(token); } /** * Test the associativity of a certain operator token . * @param token The token to be tested (needs to operator). * @param type LEFT_ASSOC or RIGHT_ASSOC * @return True if the tokenType equals the input parameter type . */ private static boolean isAssociative(String token, int type) { if (!isOperator(token)) { throw new IllegalArgumentException("Invalid token: " + token); } if (OPERATORS.get(token)[1] == type) { return true; } return false; } /** * Compare precendece of two operators. * @param token1 The first operator . * @param token2 The second operator . * @return A negative number if token1 has a smaller precedence than token2, * 0 if the precendences of the two tokens are equal, a positive number * otherwise. */ private static final int cmpPrecedence(String token1, String token2) { if (!isOperator(token1) || !isOperator(token2)) { throw new IllegalArgumentException("Invalied tokens: " + token1 + " " + token2); } return OPERATORS.get(token1)[0] - OPERATORS.get(token2)[0]; } public static String[] infixToRPN(String[] inputTokens) { ArrayList<String> out = new ArrayList<String>(); Stack<String> stack = new Stack<String>(); // For all the input tokens [S1] read the next token [S2] for (String token : inputTokens) { if (isOperator(token)) { // If token is an operator (x) [S3] while (!stack.empty() && isOperator(stack.peek())) { // [S4] if ((isAssociative(token, LEFT_ASSOC) && cmpPrecedence( token, stack.peek()) <= 0) || (isAssociative(token, RIGHT_ASSOC) && cmpPrecedence( token, stack.peek()) < 0)) { out.add(stack.pop()); // [S5] [S6] continue; } break; } // Push the new operator on the stack [S7] stack.push(token); } else if (token.equals("(")) { stack.push(token); // [S8] } else if (token.equals(")")) { // [S9] while (!stack.empty() && !stack.peek().equals("(")) { out.add(stack.pop()); // [S10] } stack.pop(); // [S11] } else { out.add(token); // [S12] } } while (!stack.empty()) { out.add(stack.pop()); // [S13] } String[] output = new String[out.size()]; return out.toArray(output); } public static void main(String[] args) { String[] input = "( 1 + 23.5 ) * ( 3 / 4 ) ^ ( 5 + 6 )".split(" "); String[] output = infixToRPN(input); for (String token : output) { System.out.print(token + " "); } } } // Classe RPNCalc /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ // http://andreinc.net/2011/01/03/rpn-calculator-using-python-scala-and-java/ import import import import java.util.Arrays; java.util.HashMap; java.util.LinkedList; java.util.Map; public class RPNCalc { // Helper interface needed to immitate anonymous functions public static interface Operation { public Double eval(Double e1, Double e2); } public static Map<String, Operation> OPS = new HashMap<String, Operation>(); static { OPS.put("+", new Operation() { public Double eval(Double e1, return e1 + e2; } }); OPS.put("-", new Operation() { public Double eval(Double e1, return e2 - e1; } }); OPS.put("*", new Operation() { public Double eval(Double e1, return e1 * e2; } }); OPS.put("/", new Operation() { public Double eval(Double e1, return e2 / e1; } }); OPS.put("^", new Operation() { public Double eval(Double e1, return Math.pow(e2, e1); } }); OPS.put("%", new Operation() { public Double eval(Double e1, return e2 % e1; } }); Double e2) { Double e2) { Double e2) { Double e2) { Double e2) { Double e2) { } ; /** * Evaluate RPN expr (given as array of tokens) * @param tokens * @return the double value corresponding to the given expression */ public static Double eval(String[] tokens) { LinkedList<Double> stack = new LinkedList<Double>(); for(String token : tokens) { if (OPS.containsKey(token)) { stack.push(OPS.get(token).eval(stack.pop(), stack.pop())); } else { stack.push(Double.parseDouble(token)); } } return stack.pop(); } /** * Evaluate RPN expr (given as array of tokens with explanations of calculation steps) * @param tokens * @return the double value corresponding to the given expression */ public static Double eval2(String[] tokens) { LinkedList<Double> stack = new LinkedList<Double>(); System.out.println("Contenuto dello stack di calcolo iniziale: " + stack); for (int i = 0; i < tokens.length; i++) { System.out.println("Stringa RPN da valutare: " + Arrays.toString(Arrays.copyOfRange(tokens, i, tokens.length))); if (OPS.containsKey(tokens[i])) { //prelevo gli operandi Double operand1 = stack.pop(); System.out.println("Prelevo " + operand1); Double operand2 = stack.pop(); System.out.println("Prelevo " + operand2); //calcolo il parziale Double parziale = OPS.get(tokens[i]).eval(operand1, operand2); //inserisco il parziale sullo stack stack.push(parziale); System.out.println("Inserisco " + parziale); System.out.println("Contenuto dello stack di calcolo: " + stack); //stack.push(OPS.get(token).eval(stack.pop(), stack.pop())); } else { System.out.println("Inserisco " + Double.parseDouble(tokens[i])); stack.push(Double.parseDouble(tokens[i])); System.out.println("Contenuto dello stack di calcolo: " + stack); } } Double risultato = stack.pop(); System.out.println("Restituisco " + risultato); return risultato; } // Main function public static void main(String args[]) { //String[] input = "( 1 + 2 ) * ( 3 / 4 ) ^ ( 5 + 6 )".split(" "); //String[] input ="( 1 + 2 ) * ( 3 / 4 ) - ( 5 + 6 )".split(" "); //String[] input="2 ^ 3 - 3 + 1 - 5".split(" "); //String[] input="2 ^ 3 - 3 % 2 + 1 - 5".split(" "); String inputString = "( 1 + 23.5 ) * ( 3 / 4 ) ^ ( 5 + 6 )"; //String inputString = "4 + 5 * 6"; String[] input = inputString.split(" "); String[] output = ReversePolishNotation.infixToRPN(input); System.out.println("Valutazione della stringa: " + inputString); /* System.out.print("Stringa in RPN: "); for (String token : output) { System.out.print(token + " "); } */ System.out.println(""); System.out.println("\nexp=" + eval2(output)); } } Un Parser complesso https://github.com/uklimaschewski/EvalEx Testarlo per capire come funziona. Osservare il ruolo del Tokenizer. Questo parser sarà usato nella versione avanzata della calcolatrice scientifica che sarà sviluppata a conclusione di questa unità didattica. Calcoli scientifici e monetari di elevata precisione in Java: classe BigDecimal Links sui BigDecimals http://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html http://www.opentaps.org/docs/index.php/How_to_Use_Java_BigDecimal:_A_Tutorial http://epramono.blogspot.it/2005/01/double-vs-bigdecimal.html Perché BigDecimal? Potremmo dire “Quando la somma non fa il totale… è il momento di passare da double a BigDecimal”. Vediamo qualche esempio per capire. Esempio 1 Tratto da : http://epramono.blogspot.it/2005/01/double-vs-bigdecimal.html public class BigDecimalTest1 { public static void main(String[] args) { System.out.println( "(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1) = " + (0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1)); double d = 0.0; while (d <= 1.0) { d += 0.1; } System.out.println("d = " + d); System.out.println("0.0175 * 100000 = " + 0.0175 * 100000); } } // Il risultato è: (0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1) = 0.9999999999999999 d = 1.0999999999999999 0.0175 * 100000 = 1750.0000000000002 A tutorial on BigDecimal La documentazione ufficiale sulla classe BigDecimal è http://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html Immutable, arbitrary-precision signed decimal numbers. A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale. If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale). The BigDecimal class provides operations for arithmetic, scale manipulation, rounding, comparison, hashing, and format conversion. The toString() method provides a canonical representation of aBigDecimal. The BigDecimal class gives its user complete control over rounding behavior. If no rounding mode is specified and the exact result cannot be represented, an exception is thrown; otherwise, calculations can be carried out to a chosen precision and rounding mode by supplying an appropriate MathContext object to the operation. In either case, eight rounding modes are provided for the control of rounding. it should be clear that we need two things: 1. Ability to specify a scale, which represents the number of digits after the decimal place 2. Ability to specify a rounding method The java.math.BigDecimal class handles both of these considerations. See BigDecimal Javadocs Creating a big decimal from a (scalar) double is simple: bd = new BigDecimal(1.0); To get a BigDecimal from a Double, get its doubleValue() first. However it is a good idea to use the string constructor (usare sempre questo): bd = new BigDecimal("1.5"); If you don't, then you'll get the following, bd = new BigDecimal(1.5); bd.toString(); // => 0.1499999999999999944488848768742172978818416595458984375 The worst part about using the BigDecimal class in Java is that you can’t use normal arithmetic operators with BigDecimal objects. BigDecimalarithmetic has different rules. The following code, for example, won’t compile: BigDecimal subTotal, taxRate, tax, total; subTotal = new BigDecimal("32.50"); taxRate = new BigDecimal("0.05"); tax = subTotal * taxRate; // error: won’t compile total = subTotal + tax // this won’t compile either Instead, you have to call methods of the BigDecimal class to perform basic arithmetic. All these methods return the result of the calculation asBigDecimal objects. Here’s how you can perform the preceding tax calculation: BigDecimal subTotal, taxRate, tax, total; subTotal = new BigDecimal("32.50"); taxRate = new BigDecimal("0.05"); tax = subTotal.multiply(taxRate); total = subTotal.add(tax); Rounding and Scaling To set the number of digits after the decimal, use the .setScale(scale) method. However, it is good practice to also specify the rounding mode along with the scale by using .setScale(scale, roundingMode). The rounding mode specifies how to round the number. Why do we also want to specify the rounding mode? bd = new BigDecimal(1.5); // is actually 1.4999.... bd.setScale(1); // throws ArithmeticException It throws the exception because it does not know how to round 1.49999. So it is a good idea to always use .setScale(scale, roundingMode). There are eight choices for rounding mode, Enum Constant Summary http://docs.oracle.com/javase/8/docs/api/java/math/RoundingMode.html CEILING Rounding mode to round towards positive infinity. DOWN Rounding mode to round towards zero. FLOOR Rounding mode to round towards negative infinity. HALF_DOWN Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. HALF_EVEN Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor. HALF_UP Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up. UNNECESSARY Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary. UP Rounding mode to round away from zero Examples: BigDecimal value1 = new BigDecimal("4.5"); value1=value1.setScale(0, RoundingMode.HALF_EVEN); BigDecimal value2 = new BigDecimal("6.5"); value2=value2.setScale(0, RoundingMode.HALF_EVEN); System.out.println(value1+"\n"+value2); When dividing BigDecimals, be careful to specify the rounding in the .divide(...) method. Otherwise, you could run into an ArithmeticException if there is no precisely rounded resulting value, such as 1/3. Thus, you should always do: a = b.divide(c, decimals, rounding); a = b.divide(c, decimals, rounding); MathContext Per utilizzare impostazioni predefinite di scale (the number of digits after the decimal place) e rounding mode si può fare riferimento alla classe MathContext: Immutable objects which encapsulate the context settings which describe certain rules for numerical operators, such as those implemented by the BigDecimal class. The base-independent settings are: 1. precision: the number of digits to be used for an operation; results are rounded to this precision 2. roundingMode: a RoundingMode object which specifies the algorithm to be used for rounding. http://stackoverflow.com/questions/7539/use-of-java-math-mathcontext System.out.println(new new System.out.println(new new System.out.println(new new System.out.println(new new BigDecimal("123.4", MathContext(4,RoundingMode.HALF_UP))); BigDecimal("123.4", MathContext(2,RoundingMode.HALF_UP))); BigDecimal("123.4", MathContext(2,RoundingMode.CEILING))); BigDecimal("123.4", MathContext(1,RoundingMode.CEILING))); Outputs: 123.4 1.2E+2 1.3E+2 2E+2 You can see that both the precision and the rounding mode affect the output. Molto utili sono le costanti predefinite del MathContext: static MathContext DECIMAL128 A MathContext object with a precision setting matching the IEEE 754R Decimal128 format, 34 digits, and a rounding mode of HALF_EVEN, the IEEE 754R default. static MathContext DECIMAL32 A MathContext object with a precision setting matching the IEEE 754R Decimal32 format, 7 digits, and a rounding mode of HALF_EVEN, the IEEE 754R default. static MathContext DECIMAL64 A MathContext object with a precision setting matching the IEEE 754R Decimal64 format, 16 digits, and a rounding mode of HALF_EVEN, the IEEE 754R default. static MathContext UNLIMITED A MathContext object whose settings have the values required for unlimited precision arithmetic. Ad esempio: new BigDecimal("1264.7").divide(new BigDecimal("20"), MathContext.DECIMAL64) public BigDecimal somma(BigDecimal v1, BigDecimal v2) { return v1.add(v2, MathContext.DECIMAL64); } Immutability and Arithmetic BigDecimal numbers are immutable. What that means is that if you create a new BD with value "2.00", that object will remain "2.00" and can never be changed. So how do we do math then? The methods .add(), .multiply(), and so on all return a new BD value containing the result. For example, when you want to keep a running total of the order amount, amount = amount.add( thisAmount ); Make sure you don't do this, amount.add( thisAmount ); THIS IS THE MOST COMMON MISTAKE MADE WITH BIGDECIMALS! Comparison It is important to never use the .equals() method to compare BigDecimals. That is because this equals function will compare the scale. If the scale is different, .equals() will return false, even if they are the same number mathematically. BigDecimal a = new BigDecimal("2.00"); BigDecimal b = new BigDecimal("2.0"); print(a.equals(b)); // false Instead, we should use the .compareTo() and .signum() methods. a.compareTo(b); // returns (-1 if a < b), (0 if a == b), (1 if a > b) a.signum(); // returns (-1 if a < 0), (0 if a == 0), (1 if a > 0) Progetto: Una calcolatrice scientifica Utilizzando il Parser reperibile all’indirizzo https://github.com/uklimaschewski/EvalEx creare un’applicazione Java con interfaccia grafica che permetta di emulare la calcolatrice di Windows in modalità standard e in modalità scientifica. La calcolatrice avrà anche la possibilità di tracciare il grafico di una funzione f(x) di cui si conosce l’espressione f e l’intervallo di valori a<=x<=b. La calcolatrice farà l’autoscaling sia sull’asse delle ascisse che sull’asse delle ordinate. Inoltre passando il mouse sopra il grafico della funzione verranno visualizzati i valori di x e di f(x). Nel progetto software l’interfaccia grafica dovrà richiamare i metodi di una classe CalcEngine che gestisce la macchina a stati e il parser. In altri termini l’applicazione deve essere realizzata in modo da poter disaccoppiare l’interfaccia utente dal CalcEngine. In seguito la classe CalcEngine sarà utilizzata in un’applicazione Android. Ad esempio, supponendo di avere i bottoni: private javax.swing.JButton C; private javax.swing.JButton CE; private javax.swing.JButton MC; private javax.swing.JButton MR; private javax.swing.JButton MS; private javax.swing.JButton Mminus; private javax.swing.JButton Mplus; private javax.swing.JButton back; private javax.swing.JButton one; private javax.swing.JButton percentage; private javax.swing.JButton plus; private javax.swing.JButton plusMinus; private javax.swing.JButton reciprocal; private javax.swing.JButton seven; private javax.swing.JButton six; private javax.swing.JButton sqrt; //..e altri.. I gestori degli eventi di ciascun bottone dell’interfaccia grafica richiameranno i metodi di un oggetto “calc” di tipo CalcEngine, il quale a sua volta utilizzerà un oggetto di classe Expression… //A titolo esemplificativo…. private void zeroActionPerformed(java.awt.event.ActionEvent evt) { setNumber(evt); redisplay(); } private void oneActionPerformed(java.awt.event.ActionEvent evt) { setNumber(evt); redisplay(); } private void CEActionPerformed(java.awt.event.ActionEvent evt) { calc.clearCurrentDisplayNumber(); redisplay(); } private void MCActionPerformed(java.awt.event.ActionEvent evt) { calc.memoryClearValue(); redisplay(); } private void MSActionPerformed(java.awt.event.ActionEvent evt) { calc.memoryStoreValue(calc.getDisplayValue()); redisplay(); } private void MRActionPerformed(java.awt.event.ActionEvent evt) { calc.memoryRecallValue(); redisplay(); } private void MplusActionPerformed(java.awt.event.ActionEvent evt) { calc.addToMemory(calc.getDisplayValue()); } Le funzioni di base della calcolatrice di Windows What the Buttons Do! Backspace --> Removes the last digit of the displayed number. CE --> Clears the number displayed at that time. C --> Clears the entire calculation. MC --> Clears the numbers in the memory. MR --> Recalls a number from the memory. MS --> Stores numbers in the memory. M+ --> Adds the displayed number to the memory. M- --> Subtracts the displayed number from the memory. Sqrt --> This calculates the square root of the number on the screen. 1/x --> This calculates the reciprocal of the displayed number. Per il calcolo percentuale: osservare che la percentuale è rispetto all’espressione già inserita. Ad esempio: 50 + 5% 50 + 2,5 50 *5% 50 * 2,5 Per il calcolo della radice si può usare l’elevamento a potenza usando come esponente (0.5). Il grafico di funzione Ci sono diversi metodi per effettuare il grafico di una funzione di una variabile. Metodo diretto Si utilizzano le classi Polygon, Graphics, etc. di Java come nel seguente esempio: http://stackoverflow.com/questions/19914476/plot-the-sine-and-cosine-functions import javax.swing.*; import java.awt.*; public class Exercise extends JFrame { public Exercise() { setLayout(new BorderLayout()); add(new DrawSine(), BorderLayout.CENTER); } public static void main(String[] args) { Exercise frame = new Exercise(); frame.setSize(400, 300); frame.setTitle("Exercise"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); frame.setVisible(true); } class DrawSine extends JPanel { double f(double x) { return Math.sin(x); } double gCos(double y) { return Math.cos(y); } protected void paintComponent(Graphics g) { super.paintComponent(g); g.drawLine(10, 100, 380, 100); g.drawLine(200, 30, 200, 190); g.drawLine(380, g.drawLine(380, g.drawLine(200, g.drawLine(200, 100, 370, 90); 100, 370, 110); 30, 190, 40); 30, 210, 40); g.drawString("X", 360, 80); g.drawString("Y", 220, 40); Polygon p = new Polygon(); Polygon p2 = new Polygon(); for (int x = -170; x <= 170; x++) { p.addPoint(x + 200, 100 - (int) (50 * f((x / 100.0) * 2 * Math.PI))); } for (int x = -170; x <= 170; x++) { p2.addPoint(x + 200, 100 - (int) (50 * gCos((x / 100.0) * 2 * Math.PI))); } g.setColor(Color.red); g.drawPolyline(p.xpoints, p.ypoints, p.npoints); g.drawString("-2\u03c0", 95, 115); g.drawString("-\u03c0", 147, 115); g.drawString("\u03c0", 253, 115); g.drawString("2\u03c0", 305, 115); g.drawString("0", 200, 115); g.setColor(Color.blue); g.drawPolyline(p2.xpoints, p2.ypoints, p2.npoints); } } } Utilizzo della classe Draw di Sedgewick e Wayne Usando le classi e interfacce: http://introcs.cs.princeton.edu/java/stdlib/Draw.java http://introcs.cs.princeton.edu/java/stdlib/DrawListener.java Si tratta di classi presenti nella libreria standard del corso universitario di introduzione alla programmazione dell’università di Princeton: http://introcs.cs.princeton.edu/java/stdlib/ http://introcs.cs.princeton.edu/java/15inout/ La libreria in formato jar può essere scaricata direttamente da qui: http://introcs.cs.princeton.edu/java/stdlib/stdlib.jar Tuttavia per quello che bisogna fare in questo progetto basta scaricare il codice della classe Draw.java e dell’interfaccia DrawListener.java e fare qualche lieve adattamento/modifica: Interfaccia DrawListener.java Aggiungere il metodo: /** * * @param x the x-coordinate of the mouse * @param y the y-coordinate of the mouse */ void mouseMoved(double x, double y); Classe Draw.java Aggiungere il metodo public void plot(double[] x, double[] y) { int N = x.length; GeneralPath path = new GeneralPath(); path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); for (int i = 0; i < N - 1; i++) { path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); } offscreen.draw(path); draw(); } Ridefinire il metodo mouseMoved come di seguito riportato a titolo esemplificativo: /** * This method cannot be called directly. */ @Override public void mouseMoved(MouseEvent e) { synchronized (mouseLock) { mouseX = userX(e.getX()); mouseY = userY(e.getY()); for (DrawListener listener : listeners) { listener.mouseMoved(userX(e.getX()), userY(e.getY())); } } } Aggiungere la nested static class (dentro la classe Draw): private static class DrawListenerImpl implements DrawListener { private final Draw draw; private final double[] x; private final double[] y; public DrawListenerImpl(Draw draw, double[] x, double[] y) { this.draw = draw; this.x = x; this.y = y; } @Override public void mousePressed(double x, double y) { System.out.println("mouse pressed x= " + x + " y= " + y); draw.clear(); double deltaX = draw.xmax - draw.xmin; double deltaY = draw.ymax - draw.ymin; draw.setXscale(x - deltaX / 4, x + deltaX / 4); draw.setYscale(y - deltaY / 4, y + deltaY / 4); draw.plot(this.x, this.y); } @Override public void mouseDragged(double x, double y) { System.out.println("mouse dragged x= " + x + " y= " + y); } @Override public void mouseReleased(double x, double y) { } @Override public void keyTyped(char c) { } @Override public void keyPressed(int keycode) { } @Override public void keyReleased(int keycode) { } @Override public void mouseMoved(double x, double y) { System.out.println("mouse moved x= " + x + " y= " + y); } } Riscrivere il main come nel seguente esempio per testare le funzionalità della classe Draw: public static void main(String[] args) { // number of line segments to plot int N = Integer.parseInt("20000");//args[0] // the function y = sin(4x) + sin(20x), sampled at N points // between x = 0 and x = pi double[] x2 = new double[N + 1]; double[] y2 = new double[N + 1]; for (int i = 0; i <= N; i++) { x2[i] = Math.PI * i / N; y2[i] = Math.sin(4 * x2[i]) + Math.sin(2 * x2[i]); } // rescale the coordinate system Draw draw3 = new Draw("Test client 3"); draw3.setXscale(0, Math.PI); draw3.setYscale(-2.0, +2.0); draw3.plot(x2, y2); /* // plot the approximation to the function for (int i = 0; i < N; i++) { draw.line(x[i], y[i], x[i+1], y[i+1]); } */ draw3.addListener(new DrawListenerImpl(draw3, x2, y2)); } Gestione dei tasti della tastiera Per associare i tasti della tastiera (inclusi i tasti del numpad e le combinazioni di tasti come SHIFT+altro tasto…) Links di riferimento http://stackoverflow.com/questions/14318191/shift-enter-enter-in-the-same-component-in-java Dont use KeyListener/KeyAdapter with Swing. Always use KeyBindings https://tips4java.wordpress.com/2008/10/10/key-bindings/ http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html https://docs.oracle.com/javase/7/docs/api/javax/swing/KeyStroke.html http://stackoverflow.com/questions/16069502/how-to-define-the-keystroke-for-the-numpad-enter-key http://docs.oracle.com/javase/7/docs/api/java/awt/event/KeyEvent.html http://stackoverflow.com/questions/2419608/java-swing-keystrokes-how-to-make-ctrl-modifier-work https://docs.oracle.com/javase/7/docs/api/java/awt/KeyEventPostProcessor.html http://docs.oracle.com/javase/7/docs/api/java/awt/KeyboardFocusManager.html http://docs.oracle.com/javase/7/docs/api/java/awt/KeyEventDispatcher.html http://www.simplesoft.it/java/tips/implementare-un-keylistener-in-java-swing.html http://stackoverflow.com/questions/100123/application-wide-keyboard-shortcut-java-swing#100754 http://stackoverflow.com/questions/286727/java-keylistener-for-jframe-is-being-unresponsive Esempio applicativo import import import import import import java.awt.event.ActionEvent; java.awt.event.InputEvent; java.awt.event.KeyEvent; javax.swing.AbstractAction; javax.swing.JComponent; javax.swing.KeyStroke; /** * * @author Prof.G */ public class UserInterface extends javax.swing.JFrame { private static final long serialVersionUID = 1L; private final CalcEngine calc; public UserInterface(CalcEngine calc) { this.calc = calc; initComponents(); notificationText.setVisible(false); //GESTISCE I KEYSTROKE //moltiplicazione //KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.SHIFT_DOWN_MASK) -> SHIFT + (+) this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.SHIFT_DOWN_MASK, false), "moltiplica"); //KeyStroke.getKeyStroke(KeyEvent.VK_MULTIPLY, 0) --> * on numpad this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MULTIPLY , 0, false), "moltiplica"); this.getRootPane().getActionMap().put("moltiplica", new AbstractAction() { //multiplication by SHIFT and + on the keyboard private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { calc.multiply(); redisplay(); } }); //somma //KeyStroke.getKeyStroke(KeyEvent.VK_PLUS,0, true) --> + on keyboard //KeyStroke.getKeyStroke(KeyEvent.VK_ADD,0, true) --> + on numpad of keyboard this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS,0, false), "somma"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD,0, false), "somma"); this.getRootPane().getActionMap().put("somma", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { calc.plus(); redisplay(); } }); //differenza //KeyStroke.getKeyStroke(KeyEvent.VK_MINUS,0) --> - on keyboard //KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT,0) --> - on numpad of keyboard this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS,0, false), "differenza"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT ,0, false), "differenza"); this.getRootPane().getActionMap().put("differenza", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { calc.minus(); redisplay(); } }); //divisione //KeyStroke.getKeyStroke(KeyEvent.VK_7,InputEvent.SHIFT_DOWN_MASK) --> / on keyboard //KeyStroke.getKeyStroke(KeyEvent.VK_DIVIDE,0) --> / on numpad of keyboard this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_7,InputE vent.SHIFT_DOWN_MASK, false), "divisione"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DIVIDE,0 , false), "divisione"); this.getRootPane().getActionMap().put("divisione", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { calc.divide(); redisplay(); } }); //uguale - invio this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0, false), "uguale"); this.getRootPane().getActionMap().put("uguale", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { calc.equals(); redisplay(); } }); //inserimento numeri // this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_0,0, false), "zero"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, 0, false), "zero"); this.getRootPane().getActionMap().put("zero", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(0); } }); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_1,0, false), "uno"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD1, 0, false), "uno"); this.getRootPane().getActionMap().put("uno", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(1); } }); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_2,0, false), "due"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD2, 0, false), "due"); this.getRootPane().getActionMap().put("due", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(2); } }); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_3,0, false), "tre"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD3, 0, false), "tre"); this.getRootPane().getActionMap().put("tre", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(3); } }); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_4,0, false), "quattro"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD4, 0, false), "quattro"); this.getRootPane().getActionMap().put("quattro", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(4); } }); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_5,0, false), "cinque"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD5, 0, false), "cinque"); this.getRootPane().getActionMap().put("cinque", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(5); } }); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_6,0, false), "sei"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD6, 0, false), "sei"); this.getRootPane().getActionMap().put("sei", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(6); } }); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_7,0, false), "sette"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD7, 0, false), "sette"); this.getRootPane().getActionMap().put("sette", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(7); } }); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_8,0, false), "otto"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD8, 0, false), "otto"); this.getRootPane().getActionMap().put("otto", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(8); } }); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_9,0, false), "nove"); this.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD9, 0, false), "nove"); this.getRootPane().getActionMap().put("nove", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setCifraAndRedisplay(9); } }); } /** * Update the interface display to show the current value of the calculator. */ private void redisplay() { //check if the "M" on the dispaly has to be shown if (calc.getMemoryValue() != 0) {//if a value diffeerent than 0 is in memory notificationText.setVisible(true); } else { notificationText.setVisible(false); } display.setText(calc.getDisplayValueString()); } /** * intercetta l'evento corrispondente alla pressione di un tasto * corrispondente a un numero; il numero è passato all'oggetto CalcEngine. * prerequisito: il tasto ha un nome corrispondente al numero, ad esempio * "2" per il tasto corrispondente a 2. * * @param evt evento corrispondente alla pressione del numero */ private void setNumber(java.awt.event.ActionEvent evt) { String command = evt.getActionCommand(); int number = Integer.parseInt(command); calc.numberPressed(number); } private void setCifraAndRedisplay(int numero) { calc.numberPressed(numero); redisplay(); } @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code"> private void initComponents() { // da fare} private void zeroActionPerformed(java.awt.event.ActionEvent evt) { /* setNumber(evt); redisplay(); */ setCifraAndRedisplay(0); } private void oneActionPerformed(java.awt.event.ActionEvent evt) { /* setNumber(evt); redisplay(); */ setCifraAndRedisplay(1); } private void twoActionPerformed(java.awt.event.ActionEvent evt) { /* setNumber(evt); redisplay(); */ setCifraAndRedisplay(2); } //e altri metodi da implementare } Valutazione Livello sufficiente-discreto (voti da 6 a 7): calcolatrice standard di Windows con interfaccia grafica, usando il parser della classe ReversePolishNotation e l’algoritmo di valutazione di RPNCalc, senza l’uso della classe BigDecimal. Livello buono (voti da 7 a 8): uso del parser https://github.com/uklimaschewski/EvalEx, uso della classe BigDecimal, e doppia modalità della calcolatrice (base e scientifica). Come nella calcolatrice di Windows bisogna avere un pulsante per mostrare lo storico dei calcoli effettuati. Tale storico potrà essere scartato oppure salvato su file. Livello eccellente-ottimo (voti da 8 al 10): 1. Tutto quanto previsto dal livello eccellente con in aggiunta la possibilità di eseguire il grafico di una funzione di una variabile di cui si inserisce in input l’espressione. 2. Il grafico di funzione dovrà essere interattivo (click sinistro si fa uno zoom in avanti, SHIFT+ click sinistro si fa uno zoom indietro). 3. Il pannello che mostra il grafico di funzione dovrà permettere di definire anche il numero di campioni della funzione (con valore di default se non impostato dall’utente). 4. La calcolatrice dovrà avere la possibilità di associare almeno 5 funzioni, programmabili da utente, ad altrettanti pulsati f0, f1,…,f4. 5. La calcolatrice dovrà avere la possibilità di mostrare, con colori diversi, almeno 5 grafici di funzione differenti sullo stesso pannello, con legenda dei colori (ad es. f0 rosso, f1 blu, etc.). Inoltre l’utente dovrà avere la possibilità di deselezionare un grafico, ad esempio, mediante una checkbox posta sopra il pannello del grafico. 6. Sugli assi cartesiani dovrà essere riportata una scala graduata con i valori numerici. 7. La scala utilizzata userà impostazioni di default (calcolo automatico della scala), ma l’utente dovrà avere, sul frame che contiene il pannello che rappresenta il grafico, la possibilità di impostare manualmente i valori massimi e minimi degli assi cartesiani. 8. L’utente dovrà avere la possibilità di salvare il grafico di funzione. 9. L’utente dovrà avere la possibilità di esportare i valori della funzione (x e f(x)) in un file csv. 10. Quando l’utente porta il mouse sul grafico di una funzione viene visualizzato un tooltip che riporta il valore della funzione e della coordinata x. 11. Il grafico di funzione deve escludere le singolarità: i punti per i quali il calcolo con i double fornirebbe NaN, POSITIVE_INFINITY, NEGATIVE_INFINITY oppure con i BigDecimal fornirebbe NumberFormatException http://stackoverflow.com/questions/10080084/how-to-convert-double-positive-infinity-tobigdecimal Suggerimento implementare una versione di plot che esclude dal path i punti di singolarità.