/* CSC 123/252 Programming Challenge: The Super Visitor Pattern There are 4 types of numbers: integers, rationals, reals and complex numbers. You want to test for equality between them, add and multiply them *without loss of precision* whenever possible. That is, only convert to a float (used in real and complex numbers) when necessary. This program contains an interface called number and three classes that implement it: integer, rational and real (and you'll add complex). An integer i contains i.val; a rational f contains f.n and f.d and represents the fraction f.n/f.d. A real r consists of a double r.val. An incomplete complex number class also exists, which consists of a pair of doubles, r (real part) and i (imaginary part). The classes has ToString functions that allows each number to be printed directly with Console.Write. In this program, the "visitees" are numbers and the "visitors" are operations on *pairs* of numbers. Since there are two numbers and several possible visitees, this program requires triple dispatch to get to the right algorithm (the right visit method). Currently, this program works with integers, rationals and reals, but the class (and visitors) for complex numbers are incomplete. Your task is to complete the complex number class, then modify the rest of the program to take into account your new class. **** THIS TIME, YOU CAN EDIT THE CODE DIRECTLY. **** (one of the problems of this design is that it is much more difficult to extend modularly compared to the regular visitor pattern - because each number class must be aware of each other number class, and these classes must be extended, rendering your new code incompatible with the existing code (real extension methods would have helped). ) Specifically, you are to: 1. Complete the definition of complex number class and modify the existing interfaces and classes so that the code in main will work with the longer array of numbers that contain some complex numbers. 2. Add a new visitor that can multiply any pair of numbers, including complex numbers. 3. Modify main to test your new code. 4. To obtain value from the exercise, you must understand the rationale behind it. That is, why wouldn't the following approach work: just define several operations by overloading: static bool eq(integer x, integer y) { return x.val==y.val; } static bool eq(integer x, rational y) { return x.val*y.d == y.n; } static bool eq(rational a, rational b) {return a.n*b.d==a.d*b.n;} static bool eq(rational a, real b) => a.n/a.d == b.val; // new syntax static number add(integer a, real b) { return new real(a.val+b.val); } static number add(rational a, rational b) { return new rational(a.n*b.d + b.n*a.d, a.d*b.d); } // rational addition //.... EVEN IF YOU WRITE ALL THE CASES, //.... THIS APPROACH ALONE WON'T WORK. Why? Because overloading is resolved using static type information and not dynamic type information. If you have: number[] N = {new rational(1,2),new integer(3),new real(3.14)}; number sum = new integer(0); foreach(number x in N) sum = add(sum,x); The code would not even compile because the static type of each N[i] is number. To make this code work you would have to write something like: if (x is integer && y is rational) return add((integer)x,(rational)y); else if (x is integer && y is real) return add((integer)x,(real)y)); else if... Using code like this means that your object oriented design has failed. Either you resort to brute force or try to extend the visitor pattern, the situation is unfortunate. You should complain. If you're asked to write a program that compares and adds numbers, then ideally you should be thinking mostly ABOUT MATH. This program is hard to write, but not because the math is hard but because getting to the right formula is hard. Most of the code you had to write had nothing to do with math. We write programs to implement algorithms. The ideal programming language should allow the programmer to concentrate on the algorithm and minimally on the mechanisms of the language itself. Only when you understand this point, will you see the need for a completely NEW KIND OF LANGUAGE... */ using System; public interface number { T accept1(visitor v, number n); //n1, n2. n1.accept1(v,n2) T accept2(visitor v, integer n); T accept2(visitor v, rational n); T accept2(visitor v, real n); } public interface visitor { T visit2(integer x,integer n); T visit2(integer x,rational n); T visit2(integer x,real n); T visit2(rational x, integer n); T visit2(rational x, rational n); T visit2(rational x, real n); T visit2(real x, integer n); T visit2(real x, rational n); T visit2(real x, real n); }//visitor public class integer : number { public int val; // value of integer public integer(int v) {val=v;} public override string ToString() { return val+"";} public T accept1(visitor v, number n) { return n.accept2(v,this); } public T accept2(visitor v, integer n) {return v.visit2(n,this);} public T accept2(visitor v, rational n) {return v.visit2(n,this);} public T accept2(visitor v, real n) {return v.visit2(n,this);} }//integer public class rational : number // fraction like 1/2 { public int n; //numerator and denominator public int d; public rational(int a, int b) { if (b!=0) { n=a; d=b;} else throw new Exception("bad rational"); } public override string ToString() { return n+"/"+d; } public T accept1(visitor v, number n) { return n.accept2(v,this); } public T accept2(visitor v, integer n) {return v.visit2(n,this);} public T accept2(visitor v, rational n) {return v.visit2(n,this);} public T accept2(visitor v, real n) {return v.visit2(n,this);} /* rules of rational arithmetic: a/b + c/d = (a*d+c*b)/b*d a/b * c/d = (a*c)/(b*d) a/b==c/d if a*d==b*c an integer n can be cast to a rational as n/1 a rational should not be cast to a real as that will cause loss of precision: only convert when adding rational to real/complex. */ }//rational public class real : number // approximate real number using a float { public double val; public real(double v) { val=v; } public override string ToString() { return val+""; } public T accept1(visitor v, number n) { return n.accept2(v,this); } public T accept2(visitor v, integer n) {return v.visit2(n,this);} public T accept2(visitor v, rational n) {return v.visit2(n,this);} public T accept2(visitor v, real n) {return v.visit2(n,this);} } // complex numbers like 2+3i public class complex // : number // doesn't implement number yet! { public double r; // real part public double i; // imaginary part (sqrt(-1)) public complex(double a, double b) {r=a; i=b; } public override string ToString() { return r+"+"+i+"i"; } // rules of complex arithmetic: // (a+bi) + (c+di) = (a+c) + (b+d)i // (a+bi) * (c+di) = (ac-bd) + (bc+ad)i // a+bi == c+di if a==c and b==d. // A real number r can be cast to complex r+0i and similarly with rationals // and integers. }//complex // visitor that tests for equality between two numbers: public class eqvisitor : visitor // test for equality between numbers { public bool visit2(integer x,integer y) { return x.val==y.val; } public bool visit2(integer x, rational y) => y.n==x.val*y.d; public bool visit2(integer x, real n) => x.val == n.val; public bool visit2(rational x, integer y) => visit2(y,x); public bool visit2(rational x, rational y) => x.n*y.d==y.n*x.d; public bool visit2(rational x, real y) => x.n==y.val*x.d; public bool visit2(real x, integer y) => visit2(y,x); public bool visit2(real x, rational y) => visit2(y,x); public bool visit2(real x, real y) => x.val==y.val; // hint "accidentally" left by prof ... // public bool visit2(rational x, complex y) => x.n==y.r*x.d && y.i==0; }//equality visitor // visitor that adds any two numbers public class adder :visitor { public number visit2(integer x,integer y) { return new integer(x.val+y.val); } public number visit2(integer x, rational y) => new rational(x.val*y.d+y.n,y.d); public number visit2(integer x, real n) => new real(n.val+x.val); public number visit2(rational x, integer y) => visit2(y,x); public number visit2(rational x, rational y) => new rational(x.n*y.d+y.n*x.d, x.d*y.d); public number visit2(rational x, real y) => new real(y.val+1.0*x.n/x.d); public number visit2(real x, integer y) => visit2(y,x); public number visit2(real x, rational y) => visit2(y,x); public number visit2(real x, real y) => new real(y.val+x.val); }// addition visitor public class tripledispatch { //Want this to work: public static void Main() { number[] N = {new integer(3), new rational(1,2),new rational(1,3)}; //,new real(3.14)}; // can't handle complex numbers yet: //number[] N = {new integer(3), new rational(1,2),new rational(1,3),new real(3.14),new complex(0.5,0), new complex(2.5,1)}; number half = new real(0.5); // search for a number in an array of numbers: eqvisitor eqv = new eqvisitor(); foreach(number x in N) if (half.accept1(eqv,x)) Console.WriteLine(x+" is equal to "+half); // adding up all the numbers: visitor adv = new adder(); number sum = new integer(0); foreach(number x in N) sum = sum.accept1(adv,x); Console.WriteLine("sum is "+sum); // add code to test your multiplication visitor }//main } /* The .add method must keep the same level of precision. e.g., adding an integer to a rational should return a rational, not a real. rational(1/3) is more precise than 0.3333... In other words, you can return a real when adding an integer to a real but not when you're adding a rational to another integer or rational. */