Metonymie.com :: Apuntes
Bloque de Colores

foldr, foldl, scanl, scanr

Icono de Post August 13 2009 :: Haskell, Programación Funcional ::

Foldr y Foldl

foldr y foldl son funciones de plegado sobre la estructura recursiva de las listas.

Cumpliendose que:

foldr f z (x1 : (x2 : (… : (xn : []))))

= x1 ‘f’ (x2 ‘f’ (… ‘f’ (xn ‘f’ z))))

foldl f z (x1 : (x2 : (… : (xn : []))))

= ((((z ‘f’ x1) ‘f’ x2) … ‘f’ xn-1) ‘f’ xn)

En ambas se toma una función y se la aplica sucesivamente a cada miembre de una lista, hasta recibir un resultado final.

Ejemplos. Definición de una función and que devuelve True si todos los elementos de una lista de Bool son verdaderos y definición de una función or que devuelve True si alguno es verdadero.


and3 :: [Bool] -> Bool
and3 = foldr (&&) True

or3 :: [Bool] -> Bool
or3 = foldr (||) False

scanl y scanl, devuelven los pasos intermedios de una operación de foldl y foldr respectivamente. Los siguientes ejemplos demuestran que si una operación no es asociativa (como la resta en los naturales) foldr y foldl devuelven resultados distintos.


*Main> scanl (+) 0 [1..5]
[0,1,3,6,10,15]
*Main> scanr (+) 0 [1..5]
[15,14,12,9,5,0]

*Main> scanr (-) 0 [1..5]
[3,-2,4,-1,5,0]
*Main> scanl (-) 0 [1..5]
[0,-1,-3,-6,-10,-15]

El segundo argumento es el caso final o primero a aplicarse.


*Main> scanl (*) 10 [1..5]
[10,10,20,60,240,1200]
*Main> scanr (*) 10 [1..5]
[1200,1200,600,200,50,10]

Referencias

Razonando con Haskell;Blas C. Ruiz, Francisco Gutiérrez, Pablo Guerrero y José E. Gallardo;Thomson Editores Spain;2004

Autor: Emiliano Martínez Luque.

Comments Off on foldr, foldl, scanl, scanr

Analizando los punteros de una linked list

Icono de Post August 10 2009 :: c, Estructuras de datos ::

Uno de los temas que es difícil de entender en C es el tema de los punteros y como se relacionan con las estructuras de datos. Lo difícil ( o por lo menos fue difícil para mi que venia de lenguajes dinámicos ) es entender que los punteros guardan direcciones a memoria y a que a partir de esto se puede hacer el entrelazamiento de diferentes estructuras apuntadas. Voy a usar los ejemplo de linked list de C & Data Structures para mostrar como se da esto.

Primero creamos la estructura y una función para insertar elementos en la misma:

struct node {

   int data;

   struct node *link;

};



/*Insert en versión recursiva */

struct node *insert(struct node *p, int n) {

   if(p==NULL) {

      p=(struct node *)malloc(sizeof(struct node));

      if(p==NULL) {

        printf("Allocation Errorn");

         exit(0);

      }

      p-> data = n;

      p-> link = NULL;

   } else {

       p->link = insert(p->link,n);

    }

      return(p);

}

Nota: Se hace la búsqueda del ultimo nodo para insertar en forma recursiva.

Luego hago una función para mostrar la lista:

void printnode( struct node *p) {

    printf("Memory Space: %pt Value: %d t Link: %ptn",p, p-> data, p-> link);

}



void printlist ( struct node *p ) {

      printf("The data values in the list aren");

      while (p!= NULL) {

         printnode(p);

         p = p-> link;

      }

}

La función printnode, muestra primero la dirección de memoria de la estructura, después su valor y finalmente la dirección de memoria a la que apunta, de esta manera podemos ver como se enlazan los nodos dentro de la lista.

Agregando y borrando nodos.

   /* a function which inserts a newly created node after the specified

node */

    //node_no empieza en 0 como en los arrays

   struct node * newinsert ( struct node *p, int node_no, int value ) {

      struct node *temp, * temp1;

      int i;

      temp = p;



      for(i=0; i<node_no && temp->link != NULL; i++) {

            temp = temp->link;

      }

        if(i != node_no ) {

            printf("Se pide insertar en un nodo mayor a la cantidad existente, se agrega tras el ultimo nodo existente.n");

        }

        temp1 = ( struct node * )malloc ( sizeof ( struct node ));

        if ( temp1 == NULL ) {

            printf( " Cannot allocate n");

            exit (0);

        }

        temp1->data = value;

        temp1->link = temp->link;

        printf("The newly created node to be inserted is:n");

        printnode(temp1);

        temp->link = temp1;

        return(p);

   }



   struct node * erasenode( struct node *p, int node_no) {

        struct node *temp, *prev;

        int i;

        temp = p;

        if(node_no == 0) {

            temp = p->link;

            free(p);

            p = temp;

        } else {

            i = 0;

            while(i<node_no && temp->link != NULL) {

                prev = temp;

                temp = temp->link;

                i++;

            }

            if(i != (node_no)) {

                printf("Se pide borrar un nodo que no existe.n");

                exit(0);

            }

            prev->link = temp->link;

            free(temp);

        }

        return(p);

   }

