Grammatiche, espressioni regolari BFN, RPN

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à.