martes, 28 de febrero de 2012

Sobrecarga de operadores en C# utilizando vectores

Además de los operadores para los tipos primitivos, C# tiene una característica conocida como sobrecarga de operadores la cuál permite que los operadores para tipos primitivos puedan utilizarse con objetos, permitiéndonos definir el tipo de operación, como se va a efectuar, los tipos involucrados y valor que devuelve, un ejemplo típico de esta funcionalidad lo tenemos en la concatenación de objetos String en donde se utiliza el símbolo "+" para la concatenación de cadenas que igualmente es utilizado para la adicción de enteros, como se muestra en los siguientes ejemplos:



Adicción de enteros
--------------------
int a = 18;
int b = 66;
Console.Write(a + b); //imprime 84

Concatenación de cadenas
-------------------------

String s1 = "Once upon ";
String s2 = "a hero ";
Console.Write(s1 + s2); //imprime Once upon a hero


Para ejemplificar como funciona la sobrecarga de operadores en objetos utilizaremos las operaciones con vectores que se estudian en el álgebra lineal, por lo que antes de codificar daremos algunas definiciones.


Un vector es un objeto perteneciente a un espacio vectorial, que para los casos particulares de espacios R2 y R3 podemos representarlos gráficamente como segmentos de línea dirigidos con un punto inicial y un punto final describiendo la asociación de una magnitud y una dirección.

Un espacio vectorial consiste de:


  1. Un campo F de escalares (en general los número reales).

  2. Un conjunto V de elementos llamados vectores.

  3. Un conjunto de reglas (u operaciones) llamadas suma y multiplicación que según los textos especialistas en la materia se dividen en dos categorías, una para la adicción y otra para la multiplicación.

Los axiomas para la adición espacios vectoriales son:

  • A1. Si u y v están en V, entonces u + v está dentro de V

  • A2. u + v = v + u para todos u y v que estan en V

  • A3. u + (v + w) = (u + v) + w para todos los u,v y w en V

  • A4. Un elemento 0 en V existe tal que v + 0 = v para cada v en V.

  • A5. Para cada v en V, existe un elemento -v en V tal que -v + v = 0 y v + (-v) = 0



Los axiomas para la multiplicación son:

  • S1. Si v esta en V entonces av esta en V para cada a en R.

  • S2. a(v + w) = av + aw para cada v y w en V y para cada a en R.

  • S3. (a+b)v = av + bv para cada v en V y para cada a y b en R.

  • S4. a(bv) = (ab)v para cada v en V y para todos cada a y b en R.

  • S5. 1v = v para cada v en V.



Ahora con estos conceptos pasemos al código, primeramente crearemos nuestra clase Vector en donde utilizando la palabra reservada operator definiremos las operaciones para demostrar algunos de los axiomas expuestos. A continuación el listado de dicha clase.



using System;
using System.Text;

namespace VectorSpaces
{
public class Vector
{
delegate double Operation(double x, double y);
double[] _components;
Vector(){}
public Vector(double[] components){
this._components = components;
}
public double[] GetComponents(){
return _components;
}
public Vector(Vector v){
this._components = v.GetComponents();
}
public static Vector operator +(Vector u,Vector v){
return new Vector(Operating(u, v, (x, y) => (x + y)));
}
public static Vector operator *(Vector u, Vector v) {
return new Vector(Operating(u,v,(x,y) => (x * y)));
}
public static Vector operator -(Vector u){
if(u != null)
return scalarMultiplicacion(-1,u);
else
throw new ApplicationException("Vector is null");
}
public static Vector operator *(double d,Vector v){
return scalarMultiplicacion(d,v);
}
public static Vector operator *(Vector v,double d){
return scalarMultiplicacion(d,v);
}

static double[] Operating(Vector u, Vector v, Operation op) {
double[] resp = null;

if (u != null && v != null)
{
resp = new double[u.Length];
if (u.Length != v.Length)
throw new ApplicationException("Vectors length must be equals in Length");
else
{
for (int i = 0; i < u.Length; i++)
{
resp[i] = op(u.GetComponents()[i], v.GetComponents()[i]);
}

}
}
return resp;
}
static Vector scalarMultiplicacion(double d,Vector v){
double[] resp = new double[v.Length];
Vector r = null;
for(int i = 0;i < v.Length;i++)
resp[i] = d * v.GetComponents ()[i];
r = new Vector(resp);
return r;
}
public int Length{ get{ return _components.Length;}}
public override string ToString ()
{
StringBuilder buf = new StringBuilder();
buf.Append("[ ");
foreach(double d in _components){
buf.AppendFormat("{0} ",d.ToString());
}
buf.Append(" ]");
return buf.ToString();
}

}
}

Como vemos en este listado utilizamos la sobrecarga de operadores utilizando la palabra clave operator en los siguientes métodos:


public static Vector operator +(Vector u,Vector v)
public static Vector operator *(Vector u, Vector v)
public static Vector operator -(Vector u)
public static Vector operator *(double d,Vector v)
public static Vector operator *(Vector v,double d)

Aquí definimos la operación, el número de parámetros con los que se llevará a cabo y por supuesto su implementación.
Ahora con el siguiente listado mostraremos la utilización de clase Vector y el uso de la sobrecarga de operadores para vectores de números reales:

using System;
using VectorSpaces;

namespace VectorSpaces
{
class MainClass
{
public static void Main (string[] args)
{
//Set of Vectors
double[] p = {4,12,20,28,36,44,52,60};
double[] q = {6,14,22,30,38,46,54,62};
double[] r = {8,16,24,32,40,48,56,64};
double[] s = {10,18,26,34,42,50,58,66};
//Set of scalars
double alpha = 3;
Vector Px = new Vector(p);
Vector Qx = new Vector(q);
Console.WriteLine("Set of vectors:\n");
PrintArray("P",p);
PrintArray("q", q);
PrintArray("r", r);
PrintArray("s", s);
Console.WriteLine("\nscalar:\n");
Console.WriteLine("a = 3");
Console.WriteLine("\nOperations:\n");
Console.WriteLine("A1) p + q = {0}", Px + Qx);
Console.WriteLine("\n");
Vector Rx = new Vector(r);
Console.WriteLine("A3) p + (q + r) = {0}",(Px + (Qx + Rx)).ToString());
Console.WriteLine(" (p + q) + r = {0}",((Px + Qx) + Rx).ToString());
Console.WriteLine("\n");
Vector Sx = new Vector(s);
Console.WriteLine("A5) -s + s = {0}",(-Sx + Sx).ToString());
Console.WriteLine(" s + (-s) = {0}",(Sx + (-Sx)).ToString());
Console.WriteLine("\n");
Console.WriteLine("S1) ap = {0} ",(alpha * Px).ToString());
Console.WriteLine("\n");
Console.WriteLine("S3) (p + s)a = {0}",((Px + Sx) * alpha).ToString());
Console.WriteLine(" ap + as = {0} ",((alpha * Px) + (alpha * Sx)).ToString());
Console.ReadLine();
}

static void PrintArray(string l,double[] doubleArray) {
Console.Write("{0} = ", l);
for (int i = 0; i < doubleArray.Length; i++)
{
Console.Write("{0} ",doubleArray[i]);
if (i == (doubleArray.Length - 1))
Console.Write("\n");
}
}
}
}

Si agregamos estas clases a un proyecto de consola en MonoDevelop podemos tener una solución lista para corregir y compilar.


Al ejecutar la solución, veremos el resultado como en la siguiente imagen:


Descarga el código fuente en un proyecto para Visual Srudio o MonoDevelop