Corriéndolo en el main:

int main()

{

      int n;

      int x;

      struct node *start = NULL ;

      printf("Enter the nodes to be created n");

      scanf("%d",&n);

      while ( n-- > 0 )

      {

              printf( "Enter the data values to be placed in a noden");

         scanf("%d",&x);

         start = insert ( start,x);

      }

      printf("The created list isn");

      printlist ( start );

      start = newinsert(start, 2, 7);

      printlist (start);

      start = erasenode(start, 2);

      printlist ( start );

      return 0;

}

Resultado en pantalla:

Enter the nodes to be created 
5
Enter the data values to be placed in a node
5
Enter the data values to be placed in a node
4
Enter the data values to be placed in a node
3
Enter the data values to be placed in a node
2
Enter the data values to be placed in a node
1
The created list is
The data values in the list are
Memory Space: 0x81a5008	 Value: 5 	 Link: 0x81a5018	
Memory Space: 0x81a5018	 Value: 4 	 Link: 0x81a5028	
Memory Space: 0x81a5028	 Value: 3 	 Link: 0x81a5038	
Memory Space: 0x81a5038	 Value: 2 	 Link: 0x81a5048	
Memory Space: 0x81a5048	 Value: 1 	 Link: (nil)	
The newly created node to be inserted is:
Memory Space: 0x81a5058	 Value: 7 	 Link: 0x81a5038	
The data values in the list are
Memory Space: 0x81a5008	 Value: 5 	 Link: 0x81a5018	
Memory Space: 0x81a5018	 Value: 4 	 Link: 0x81a5028	
Memory Space: 0x81a5028	 Value: 3 	 Link: 0x81a5058	
Memory Space: 0x81a5058	 Value: 7 	 Link: 0x81a5038	
Memory Space: 0x81a5038	 Value: 2 	 Link: 0x81a5048	
Memory Space: 0x81a5048	 Value: 1 	 Link: (nil)	
The data values in the list are
Memory Space: 0x81a5008	 Value: 5 	 Link: 0x81a5018	
Memory Space: 0x81a5018	 Value: 4 	 Link: 0x81a5058	
Memory Space: 0x81a5058	 Value: 7 	 Link: 0x81a5038	
Memory Space: 0x81a5038	 Value: 2 	 Link: 0x81a5048	
Memory Space: 0x81a5048	 Value: 1 	 Link: (nil)	

Se ve aca como en la primera corrida los nodos estan ubicados uno tras otro en la memoria, pero al agregar un nuevo nodo se salta en el tercer nodo ( en este caso, Memory Space: 0x81a5028 Value: 3 Link: 0x81a5058 ) se salta al agregado ( Memory Space: 0x81a5058 Value: 7 Link: 0x81a5038 ) y del agregado al que antes existia contiguamente ( Memory Space: 0x81a5038 Value: 2 Link: 0x81a5048 ). Algo similar ocurre cuando se borra un nodo.

Revirtiendo y ordenando la lista.

/* a function to sort reverse list */

struct node *reverse(struct node *p) {

   struct node *prev, *curr;

   prev = NULL;

   curr = p;

   while (curr != NULL) {

      p = p-> link;

      curr-> link = prev;

      prev = curr;

      curr = p;

   }

   return(prev);

}



/* a function to sort a list */

struct node *sortlist(struct node *p)

{

   struct node *temp1,*temp2,*min,*prev,*q;

   q = NULL;

   while(p != NULL) {

      //Encontrar el menor de la lista p

      prev = NULL;

      min = temp1 = p;

      temp2 = p -> link;

      while ( temp2 != NULL ) {

              if(min -> data > temp2 -> data) {

                     min = temp2;

                     prev = temp1;

            }

         temp1 = temp2;

         temp2 = temp2-> link;

      }



      //Cambiar el link en la lista p del previo al menor al siguiente del menor

      if(prev == NULL) {

             p = min -> link;

      } else {

             prev -> link = min -> link;

      }



      //Agregar el menor a q

      min -> link = NULL;

      if( q == NULL){

            q = min; /* moves the node with lowest data value in the list pointed to by p to the list pointed to by q as a first node*/

       } else {

            temp1 = q;

            /* traverses the list pointed to by q to get pointer to its last node */

            while( temp1 -> link != NULL) {

                         temp1 = temp1 -> link;

            }

            temp1 -> link = min; /* moves the node with lowest data value in the list pointed to by p to the list pointed to by q at the end of list pointed by q*/

      }

   }

   return (q);

}

Vemos la impresión en pantalla de como quedan ordenados los nodos:

The sorted list is
The data values in the list are
Memory Space: 0x8c03048	 Value: 1 	 Link: 0x8c03038	
Memory Space: 0x8c03038	 Value: 2 	 Link: 0x8c03018	
Memory Space: 0x8c03018	 Value: 4 	 Link: 0x8c03008	
Memory Space: 0x8c03008	 Value: 5 	 Link: 0x8c03058	
Memory Space: 0x8c03058	 Value: 7 	 Link: (nil)	
The reversed list is
The data values in the list are
Memory Space: 0x8c03058	 Value: 7 	 Link: 0x8c03008	
Memory Space: 0x8c03008	 Value: 5 	 Link: 0x8c03018	
Memory Space: 0x8c03018	 Value: 4 	 Link: 0x8c03038	
Memory Space: 0x8c03038	 Value: 2 	 Link: 0x8c03048	
Memory Space: 0x8c03048	 Value: 1 	 Link: (nil)	

Referencias

C & Data Structures, Deshpande, Kakde; Charles River Media, 2004

Autor: Emiliano Martínez Luque.

Comments Off on Analizando los punteros de una linked list

Funciones (Definiciones Básicas)

Icono de Post August 10 2009 :: Apuntes, Teoría de Conjuntos ::

Definición de Función.

Sea F una relación de A en B. F es una función si se cumple que:

F: A -> B <=> ∀a ∈ A( ∃b ∈ B ( (a, b) ∈ F ∧ ( (a, c) ∈ F => b = c)))

Coloquialmente para todo elemento de A existe un elemento de B tal que (a, b) ∈ F y este elemento es unico.

Considerando esto se suele expresar el valor de f  en a es b o en simbolos:

f(a) = b

Composición de Funciones

Sean f: A -> B y g: B -> C, entonces (g o f) (a) : A -> C es equivalente a g ( f (a) ) .

Funciones inyectivas (uno a uno)

f es inyectiva si se cumple que:

∀a1 ∈ A ( ¬∃ a2 ∈ A ( f(a1) = f(a2) )  

Funciones suryectivas

f : A -> B es suryectiva si se cumple que:

∀b ∈ B( ∃ a ∈ A( f(a) = b) )

Función inversa

Si f: A -> B es inyectiva y suryectiva se cumple que:

  • f-1: B -> A también es función.
  • (f o f-1) = IA
  • (f-1 o f) = IB

Donde IC es la función identidad de C, I: C -> C tal que ∀ c ∈ C ( I(c) = c) ).

Referencias

How to Prove It: A Structured Approach; Velleman, Daniel; Cambridge University Press; 2006

Autor: Emiliano Martínez Luque.

Comments Off on Funciones (Definiciones Básicas)

Definición de estructura de datos recursiva

Icono de Post August 10 2009 :: Estructuras de datos ::

A recursive data structure is a data structure that has the same form regardless of the size of the data.

Como ejemplo: linked list, arbol binario, etc.

P S Deshpande, O G Kakde: C & Data Structures

Iterando por una matriz cuadrada recursivamente

Icono de Post June 5 2009 :: Algoritmia, Estructuras de datos, Java, Lógica ::

En otro ejercicio de la facultad se me pedía sumar todos los valores individuales de una matriz cuadrada a través de un algoritmo recursivo. Mi resolución en Java fue la siguiente:


	public static int sumaMatriz(int arr2[][], int m, int n) {
		if( (m == n) && (m == 0) ) return arr2[m][n];
		if(m == n) return arr2[m][n] + sumaMatriz(arr2, m-1, n) + sumaMatriz(arr2, m-1, n-1) + sumaMatriz(arr2, m, n-1); //Caso a
		if(m > n) {
			if(n > 0) {
				return arr2[m][n] + sumaMatriz(arr2, m, n-1); //Caso b
			} else {	
				return arr2[m][n];
			}
		} else { //n > m
			if(m > 0) {
				return arr2[m][n] + sumaMatriz(arr2, m-1, n); //Caso c
			} else {
				return arr2[m][n];
			}
		}
	}

Me interesa este ejercicio para pensar de otra forma sobre corrección de programas. La pregunta es si el algoritmo es correcto y después de pensarlo un poco llegue a la conclusión de que lo era. Por lo pronto para hacer una demostración formal debería usar inducción estructural pero este post trata más bien sobre trabajo previo a una demostración, lo que en How to Prove it llaman scratchwork. O sea el razonamiento a través del cual se llega a una demostración.

Primero que nada hagamos un poco de debugging con un caso simple de una matriz de 3x3. Para esto agregamos lo siguiente al código anterior:


	public static int sumaMatriz(int arr2[][], int m, int n) {
		if( (m == n) && (m == 0) ) return arr2[m][n];
		System.out.println(n + " " + m);
		if(m == n) return arr2[m][n] + sumaMatriz(arr2, m-1, n) + sumaMatriz(arr2, m-1, n-1) + sumaMatriz(arr2, m, n-1); //Caso a
		if(m > n) {
....
....

Y testeamos la función llamándola con:


public class TP4 {

	public static void main (String[] args) {
		int[][] arr2;
		arr2 = new int[3][3];
		arr2[0][0] = 1;
		arr2[0][1] = 2;
		arr2[0][2] = 2;
		arr2[1][0] = 4;
		arr2[1][1] = 2;
		arr2[1][2] = 3;
		arr2[2][0] = 4;
		arr2[2][1] = 7;
		arr2[2][2] = 8;
		System.out.println("Ej9:");
		System.out.println( TP4.sumaMatriz(arr2,2 ,2));
	}
}

Lo que muestra por consola es:


Ej9:

2 2
2 1
2 0
1 1
1 0
0 0
0 1
1 2
0 2

33

Podemos ver que todos los elementos de la matriz fueron accedidos y que el resultado es el correcto. Ahora imaginémonos el árbol de las llamadas de función.

Vemos que los elementos de la diagonal principal hacen 3 llamados más, al próximo elemento de la diagonal principal, y a los dos elementos con m o n decrementados en uno. Mientras que los elementos que no están en la diagonal principal hacen un solo llamado.

Razonamiento (Informal) sobre la corrección del Algoritmo

Consideremos una matriz cuadrada de grado m, tal que m sea mayor que 5 (esto es por claridad para hacer este ejercicio) y concentrémonos en los últimos 5x5 casilleros del limite inferior derecho, que es donde comienza el algoritmo.

Primer paso toma el elemento en m x m:

A partir de esto se realiza la llamada recursiva:


if(m == n) return arr2[m][n] + sumaMatriz(arr2, m-1, n) + sumaMatriz(arr2, m-1, n-1) + sumaMatriz(arr2, m, n-1); //Caso a

De la que se desprenden los siguientes 3 pasos.

El caso b:


return arr2[m][n] + sumaMatriz(arr2, m, n-1); //Caso b

De nuevo el caso a:


if(m == n) return arr2[m][n] + sumaMatriz(arr2, m-1, n) + sumaMatriz(arr2, m-1, n-1) + sumaMatriz(arr2, m, n-1); //Caso a

Y finalmente el caso c:


				return arr2[m][n] + sumaMatriz(arr2, m-1, n); //Caso c

Hemos llegado a llenar una matriz de 3x3, pero esto no nos demuestra nada, ahora lo interesante es saber que pasa cuando continua el algoritmo en los pasos siguientes y aquí es donde esta el truco.

Lo interesante es que las 3 posibles llamadas recursivas garantizan la siguiente:

  • Por el caso a, desde una llamada de la función desde un casillero en la diagonal principal, no se llamara a la función en ningún casillero que este en la misma linea o columna de los elemento de donde han sido llamados por un casillero en la diagonal principal anterior.También por el caso a, la iteración a través de elementos de la diagonal principal eventualmente cubrirá todos los elementos de la diagonal principal de la matriz.

  • Por el caso b, desde una llamada de la función desde un casillero en una columna que no sea en la diagonal principal, siempre continuara por esa misma columna hasta completarla.

  • Por el caso c, desde una llamada de la función desde un casillero en la fila que no sea en la diagonal principal, siempre continuara por esa misma fila hasta completarla.

A partir de esto podemos inferir que el algoritmo pasa por todos los elementos de cualquier matriz cuadrada.

Importante

Quiero recalcar que esto es un razonamiento, a partir del cual por inducción estructural podría construirse una demostración, pero en términos de lógica formal no es una demostración. Igual, no deja de ser un razonamiento valido, lo cierto es que en la programación como practica es importante poder pensar si lo que se esta haciendo es correcto y si los programas que uno hace cumplen con las necesidades que uno tiene, pero también es cierto que sería completamente Overkill el tener que hacer una demostración formal de cada programa que uno hace. Lo importante es tener herramientas para poder razonar y pensar sobre como se programa.

Autor: Emiliano Martínez Luque.

Comments Off on Iterando por una matriz cuadrada recursivamente

Implementación del Algoritmo de Euclides

Icono de Post June 4 2009 :: Algoritmia, Haskell, Java ::

En un ejercicio de la facultad me pidieron hacer un función recursiva que encuentre el Maximo común divisor entre dos números por medio de restas. Ahora, esto que parece un simple ejercicio en realidad se engancha con un montón de temas interesantes de la historia de la matemática. Bueno, lo primero que se me ocurrió fue factorizar los dos números y después buscar los factores comunes, obviamente esta idea no es lo que se dice fácil ( quizás hasta imposible ) de hacer recursivamente. Se me ocurrió investigar un poco y encontré el Algoritmo de Euclides. La implementación básica en Haskell es:

algEuclid :: Int -> Int -> Int
algEuclid a b	| c < 0 	= algEuclid b a
		| c == 0	= b
		| c >= b 	= algEuclid c b
		| c < b		= algEuclid b c
		where c = a - b

La versión en Haskell es super clara, pero se me pedía implementarla en Java y quedo así:

public class Ej {

	public static void main (String[] args) {
		//Ej8. Maximo Comun Divisor de 2 numeros
		System.out.println("Ej8:");
		System.out.println( TP4.algEuclid(64, 48));
	}


	public static int algEuclid( int a, int b) {
		int c = a - b;
		if ( c < 0) {
			return algEuclid(b, a);
		} else if (c == 0) {
			return b;
		} else if ( c >= b ) {
			return algEuclid(c, b);
		} //(c < b)
			return algEuclid(b, c);
	}
	
}

Lo interesante es que Euclides hace su demostración en términos puramente geométricos y en términos geométricos existen magnitudes a las que no es posible aplicarles esta formula. En particular raíz cuadrada de 2, es decir que no son conmensurables. Esto tuvo profundas implicancias en el desarrollo de la matemática, porque durante siglos al existir relaciones que no podían ser expresadas puramente en números (tal y como pretendían los pitagóricos) los griegos decidieron priorizar la geometría sobre otras formas de razonamiento matemático. Fue necesario un salto de paradigma, en el sentido de Kuhn, a saber la aceptación de los números irracionales como objetos matemáticos por derecho propio para poder continuar avanzando en diversas áreas de la matemática.

En definitiva, si la historia de las matemáticas demuestra algo, es que si hay algo que no podemos entender actualmente o que parece que nos lleva a un callejón sin salida, en algún momento quizás con un enfoque nuevo probablemente podremos integrarlo a un sistema explicativo de orden superior.

Referencias

Journey through Genius: The Great Theorems of Mathematics; William Dunham; Penguin Books; 1990

Autor: Emiliano Martínez Luque.

Comments Off on Implementación del Algoritmo de Euclides

Armonizando escalas con Haskell

Icono de Post May 22 2009 :: Haskell ::

Este es el primer programita en Haskell que hice por gusto propio, principalmente para afianzar un poco conceptos y además para probar tipos de datos y sobre todo de pensar desde Haskell. Lo fantástico es que estas cosas las hacia a mano, cuando tenía 17 años y estaba aprendiendo armonía (Por mi cuenta, obviamente).

Primero definí un tipo de datos Nota y una función sucesor cíclica (dado que estoy interesado en relaciones puramente armónicas).


data Nota = A | Bb | B | C | Db | D | Eb | E | F | Gb | G | Ab deriving (Eq)

suc :: Nota -> Nota
suc A = Bb; suc Bb = B; suc B = C; suc C = Db; suc Db = D; suc D = Eb; suc Eb = E; suc E = F; suc F = Gb; suc Gb = G; suc G = Ab; suc Ab = A;

Después hice que el tipo de datos Nota fuera instancia de la clase show, dando una definición especifica de la función show para mi tipo de datos para tratar los problemas del doble nombre de determinadas notas ( equivalencias enarmonicas ).


instance Show Nota where
	show A = show "A"
	show Bb = show "A#/Bb"
	show B = show "B"
	show C = show "C"
	show Db = show "C#/Db"
	show D = show "D"
	show Eb = show "D#/Eb"
	show E = show "E"
	show F = show "F"
	show Gb = show "F#/Gb"
	show G = show "G"
	show Ab = show "G#/Ab"

A partir de esto puedo definir la función intervalo, que toma un número natural (de 0 a 11, aunque se puede considerar al tipo Nota y a la función intervalo como compartiendo propiedades con la aritmética mod(12), por lo que n puede tomar valores mayores a 11 y los resultados van a ser los mismos a intervalo _ (n mod (12))). En un principio había pensado que se podía pensar como un isomorfismo, pero no estoy convencido, aunque si tomamos aplicación parcial de intervalo x como una función en si misma, ahí si podría serlo.


intervalo :: Nota -> Integer -> Nota
intervalo a 0 = a
intervalo a 1 = suc(a)
intervalo a n = suc( intervalo a (n-1))

No intente definir los intervalos a partir de sus nombres usuales sino simplemente desde su distancia (ascendente) a partir de la nota base. O sea, se sobreentiende que si le doy un valor de uno representa a una segunda menor, de 4 a una tercera mayor, etc. (Por otro lado es como siempre me acerque al concepto de intervalo).

Paso siguiente construir funciones para definir las cuatro triadas básicas.


type Triada = (Nota, Nota, Nota) 

maj :: Nota -> Triada
maj a = (a, intervalo a 4, intervalo a 7)

men :: Nota -> Triada
men a = (a, intervalo a 3, intervalo a 7)

aug :: Nota -> Triada
aug a = (a, intervalo a 4, intervalo a 8 )

dim :: Nota -> Triada
dim a = (a, intervalo a 3, intervalo a 6)

Y luego definir las escalas y modos básicos.


escala :: Nota -> [Integer] -> [Nota]
escala a [] = []
escala a (x:xs) = intervalo a x : escala a xs

escala_cromatica :: Nota -> [Nota]
escala_cromatica a = escala a [0..11]

escala_mayor :: Nota -> [Nota]
escala_mayor a = escala a [0,2,4,5,7,9,11]

escala_menor :: Nota -> [Nota]
escala_menor a = escala a [0,2,3,5,7,8,10]

escala_menor_armonica :: Nota -> [Nota]
escala_menor_armonica a = escala a [0,2,3,5,7,8,11]

escala_menor_melodica :: Nota -> [Nota]
escala_menor_melodica a = escala a [0,2,3,5,7,9,11]

modo_dorico :: Nota -> [Nota]
modo_dorico a = escala a [0,2,3,5,7,9,10]

modo_frigio :: Nota -> [Nota]
modo_frigio a = escala a [0,1,3,5,7,8,10]

modo_lidio :: Nota -> [Nota]
modo_lidio a = escala a [0,2,4,6,7,9,11]

modo_mixolidio :: Nota -> [Nota]
modo_mixolidio a = escala a [0,2,4,5,7,9,10]

modo_locrio :: Nota -> [Nota]
modo_locrio a = escala a [0,1,3,5,6,8,10]

escala_disminuida :: Nota -> [Nota]
escala_disminuida a = escala a [0,2,3,5,6,8,9,11]

escala_aumentada :: Nota -> [Nota]
escala_aumentada a = escala a [0,2,4,6,8,10]

Dado que voy a intentar hacer reconocimiento de acordes, se vuelve necesario definir la función distancia (que es distancia ascendente).


distancia :: Nota -> Nota -> Integer
distancia a b 	| (intervalo a 0) == b 	= 0
		| otherwise 		= 1 + (distancia (suc a) b)

Probar de Reconocer Acordes.


es_triada_mayor :: Triada -> Bool
es_triada_mayor (a,b,c) | (distancia a b) == 4 && (distancia a c) == 7 = True
			| otherwise = False

es_triada_menor :: Triada -> Bool
es_triada_menor (a,b,c) | (distancia a b) == 3 && (distancia a c) == 7 = True
			| otherwise = False

es_triada_disminuida :: Triada -> Bool
es_triada_disminuida (a,b,c) 	| (distancia a b) == 3 && (distancia a c) == 6 = True
				| otherwise = False

es_triada_aumentada :: Triada -> Bool
es_triada_aumentada (a,b,c) 	| (distancia a b) == 4 && (distancia a c) == 8 = True
				| otherwise = False

Ejemplo de uso:


*Main> es_triada_mayor (A,Db,E)
True
*Main> es_triada_mayor (A,C,E)
False

Aclaración: Como el tipo de datos es cíclico a partir de la función suc y estamos trabajando con triadas básicas, no importan las diferentes inversiones de los acordes.

Ver si una triada existe en una escala:


esta_triada_en_escala :: [Nota] -> Triada -> Bool
esta_triada_en_escala xs (a,b,c)	| elem a xs && elem b xs && elem c xs = True
					| otherwise = False
{- 
*Main> esta_triada_en_escala (men A) (modo_dorico A)
True
-}

Ahora ya puedo armonizar escalas por un método de Brute Force, no muy elegante, pero por lo pronto efectivo. Primero creo todas las posibles triadas (4*12 = 48) y después filtro las que corresponden a la escala, además de crear una función que muestre los resultados de una forma visualmente más agradable.


show_triada :: Triada -> String
show_triada (a,b,c) 	| es_triada_mayor (a,b,c) = strip_slashes(show a) ++ " maj"
			| es_triada_menor (a,b,c) = strip_slashes(show a) ++ " min"
			| es_triada_aumentada (a,b,c) = strip_slashes(show a) ++ " aug"
			| es_triada_disminuida (a,b,c) = strip_slashes(show a) ++ " dim"
			| otherwise = strip_slashes(show (a,b,c)) ++ " No es Triada"
			where 
				strip_slashes :: [Char] -> [Char]
				strip_slashes [] = []
				strip_slashes (x:xs) 	| x == '"' = strip_slashes xs
							| otherwise = x : strip_slashes xs		

todas_las_triadas :: Nota -> [Triada]
todas_las_triadas a = concat( map hacer_triadas_de (escala_cromatica a))
			where
			hacer_triadas_de :: Nota -> [Triada]
			hacer_triadas_de a = [(dim a),(men a),(maj a),(aug a)]
armonizar_escala :: [Nota] -> [String]
armonizar_escala (x:xs) = map show_triada (filter (esta_triada_en_escala (x:xs)) (todas_las_triadas x))

{-
*Main> armonizar_escala (modo_dorico A)
["A min","B min","C maj","D maj","E min","F#/Gb dim","G maj"]
*Main> armonizar_escala (escala_menor_armonica E)
["E min","F#/Gb dim","G aug","A dim","A min","B maj","B aug","C dim","C min","C maj","D#/Eb dim","D#/Eb aug"]
*Main> armonizar_escala (escala_aumentada E)
["E aug","F#/Gb aug","G#/Ab aug","A#/Bb aug","C aug","D aug"]
*Main> armonizar_escala (escala_disminuida E)
["E dim","F#/Gb dim","F#/Gb min","F#/Gb maj","G dim","A dim","A min","A maj","A#/Bb dim","C dim","C min","C maj","C#/Db dim","D#/Eb dim","D#/Eb min","D#/Eb maj"]
-}

Detalle: otro ejemplo de currying en esta_triada_en_escala (x:xs).

Obviamente esto un principio y hay un montón de ideas que se me ocurrieron mientras hacia esto. Así que, sera continuado.

Autor: Emiliano Martínez Luque.

Comments Off on Armonizando escalas con Haskell

Funciones de Orden Superior Y Combinadores

Icono de Post May 19 2009 :: Haskell, Programación Funcional ::

Una función de orden superior es una función que devuelve otra función.

Un Combinador es un función de orden superior que encapsula un determinado comportamiento de una clase de funciones y provee un template para la creación de estas. Ej:


iter :: (Integer -> Integer -> Integer) -> Integer -> ( Integer -> Integer )
iter op e = fun
	where 
	fun 0	= e
	fun m@(n + 1) = op m (fun n)

factorial :: Integer -> Integer
factorial = iter (*) 1

sumatorio :: Integer -> Integer
sumatorio = iter (+) 0

Referencias

Razonando con Haskell;Blas C. Ruiz, Francisco Gutiérrez, Pablo Guerrero y José E. Gallardo;Thomson Editores Spain;2004

Autor: Emiliano Martínez Luque.

Comments Off on Funciones de Orden Superior Y Combinadores

Un poco de Análisis en Haskell

Icono de Post May 18 2009 :: Análisis, Haskell, Programación Funcional ::

Teniendo en cuenta que:

Derivada   f   x = lim h   0   f   ( x + h ) f   x h

Se puede hacer una definición de la función derivada en Haskell como:


derivada :: (Double -> Double) -> Double -> Double
derivada f x = ( f(x + h) - f x ) / h
		where h = 0.0001

Considerando que la derivada de f(x) = x2 es f'(x) = 2x, testeamos la definición:


*Main> derivada (x -> x^2) 2
4.0001000000078335

Afinando la definición de h:


derivada :: (Double -> Double) -> Double -> Double
derivada f x = ( f(x + h) - f x ) / h
		where h = 0.00000000001

Da como resultado:


*Main> derivada (x -> x^2) 2
4.000000330961484

Independientemente ( o más bien a pesar ) de los problemas Inherentes a la aritmetica de punto flotante, se ve que Cuando h tiende a 0 más se acerca al resultado esperable.

Aplicación Parcial (Currying)

Se puede usar la definición de derivada anterior para producir una función y no un resultado, ej:


casiPor2 :: (Double -> Double)
casiPor2  = derivada (x -> x^2)

Que si bien no es equivalente a x -> 2 * x, (por los problemas de la aritmetica de punto flotante), se le acerca.

Una versión de testeo con diversos niveles de acercamiento.

Esto es interesante para entender gráficamente el concepto de limite.


derivada3 :: (Double -> Double) -> Double -> Double ->Double
derivada3 f x h = (f(x + h) - f x ) / h

tiendeA0 :: Double -> [Double]
tiendeA0 h = h : ( tiendeA0 (h*0.1) )

Testeo de tender a 0:


*Main> take 10 (tiendeA0 1)
[1.0, 0.1, 1.0000000000000002e-2, 1.0000000000000002e-3, 1.0000000000000003e-4, 1.0000000000000004e-5, 1.0000000000000004e-6, 1.0000000000000005e-7, 1.0000000000000005e-8, 1.0000000000000005e-9]

Ahora considerando que: f(x) = x^3 => f'(x) = 3x^2 y En particular si x=2, f'(x) = 3x^2 = 3.4 = 12; podemos probar su aplicación para ver el comportamiento extraño del punto flotante.


*Main> map ( derivada3 (x -> x^3) 2 ) (take 10 (tiendeA0 1))
[19.0, 12.61000000000001, 12.060099999999705, 12.006000999999596, 12.000600010022563, 12.00006000026121, 12.000006002210734, 12.000000584322374, 11.999999927070343, 12.000000992884447]

Referencias

Razonando con Haskell;Blas C. Ruiz, Francisco Gutiérrez, Pablo Guerrero y José E. Gallardo;Thomson Editores Spain;2004

Calculus; M. Spivak; Editorial Reverte; 1992

Algebra de Boole Aplicada a Circuitos de Computación; M. C. Guinzburg; Biblioteca Técnica Superior; 1998

Autor: Emiliano Martínez Luque.

Comments Off on Un poco de Análisis en Haskell

Razonando sobre corrección de Programas en Haskell

Icono de Post May 18 2009 :: Algoritmia, Combinatoria, Haskell, Lógica ::

Una de las cosas más interesantes de Haskell es lo facil que resulta razonar sobre corrección de programas. Veamos un ejemplo simple; construiremos una función que tome 3 elementos ordenables y los devuelva como una triada ordenada (Ejercicio 2.40 de Razonando con Haskell):


ordena3:: Ord a => a -> a -> a -> (a, a, a )
ordena3 a b c 	| a <= b && b <= c = (a, b, c) 		-- Caso 1
	 	| a <= b = ordena3 a c b		-- Caso 2
		| b <= c = ordena3 b a c		-- Caso 3
		| otherwise = (c, b, a)			-- Caso 4

Las posibles permutaciones de un conjunto de 3 elementos distintos son obviamente 3! = 2 * 3 = 6. Sean 3 elementos ordenables distintos, a los que llamamos 1, 2, 3 tales que 1 < 2 < 3, veamos como trata el programa estas 6 permutaciones distintas:

ordena3 1 2 3 Caso 1 -> (1 ,2 ,3)
ordena3 1 3 2 Caso 2 -> ordena3 1 ,2 ,3; Caso 1 -> (1 ,2 ,3)
ordena3 2 1 3 Caso 3 -> ordena3 1 ,2 ,3; Caso 1 -> (1 ,2 ,3)
ordena3 2 3 1 Caso 2 -> ordena3 2 ,1 ,3; Caso 3 -> ordena3 1 ,2 ,3; Caso 1 -> (1 ,2 ,3)
ordena3 3 1 2 Caso 3 -> ordena3 1 ,3 ,2; Caso 2 -> ordena3 1 ,2 ,3; Caso 1 -> (1 ,2 ,3)
ordena3 3 2 1 Caso 4 -> (1 ,2 ,3)

Las condiciones exhaustuan todos los caso y el algoritmo termina siempre. ( No se consideran los casos en que existen elementos duplicados en la explicación ya que es trivial ver que por el uso de <= en las condiciones están contemplados en la definición de la función. )

Referencias

Razonando con Haskell;Blas C. Ruiz, Francisco Gutiérrez, Pablo Guerrero y José E. Gallardo;Thomson Editores Spain;2004

Autor: Emiliano Martínez Luque.

Comments Off on Razonando sobre corrección de Programas en Haskell